1use http_types::{Request, Response, StatusCode};
4
5#[cfg(not(target_arch = "wasm32"))]
6mod decode;
7#[cfg(not(target_arch = "wasm32"))]
8mod encode;
9
10#[cfg(not(target_arch = "wasm32"))]
11pub use decode::decode;
12#[cfg(not(target_arch = "wasm32"))]
13pub use encode::Encoder;
14use async_tls::TlsConnector;
15use async_std::net::TcpStream;
16
17use futures::io::{AsyncRead as Read, AsyncWrite as Write, self};
18
19#[cfg(not(target_arch = "wasm32"))]
20async fn native_connect<RW>(mut stream: RW, req: Request) -> http_types::Result<Response>
21where
22 RW: Read + Write + Send + Sync + Unpin + 'static,
23{
24 let mut req = Encoder::new(req);
25 log::trace!("> {:?}", &req);
26
27 io::copy(&mut req, &mut stream).await?;
28
29 let res = decode(stream).await?;
30 log::trace!("< {:?}", &res);
31
32 Ok(res)
33}
34
35pub async fn connect(req: Request) -> http_types::Result<Response> {
37 #[cfg(target_arch = "wasm32")]
38 {
39 use futures::prelude::*;
40 use send_wrapper::SendWrapper;
41 SendWrapper::new(async move {
42 let req: fetch::Request = fetch::Request::new(req).await?;
43 let mut res = req.send().await?;
44
45 let body = res.body_bytes();
46 let mut response =
47 Response::new(http_types::StatusCode::try_from(res.status()).unwrap());
48 response.set_body(Body::from(body));
49 for (name, value) in res.headers() {
50 let name: http_types::headers::HeaderName = name.parse().unwrap();
51 response.append_header(&name, value);
52 }
53
54 Ok(response)
55 })
56 .await
57 }
58
59 #[cfg(not(target_arch = "wasm32"))]
60 if req.url().scheme() == "https" {
61 let stream = TcpStream::connect(format!(
62 "{}:{}",
63 req.url().host_str().ok_or_else(|| {
64 http_types::Error::from_str(
65 StatusCode::UnprocessableEntity,
66 "No host in request URL",
67 )
68 })?,
69 req.url().port_or_known_default().ok_or_else(|| {
70 http_types::Error::from_str(
71 StatusCode::UnprocessableEntity,
72 "No port in request URL",
73 )
74 })?
75 ))
76 .await?;
77 native_connect(
78 TlsConnector::default()
79 .connect(
80 req.host().ok_or_else(|| {
81 http_types::Error::from_str(
82 StatusCode::UnprocessableEntity,
83 "No host in request URL",
84 )
85 })?,
86 stream,
87 )
88 .await?,
89 req,
90 )
91 .await
92 } else {
93 native_connect(
94 TcpStream::connect(format!(
95 "{}:{}",
96 req.url().host_str().ok_or_else(|| {
97 http_types::Error::from_str(
98 StatusCode::UnprocessableEntity,
99 "No host in request URL",
100 )
101 })?,
102 req.url().port_or_known_default().ok_or_else(|| {
103 http_types::Error::from_str(
104 StatusCode::UnprocessableEntity,
105 "No port in request URL",
106 )
107 })?
108 ))
109 .await?,
110 req,
111 )
112 .await
113 }
114}
115
116#[cfg(target_arch = "wasm32")]
117mod fetch {
118 use js_sys::{Array, ArrayBuffer, Reflect, Uint8Array};
119 use wasm_bindgen::{prelude::*, JsCast};
120 use wasm_bindgen_futures::JsFuture;
121 use web_sys::{RequestInit, Window, WorkerGlobalScope};
122
123 use std::iter::{IntoIterator, Iterator};
124 use std::pin::Pin;
125
126 use http_types::StatusCode;
127
128 use http_types::Error;
129
130 enum WindowOrWorker {
131 Window(Window),
132 Worker(WorkerGlobalScope),
133 }
134
135 impl WindowOrWorker {
136 fn new() -> Self {
137 #[wasm_bindgen]
138 extern "C" {
139 type Global;
140
141 #[wasm_bindgen(method, getter, js_name = Window)]
142 fn window(this: &Global) -> JsValue;
143
144 #[wasm_bindgen(method, getter, js_name = WorkerGlobalScope)]
145 fn worker(this: &Global) -> JsValue;
146 }
147
148 let global: Global = js_sys::global().unchecked_into();
149
150 if !global.window().is_undefined() {
151 Self::Window(global.unchecked_into())
152 } else if !global.worker().is_undefined() {
153 Self::Worker(global.unchecked_into())
154 } else {
155 panic!("Only supported in a browser or web worker");
156 }
157 }
158 }
159
160 pub(crate) struct Request {
164 request: web_sys::Request,
165 #[allow(dead_code)]
167 body_buf: Pin<Vec<u8>>,
168 }
169
170 impl Request {
171 pub(crate) async fn new(mut req: super::Request) -> Result<Self, Error> {
173 let mut init = RequestInit::new();
175
176 init.method(req.method().as_ref());
178
179 let uri = req.url().to_string();
180 let body = req.take_body();
181
182 let body_buf = body.into_bytes().await.map_err(|_| {
187 Error::from_str(StatusCode::BadRequest, "could not read body into a buffer")
188 })?;
189 let body_pinned = Pin::new(body_buf);
190 if body_pinned.len() > 0 {
191 let uint_8_array = unsafe { js_sys::Uint8Array::view(&body_pinned) };
192 init.body(Some(&uint_8_array));
193 }
194
195 let request = web_sys::Request::new_with_str_and_init(&uri, &init).map_err(|e| {
196 Error::from_str(
197 StatusCode::BadRequest,
198 format!("failed to create request: {:?}", e),
199 )
200 })?;
201
202 let headers: &mut super::Headers = req.as_mut();
204 for (name, value) in headers.iter() {
205 let name = name.as_str();
206 let value = value.as_str();
207
208 request.headers().set(name, value).map_err(|_| {
209 Error::from_str(
210 StatusCode::BadRequest,
211 format!("could not add header: {} = {}", name, value),
212 )
213 })?;
214 }
215
216 Ok(Self {
217 request,
218 body_buf: body_pinned,
219 })
220 }
221
222 pub(crate) async fn send(self) -> Result<Response, Error> {
225 let scope = WindowOrWorker::new();
227 let promise = match scope {
228 WindowOrWorker::Window(window) => window.fetch_with_request(&self.request),
229 WindowOrWorker::Worker(worker) => worker.fetch_with_request(&self.request),
230 };
231 let resp = JsFuture::from(promise)
232 .await
233 .map_err(|e| Error::from_str(StatusCode::BadRequest, format!("{:?}", e)))?;
234
235 debug_assert!(resp.is_instance_of::<web_sys::Response>());
236 let res: web_sys::Response = resp.dyn_into().unwrap();
237
238 let promise = res.array_buffer().unwrap();
240 let resp = JsFuture::from(promise).await.unwrap();
241 debug_assert!(resp.is_instance_of::<js_sys::ArrayBuffer>());
242 let buf: ArrayBuffer = resp.dyn_into().unwrap();
243 let slice = Uint8Array::new(&buf);
244 let mut body: Vec<u8> = vec![0; slice.length() as usize];
245 slice.copy_to(&mut body);
246
247 Ok(Response::new(res, body))
248 }
249 }
250
251 pub(crate) struct Response {
253 res: web_sys::Response,
254 body: Option<Vec<u8>>,
255 }
256
257 impl Response {
258 fn new(res: web_sys::Response, body: Vec<u8>) -> Self {
259 Self {
260 res,
261 body: Some(body),
262 }
263 }
264
265 pub(crate) fn headers(&self) -> Headers {
267 Headers {
268 headers: self.res.headers(),
269 }
270 }
271
272 pub(crate) fn body_bytes(&mut self) -> Vec<u8> {
276 self.body.take().unwrap_or_else(|| vec![])
277 }
278
279 pub(crate) fn status(&self) -> u16 {
281 self.res.status()
282 }
283 }
284
285 pub(crate) struct Headers {
287 headers: web_sys::Headers,
288 }
289
290 impl IntoIterator for Headers {
291 type Item = (String, String);
292 type IntoIter = HeadersIter;
293
294 fn into_iter(self) -> Self::IntoIter {
295 HeadersIter {
296 iter: js_sys::try_iter(&self.headers).unwrap().unwrap(),
297 }
298 }
299 }
300
301 pub(crate) struct HeadersIter {
303 iter: js_sys::IntoIter,
304 }
305
306 impl Iterator for HeadersIter {
307 type Item = (String, String);
308
309 fn next(&mut self) -> Option<Self::Item> {
310 let pair = self.iter.next()?;
311
312 let array: Array = pair.unwrap().into();
313 let vals = array.values();
314
315 let prop = String::from("value").into();
316 let key = Reflect::get(&vals.next().unwrap(), &prop).unwrap();
317 let value = Reflect::get(&vals.next().unwrap(), &prop).unwrap();
318
319 Some((
320 key.as_string().to_owned().unwrap(),
321 value.as_string().to_owned().unwrap(),
322 ))
323 }
324 }
325}