1
1
//! http-client implementation for fetch
2
2
3
- use super :: { Body , Error , HttpClient , Request , Response } ;
3
+ use super :: { http_types :: Headers , Body , Error , HttpClient , Request , Response } ;
4
4
5
5
use futures:: future:: BoxFuture ;
6
6
use futures:: prelude:: * ;
7
7
8
- use std:: io ;
8
+ use std:: convert :: TryFrom ;
9
9
use std:: pin:: Pin ;
10
10
use std:: task:: { Context , Poll } ;
11
11
@@ -31,17 +31,16 @@ impl Clone for WasmClient {
31
31
impl HttpClient for WasmClient {
32
32
fn send ( & self , req : Request ) -> BoxFuture < ' static , Result < Response , Error > > {
33
33
let fut = Box :: pin ( async move {
34
- let url = format ! ( "{}" , req. uri( ) ) ;
35
- let req = fetch:: new ( req. method ( ) . as_str ( ) , & url) ;
34
+ let req: fetch:: Request = fetch:: Request :: new ( req) . await ?;
36
35
let mut res = req. send ( ) . await ?;
37
36
38
37
let body = res. body_bytes ( ) ;
39
- let mut response = Response :: new ( Body :: from ( body ) ) ;
40
- * response . status_mut ( ) = http :: StatusCode :: from_u16 ( res. status ( ) ) . unwrap ( ) ;
41
-
38
+ let mut response =
39
+ Response :: new ( http_types :: StatusCode :: try_from ( res. status ( ) ) . unwrap ( ) ) ;
40
+ response . set_body ( Body :: from ( body ) ) ;
42
41
for ( name, value) in res. headers ( ) {
43
- let name: http :: header :: HeaderName = name. parse ( ) . unwrap ( ) ;
44
- response. headers_mut ( ) . insert ( name, value. parse ( ) . unwrap ( ) ) ;
42
+ let name: http_types :: headers :: HeaderName = name. parse ( ) . unwrap ( ) ;
43
+ response. insert_header ( & name, value) ;
45
44
}
46
45
47
46
Ok ( response)
@@ -51,17 +50,16 @@ impl HttpClient for WasmClient {
51
50
}
52
51
}
53
52
54
- // This type e
55
53
struct InnerFuture {
56
- fut : Pin < Box < dyn Future < Output = Result < Response , io :: Error > > + ' static > > ,
54
+ fut : Pin < Box < dyn Future < Output = Result < Response , Error > > + ' static > > ,
57
55
}
58
56
59
57
// This is safe because WASM doesn't have threads yet. Once WASM supports threads we should use a
60
58
// thread to park the blocking implementation until it's been completed.
61
59
unsafe impl Send for InnerFuture { }
62
60
63
61
impl Future for InnerFuture {
64
- type Output = Result < Response , io :: Error > ;
62
+ type Output = Result < Response , Error > ;
65
63
66
64
fn poll ( mut self : Pin < & mut Self > , cx : & mut Context < ' _ > ) -> Poll < Self :: Output > {
67
65
// This is safe because we're only using this future as a pass-through for the inner
@@ -75,42 +73,87 @@ mod fetch {
75
73
use js_sys:: { Array , ArrayBuffer , Reflect , Uint8Array } ;
76
74
use wasm_bindgen:: JsCast ;
77
75
use wasm_bindgen_futures:: JsFuture ;
78
- use web_sys:: window;
79
- use web_sys:: RequestInit ;
76
+ use web_sys:: { window, RequestInit } ;
80
77
81
- use std:: io;
82
78
use std:: iter:: { IntoIterator , Iterator } ;
79
+ use std:: pin:: Pin ;
80
+
81
+ use http_types:: StatusCode ;
82
+
83
+ use crate :: Error ;
83
84
84
85
/// Create a new fetch request.
85
- pub ( crate ) fn new ( method : impl AsRef < str > , url : impl AsRef < str > ) -> Request {
86
- Request :: new ( method, url)
87
- }
88
86
89
87
/// An HTTP Fetch Request.
90
88
pub ( crate ) struct Request {
91
- init : RequestInit ,
92
- url : String ,
89
+ request : web_sys:: Request ,
90
+ /// This field stores the body of the request to ensure it stays allocated as long as the request needs it.
91
+ #[ allow( dead_code) ]
92
+ body_buf : Pin < Vec < u8 > > ,
93
93
}
94
94
95
95
impl Request {
96
96
/// Create a new instance.
97
- pub ( crate ) fn new ( method : impl AsRef < str > , url : impl AsRef < str > ) -> Self {
98
- let mut init = web_sys:: RequestInit :: new ( ) ;
99
- init. method ( method. as_ref ( ) ) ;
100
- Self {
101
- init,
102
- url : url. as_ref ( ) . to_owned ( ) ,
97
+ pub ( crate ) async fn new ( mut req : super :: Request ) -> Result < Self , Error > {
98
+ // create a fetch request initaliser
99
+ let mut init = RequestInit :: new ( ) ;
100
+
101
+ // set the fetch method
102
+ init. method ( req. method ( ) . as_ref ( ) ) ;
103
+
104
+ let uri = req. url ( ) . to_string ( ) ;
105
+ let body = req. take_body ( ) ;
106
+
107
+ // convert the body into a uint8 array
108
+ // needs to be pinned and retained inside the Request because the Uint8Array passed to
109
+ // js is just a portal into WASM linear memory, and if the underlying data is moved the
110
+ // js ref will become silently invalid
111
+ let body_buf = body. into_bytes ( ) . await . map_err ( |_| {
112
+ Error :: from_str ( StatusCode :: BadRequest , "could not read body into a buffer" )
113
+ } ) ?;
114
+ let body_pinned = Pin :: new ( body_buf) ;
115
+ if body_pinned. len ( ) > 0 {
116
+ let uint_8_array = unsafe { js_sys:: Uint8Array :: view ( & body_pinned) } ;
117
+ init. body ( Some ( & uint_8_array) ) ;
103
118
}
119
+
120
+ let request = web_sys:: Request :: new_with_str_and_init ( & uri, & init) . map_err ( |e| {
121
+ Error :: from_str (
122
+ StatusCode :: BadRequest ,
123
+ format ! ( "failed to create request: {:?}" , e) ,
124
+ )
125
+ } ) ?;
126
+
127
+ // add any fetch headers
128
+ let headers: & mut super :: Headers = req. as_mut ( ) ;
129
+ for ( name, value) in headers. iter ( ) {
130
+ let name = name. as_str ( ) ;
131
+ let value = value. as_str ( ) ;
132
+
133
+ request. headers ( ) . set ( name, value) . map_err ( |_| {
134
+ Error :: from_str (
135
+ StatusCode :: BadRequest ,
136
+ format ! ( "could not add header: {} = {}" , name, value) ,
137
+ )
138
+ } ) ?;
139
+ }
140
+
141
+ Ok ( Self {
142
+ request,
143
+ body_buf : body_pinned,
144
+ } )
104
145
}
105
146
106
147
/// Submit a request
107
148
// TODO(yoshuawuyts): turn this into a `Future` impl on `Request` instead.
108
- pub ( crate ) async fn send ( self ) -> Result < Response , io :: Error > {
149
+ pub ( crate ) async fn send ( self ) -> Result < Response , Error > {
109
150
// Send the request.
110
151
let window = window ( ) . expect ( "A global window object could not be found" ) ;
111
- let request = web_sys:: Request :: new_with_str_and_init ( & self . url , & self . init ) . unwrap ( ) ;
112
- let promise = window. fetch_with_request ( & request) ;
113
- let resp = JsFuture :: from ( promise) . await . unwrap ( ) ;
152
+ let promise = window. fetch_with_request ( & self . request ) ;
153
+ let resp = JsFuture :: from ( promise)
154
+ . await
155
+ . map_err ( |e| Error :: from_str ( StatusCode :: BadRequest , format ! ( "{:?}" , e) ) ) ?;
156
+
114
157
debug_assert ! ( resp. is_instance_of:: <web_sys:: Response >( ) ) ;
115
158
let res: web_sys:: Response = resp. dyn_into ( ) . unwrap ( ) ;
116
159
0 commit comments