1#![allow(unreachable_code)]
2
3use crate::{StreamingError, reqwest_error_to_request_error};
4use bytes::Bytes;
5use dioxus_fullstack_core::RequestError;
6use futures::Stream;
7use futures::{TryFutureExt, TryStreamExt};
8use headers::{ContentType, Header};
9use http::{Extensions, HeaderMap, HeaderName, HeaderValue, Method, StatusCode, response::Parts};
10use send_wrapper::SendWrapper;
11use serde::{Serialize, de::DeserializeOwned};
12use std::sync::{LazyLock, Mutex, OnceLock, RwLock};
13use std::{fmt::Display, pin::Pin, prelude::rust_2024::Future};
14use url::Url;
15
16pub static GLOBAL_REQUEST_CLIENT: OnceLock<reqwest::Client> = OnceLock::new();
17
18#[cfg(not(target_arch = "wasm32"))]
23#[doc(hidden)]
24pub fn install_default_crypto_provider() {
25 let _ = rustls::crypto::ring::default_provider().install_default();
26}
27
28#[cfg(not(target_arch = "wasm32"))]
31fn rustls_client_config() -> rustls::ClientConfig {
32 let roots = rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
33 let mut config = rustls::ClientConfig::builder_with_provider(std::sync::Arc::new(
34 rustls::crypto::ring::default_provider(),
35 ))
36 .with_safe_default_protocol_versions()
37 .expect("ring supports the default TLS versions")
38 .with_root_certificates(roots)
39 .with_no_client_auth();
40 config.alpn_protocols = vec![b"http/1.1".to_vec()];
42 config
43}
44
45pub type ClientResult = Result<ClientResponse, RequestError>;
46
47pub struct ClientRequest {
48 pub url: Url,
49 pub headers: HeaderMap,
50 pub method: Method,
51 pub extensions: Extensions,
52}
53
54impl ClientRequest {
55 pub fn new(method: http::Method, path: String, params: &impl Serialize) -> Self {
57 Self::fetch_inner(method, path, serde_qs::to_string(params).unwrap())
58 }
59
60 fn fetch_inner(method: http::Method, path: String, query: String) -> ClientRequest {
62 let mut server_url = get_server_url();
64
65 if server_url.is_empty() {
66 server_url = "http://this.is.not.a.real.url:9000";
67 }
68
69 let url = format!(
70 "{server_url}{path}{params}",
71 params = if query.is_empty() {
72 "".to_string()
73 } else {
74 format!("?{}", query)
75 }
76 )
77 .parse()
78 .unwrap();
79
80 let headers = get_request_headers();
81
82 ClientRequest {
83 method,
84 url,
85 headers,
86 extensions: Extensions::new(),
87 }
88 }
89
90 pub fn method(&self) -> &Method {
92 &self.method
93 }
94
95 pub fn url(&self) -> &Url {
96 &self.url
97 }
98
99 pub fn extend_query(mut self, query: &impl Serialize) -> Self {
104 let old_query = self.url.query().unwrap_or("");
105 let new_query = serde_qs::to_string(query).unwrap();
106 let combined_query = format!(
107 "{}{}{}",
108 old_query,
109 if old_query.is_empty() { "" } else { "&" },
110 new_query
111 );
112 self.url.set_query(Some(&combined_query));
113 self
114 }
115
116 pub fn header(
118 mut self,
119 name: impl TryInto<HeaderName, Error = impl Display>,
120 value: impl TryInto<HeaderValue, Error = impl Display>,
121 ) -> Result<Self, RequestError> {
122 self.headers.append(
123 name.try_into()
124 .map_err(|d| RequestError::Builder(d.to_string()))?,
125 value
126 .try_into()
127 .map_err(|d| RequestError::Builder(d.to_string()))?,
128 );
129 Ok(self)
130 }
131
132 pub fn typed_header<H: Header>(mut self, header: H) -> Self {
134 let mut headers = vec![];
135 header.encode(&mut headers);
136 for header in headers {
137 self.headers.append(H::name(), header);
138 }
139 self
140 }
141
142 pub fn new_reqwest_client() -> reqwest::Client {
144 #[allow(unused_mut)]
145 let mut client = reqwest::Client::builder();
146
147 #[cfg(not(target_arch = "wasm32"))]
148 {
149 use std::sync::Arc;
150 use std::sync::LazyLock;
151
152 static COOKIES: LazyLock<Arc<reqwest::cookie::Jar>> =
153 LazyLock::new(|| Arc::new(reqwest::cookie::Jar::default()));
154
155 client = client
156 .cookie_store(true)
157 .cookie_provider(COOKIES.clone())
158 .tls_backend_preconfigured(rustls_client_config());
159 }
160
161 client.build().unwrap()
162 }
163
164 pub fn new_reqwest_request(&self) -> reqwest::RequestBuilder {
168 let client = GLOBAL_REQUEST_CLIENT.get_or_init(Self::new_reqwest_client);
169
170 let mut req = client
171 .request(self.method.clone(), self.url.clone())
172 .header("X-Request-Client", "dioxus");
173
174 for (key, value) in self.headers.iter() {
175 req = req.header(key, value);
176 }
177
178 req
179 }
180
181 #[cfg(feature = "web")]
183 pub fn new_gloo_request(&self) -> gloo_net::http::RequestBuilder {
184 let mut builder = gloo_net::http::RequestBuilder::new(
185 format!(
186 "{path}{query_string}",
187 path = self.url.path(),
188 query_string = self
189 .url
190 .query()
191 .map(|query| format!("?{query}"))
192 .unwrap_or_default()
193 )
194 .as_str(),
195 )
196 .header("X-Request-Client", "dioxus")
197 .method(self.method.clone());
198
199 for (key, value) in self.headers.iter() {
200 let value = match value.to_str() {
201 Ok(v) => v,
202 Err(er) => {
203 tracing::error!("Error converting header {key} value: {}", er);
204 continue;
205 }
206 };
207
208 builder = builder.header(key.as_str(), value);
209 }
210
211 builder
212 }
213
214 #[cfg(not(target_arch = "wasm32"))]
216 pub async fn send_multipart(
217 self,
218 form: &dioxus_html::FormData,
219 ) -> Result<ClientResponse, RequestError> {
220 let mut outgoing = reqwest::multipart::Form::new();
221
222 for (key, value) in form.values() {
223 match value {
224 dioxus_html::FormValue::Text(text) => {
225 outgoing = outgoing.text(key.to_string(), text.to_string());
226 }
227 dioxus_html::FormValue::File(Some(file_data)) => {
228 outgoing = outgoing
229 .file(key.to_string(), file_data.path())
230 .await
231 .map_err(|e| {
232 RequestError::Builder(format!(
233 "Failed to add file to multipart form: {e}",
234 ))
235 })?;
236 }
237 dioxus_html::FormValue::File(None) => {
238 outgoing = outgoing.part(key.to_string(), reqwest::multipart::Part::bytes(b""));
240 }
241 }
242 }
243
244 let res = self
245 .new_reqwest_request()
246 .multipart(outgoing)
247 .send()
248 .await
249 .map_err(reqwest_error_to_request_error)?;
250
251 Ok(ClientResponse {
252 response: Box::new(res),
253 extensions: self.extensions,
254 })
255 }
256
257 pub async fn send_form(self, data: &impl Serialize) -> Result<ClientResponse, RequestError> {
258 if matches!(*self.method(), Method::GET | Method::HEAD) {
261 return self.extend_query(data).send_empty_body().await;
262 }
263
264 let body =
265 serde_urlencoded::to_string(data).map_err(|err| RequestError::Body(err.to_string()))?;
266
267 self.typed_header(ContentType::form_url_encoded())
268 .send_raw_bytes(body)
269 .await
270 }
271
272 pub async fn send_empty_body(self) -> Result<ClientResponse, RequestError> {
274 #[cfg(feature = "web")]
275 if cfg!(target_arch = "wasm32") {
276 return self.send_js_value(wasm_bindgen::JsValue::UNDEFINED).await;
277 }
278
279 #[cfg(not(target_arch = "wasm32"))]
280 {
281 let res = self
282 .new_reqwest_request()
283 .send()
284 .await
285 .map_err(reqwest_error_to_request_error)?;
286
287 return Ok(ClientResponse {
288 response: Box::new(res),
289 extensions: self.extensions,
290 });
291 }
292
293 unimplemented!()
294 }
295
296 pub async fn send_raw_bytes(
297 self,
298 bytes: impl Into<Bytes>,
299 ) -> Result<ClientResponse, RequestError> {
300 #[cfg(feature = "web")]
301 if cfg!(target_arch = "wasm32") {
302 let bytes = bytes.into();
303 let uint_8_array = js_sys::Uint8Array::from(&bytes[..]);
304 return self.send_js_value(uint_8_array.into()).await;
305 }
306
307 #[cfg(not(target_arch = "wasm32"))]
308 {
309 let res = self
310 .new_reqwest_request()
311 .body(bytes.into())
312 .send()
313 .await
314 .map_err(reqwest_error_to_request_error)?;
315
316 return Ok(ClientResponse {
317 response: Box::new(res),
318 extensions: self.extensions,
319 });
320 }
321
322 unimplemented!()
323 }
324
325 pub async fn send_text(
327 self,
328 text: impl Into<String> + Into<Bytes>,
329 ) -> Result<ClientResponse, RequestError> {
330 self.typed_header(ContentType::text_utf8())
331 .send_raw_bytes(text)
332 .await
333 }
334
335 pub async fn send_json(self, json: &impl Serialize) -> Result<ClientResponse, RequestError> {
337 let bytes =
338 serde_json::to_vec(json).map_err(|e| RequestError::Serialization(e.to_string()))?;
339
340 if bytes.is_empty() || bytes == b"{}" || bytes == b"null" {
341 return self.send_empty_body().await;
342 }
343
344 self.typed_header(ContentType::json())
345 .send_raw_bytes(bytes)
346 .await
347 }
348
349 pub async fn send_body_stream(
350 self,
351 stream: impl Stream<Item = Result<Bytes, StreamingError>> + Send + 'static,
352 ) -> Result<ClientResponse, RequestError> {
353 #[cfg(not(target_arch = "wasm32"))]
354 {
355 let res = self
356 .new_reqwest_request()
357 .body(reqwest::Body::wrap_stream(stream))
358 .send()
359 .await
360 .map_err(reqwest_error_to_request_error)?;
361
362 return Ok(ClientResponse {
363 response: Box::new(res),
364 extensions: self.extensions,
365 });
366 }
367
368 #[cfg(feature = "web")]
371 {
372 use wasm_bindgen::JsValue;
373
374 let stream: Vec<Bytes> = stream.try_collect().await.map_err(|e| {
375 RequestError::Request(format!("Error collecting stream for request body: {}", e))
376 })?;
377
378 let uint_8_array =
379 js_sys::Uint8Array::new_with_length(stream.iter().map(|b| b.len() as u32).sum());
380
381 let mut offset = 0;
382 for chunk in stream {
383 uint_8_array.set(&js_sys::Uint8Array::from(&chunk[..]), offset);
384 offset += chunk.len() as u32;
385 }
386
387 return self.send_js_value(JsValue::from(uint_8_array)).await;
388 }
389
390 unimplemented!()
391 }
392
393 #[cfg(feature = "web")]
394 pub async fn send_js_value(
395 self,
396 value: wasm_bindgen::JsValue,
397 ) -> Result<ClientResponse, RequestError> {
398 use std::str::FromStr;
399
400 let inner = self
401 .new_gloo_request()
402 .body(value)
403 .map_err(|e| RequestError::Request(e.to_string()))?
404 .send()
405 .await
406 .map_err(|e| RequestError::Request(e.to_string()))?;
407
408 let status = inner.status();
409 let url = inner
410 .url()
411 .parse()
412 .map_err(|e| RequestError::Request(format!("Error parsing response URL: {}", e)))?;
413
414 let headers = {
415 let mut map = HeaderMap::new();
416 for (key, value) in inner.headers().entries() {
417 if let Ok(header_value) = http::HeaderValue::from_str(&value) {
418 let header = HeaderName::from_str(&key).unwrap();
419 map.append(header, header_value);
420 }
421 }
422 map
423 };
424
425 let content_length = headers
426 .get(http::header::CONTENT_LENGTH)
427 .and_then(|val| val.to_str().ok())
428 .and_then(|s| s.parse::<u64>().ok());
429
430 let status = http::StatusCode::from_u16(status).unwrap_or(http::StatusCode::OK);
431
432 Ok(ClientResponse {
433 extensions: self.extensions,
434 response: Box::new(browser::WrappedGlooResponse {
435 inner,
436 headers,
437 status,
438 url,
439 content_length,
440 }),
441 })
442 }
443}
444
445unsafe impl Send for ClientRequest {}
447unsafe impl Sync for ClientRequest {}
448
449pub struct ClientResponse {
457 pub(crate) response: Box<dyn ClientResponseDriver>,
458 pub(crate) extensions: Extensions,
459}
460
461impl ClientResponse {
462 pub fn status(&self) -> StatusCode {
463 self.response.status()
464 }
465
466 pub fn headers(&self) -> &HeaderMap {
467 self.response.headers()
468 }
469
470 pub fn url(&self) -> &Url {
471 self.response.url()
472 }
473
474 pub fn content_length(&self) -> Option<u64> {
475 self.response.content_length()
476 }
477
478 pub async fn bytes(self) -> Result<Bytes, RequestError> {
479 self.response.bytes().await
480 }
481
482 pub fn bytes_stream(
483 self,
484 ) -> impl futures_util::Stream<Item = Result<Bytes, StreamingError>> + 'static + Unpin + Send
485 {
486 self.response.bytes_stream()
487 }
488
489 pub fn extensions(&self) -> &Extensions {
490 &self.extensions
491 }
492
493 pub fn extensions_mut(&mut self) -> &mut Extensions {
494 &mut self.extensions
495 }
496
497 pub async fn json<T: DeserializeOwned>(self) -> Result<T, RequestError> {
498 serde_json::from_slice(&self.bytes().await?)
499 .map_err(|e| RequestError::Decode(e.to_string()))
500 }
501
502 pub async fn text(self) -> Result<String, RequestError> {
503 self.response.text().await
504 }
505
506 pub fn make_parts(&self) -> Parts {
508 let mut response = http::response::Response::builder().status(self.response.status());
509
510 response = response.version(self.response.version());
511
512 for (key, value) in self.response.headers().iter() {
513 response = response.header(key, value);
514 }
515
516 let (parts, _) = response.body(()).unwrap().into_parts();
517
518 parts
519 }
520
521 pub fn into_parts(self) -> (Parts, impl Stream<Item = Result<Bytes, StreamingError>>) {
523 (self.make_parts(), self.bytes_stream())
524 }
525}
526
527pub fn set_server_url(url: &'static str) {
536 *ROOT_URL.write().unwrap() = Some(url);
537}
538
539pub fn get_server_url() -> &'static str {
541 ROOT_URL.read().unwrap().unwrap_or("")
542}
543
544static ROOT_URL: RwLock<Option<&'static str>> = RwLock::new(None);
545
546pub fn clear_request_headers() {
548 REQUEST_HEADERS.lock().unwrap().clear();
549}
550
551pub fn set_request_headers(headers: HeaderMap) {
553 *REQUEST_HEADERS.lock().unwrap() = headers;
554}
555
556pub fn get_request_headers() -> HeaderMap {
558 REQUEST_HEADERS.lock().unwrap().clone()
559}
560
561static REQUEST_HEADERS: LazyLock<Mutex<HeaderMap>> = LazyLock::new(|| Mutex::new(HeaderMap::new()));
562
563pub trait ClientResponseDriver {
564 fn status(&self) -> StatusCode;
565 fn headers(&self) -> &HeaderMap;
566 fn url(&self) -> &Url;
567 fn version(&self) -> http::Version {
568 http::Version::HTTP_2
569 }
570 fn content_length(&self) -> Option<u64>;
571 fn bytes(self: Box<Self>) -> Pin<Box<dyn Future<Output = Result<Bytes, RequestError>> + Send>>;
572 fn bytes_stream(
573 self: Box<Self>,
574 ) -> Pin<Box<dyn Stream<Item = Result<Bytes, StreamingError>> + 'static + Unpin + Send>>;
575
576 fn text(self: Box<Self>) -> Pin<Box<dyn Future<Output = Result<String, RequestError>> + Send>>;
577}
578
579mod native {
580 use futures::Stream;
581
582 use super::*;
583
584 impl ClientResponseDriver for reqwest::Response {
585 fn status(&self) -> http::StatusCode {
586 reqwest::Response::status(self)
587 }
588
589 fn version(&self) -> http::Version {
590 #[cfg(target_arch = "wasm32")]
591 {
592 return http::Version::HTTP_2;
593 }
594
595 reqwest::Response::version(self)
596 }
597
598 fn headers(&self) -> &http::HeaderMap {
599 reqwest::Response::headers(self)
600 }
601
602 fn url(&self) -> &url::Url {
603 reqwest::Response::url(self)
604 }
605
606 fn content_length(&self) -> Option<u64> {
607 reqwest::Response::content_length(self)
608 }
609
610 fn bytes(
611 self: Box<Self>,
612 ) -> Pin<Box<dyn Future<Output = Result<Bytes, RequestError>> + Send>> {
613 Box::pin(SendWrapper::new(async move {
614 reqwest::Response::bytes(*self)
615 .map_err(reqwest_error_to_request_error)
616 .await
617 }))
618 }
619
620 fn bytes_stream(
621 self: Box<Self>,
622 ) -> Pin<Box<dyn Stream<Item = Result<Bytes, StreamingError>> + 'static + Unpin + Send>>
623 {
624 Box::pin(SendWrapper::new(
625 reqwest::Response::bytes_stream(*self).map_err(|_| StreamingError::Failed),
626 ))
627 }
628
629 fn text(
630 self: Box<Self>,
631 ) -> Pin<Box<dyn Future<Output = Result<String, RequestError>> + Send>> {
632 Box::pin(SendWrapper::new(async move {
633 reqwest::Response::text(*self)
634 .map_err(reqwest_error_to_request_error)
635 .await
636 }))
637 }
638 }
639}
640
641#[cfg(feature = "web")]
642mod browser {
643 use crate::{ClientResponseDriver, StreamingError};
644 use bytes::Bytes;
645 use dioxus_fullstack_core::RequestError;
646 use futures::{Stream, StreamExt};
647 use http::{HeaderMap, StatusCode};
648 use js_sys::Uint8Array;
649 use send_wrapper::SendWrapper;
650 use std::{pin::Pin, prelude::rust_2024::Future};
651 use wasm_bindgen::JsCast;
652
653 pub(crate) struct WrappedGlooResponse {
654 pub(crate) inner: gloo_net::http::Response,
655 pub(crate) headers: HeaderMap,
656 pub(crate) status: StatusCode,
657 pub(crate) url: url::Url,
658 pub(crate) content_length: Option<u64>,
659 }
660
661 impl ClientResponseDriver for WrappedGlooResponse {
662 fn status(&self) -> StatusCode {
663 self.status
664 }
665
666 fn headers(&self) -> &HeaderMap {
667 &self.headers
668 }
669
670 fn url(&self) -> &url::Url {
671 &self.url
672 }
673
674 fn content_length(&self) -> Option<u64> {
675 self.content_length
676 }
677
678 fn bytes(
679 self: Box<Self>,
680 ) -> Pin<Box<dyn Future<Output = Result<Bytes, RequestError>> + Send>> {
681 Box::pin(SendWrapper::new(async move {
682 let bytes = self
683 .inner
684 .binary()
685 .await
686 .map_err(|e| RequestError::Request(e.to_string()))?;
687 Ok(bytes.into())
688 }))
689 }
690
691 fn bytes_stream(
692 self: Box<Self>,
693 ) -> Pin<Box<dyn Stream<Item = Result<Bytes, StreamingError>> + 'static + Unpin + Send>>
694 {
695 let body = match self.inner.body() {
696 Some(body) => body,
697 None => {
698 return Box::pin(SendWrapper::new(futures::stream::iter([Err(
699 StreamingError::Failed,
700 )])));
701 }
702 };
703
704 Box::pin(SendWrapper::new(
705 wasm_streams::ReadableStream::from_raw(body)
706 .into_stream()
707 .map(|chunk| {
708 let array = chunk
709 .map_err(|_| StreamingError::Failed)?
710 .dyn_into::<Uint8Array>()
711 .map_err(|_| StreamingError::Failed)?;
712 Ok(array.to_vec().into())
713 }),
714 ))
715 }
716
717 fn text(
718 self: Box<Self>,
719 ) -> Pin<Box<dyn Future<Output = Result<String, RequestError>> + Send>> {
720 Box::pin(SendWrapper::new(async move {
721 self.inner
722 .text()
723 .await
724 .map_err(|e| RequestError::Request(e.to_string()))
725 }))
726 }
727 }
728}