(mut self, json: &T) -> RequestBuilder {
+ let mut error = None;
+ if let Ok(ref mut req) = self.request {
+ match serde_json::to_vec(json) {
+ Ok(body) => {
+ req.headers_mut()
+ .insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
+ *req.body_mut() = Some(body.into());
+ }
+ Err(err) => error = Some(crate::error::builder(err)),
+ }
+ }
+ if let Some(err) = error {
+ self.request = Err(err);
+ }
+ self
+ }
+
+ /// Enable HTTP basic authentication.
+ pub fn basic_auth(self, username: U, password: Option) -> RequestBuilder
+ where
+ U: fmt::Display,
+ P: fmt::Display,
+ {
+ let header_value = crate::util::basic_auth(username, password);
+ self.header(crate::header::AUTHORIZATION, header_value)
+ }
+
+ /// Enable HTTP bearer authentication.
+ pub fn bearer_auth(self, token: T) -> RequestBuilder
+ where
+ T: fmt::Display,
+ {
+ let header_value = format!("Bearer {token}");
+ self.header(crate::header::AUTHORIZATION, header_value)
+ }
+
+ /// Set the request body.
+ pub fn body>(mut self, body: T) -> RequestBuilder {
+ if let Ok(ref mut req) = self.request {
+ req.body = Some(body.into());
+ }
+ self
+ }
+
+ /// Add a `Header` to this Request.
+ pub fn header(mut self, key: K, value: V) -> RequestBuilder
+ where
+ HeaderName: TryFrom,
+ >::Error: Into,
+ HeaderValue: TryFrom,
+ >::Error: Into,
+ {
+ let mut error = None;
+ if let Ok(ref mut req) = self.request {
+ match >::try_from(key) {
+ Ok(key) => match >::try_from(value) {
+ Ok(value) => {
+ req.headers_mut().append(key, value);
+ }
+ Err(e) => error = Some(crate::error::builder(e.into())),
+ },
+ Err(e) => error = Some(crate::error::builder(e.into())),
+ };
+ }
+ if let Some(err) = error {
+ self.request = Err(err);
+ }
+ self
+ }
+
+ /// Add a set of Headers to the existing ones on this Request.
+ ///
+ /// The headers will be merged in to any already set.
+ pub fn headers(mut self, headers: crate::header::HeaderMap) -> RequestBuilder {
+ if let Ok(ref mut req) = self.request {
+ crate::util::replace_headers(req.headers_mut(), headers);
+ }
+ self
+ }
+
+ /// Build a `Request`, which can be inspected, modified and executed with
+ /// `Client::execute()`.
+ pub fn build(self) -> crate::Result {
+ self.request
+ }
+
+ /// Build a `Request`, which can be inspected, modified and executed with
+ /// `Client::execute()`.
+ ///
+ /// This is similar to [`RequestBuilder::build()`], but also returns the
+ /// embedded `Client`.
+ pub fn build_split(self) -> (Client, crate::Result) {
+ (self.client, self.request)
+ }
+
+ /// Constructs the Request and sends it to the target URL, returning a
+ /// future Response.
+ ///
+ /// # Errors
+ ///
+ /// This method fails if there was an error while sending request.
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// # use reqwest::Error;
+ /// #
+ /// # async fn run() -> Result<(), Error> {
+ /// let response = reqwest::Client::new()
+ /// .get("https://hyper.rs")
+ /// .send()
+ /// .await?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub async fn send(self) -> crate::Result {
+ let req = self.request?;
+ self.client.execute_request(req)?.await
+ }
+
+ /// Attempt to clone the RequestBuilder.
+ ///
+ /// `None` is returned if the RequestBuilder can not be cloned.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// # use reqwest::Error;
+ /// #
+ /// # fn run() -> Result<(), Error> {
+ /// let client = reqwest::Client::new();
+ /// let builder = client.post("http://httpbin.org/post")
+ /// .body("from a &str!");
+ /// let clone = builder.try_clone();
+ /// assert!(clone.is_some());
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn try_clone(&self) -> Option {
+ self.request
+ .as_ref()
+ .ok()
+ .and_then(|req| req.try_clone())
+ .map(|req| RequestBuilder {
+ client: self.client.clone(),
+ request: Ok(req),
+ })
+ }
+}
+
+impl fmt::Debug for Request {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fmt_request_fields(&mut f.debug_struct("Request"), self).finish()
+ }
+}
+
+impl fmt::Debug for RequestBuilder {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let mut builder = f.debug_struct("RequestBuilder");
+ match self.request {
+ Ok(ref req) => fmt_request_fields(&mut builder, req).finish(),
+ Err(ref err) => builder.field("error", err).finish(),
+ }
+ }
+}
+
+fn fmt_request_fields<'a, 'b>(
+ f: &'a mut fmt::DebugStruct<'a, 'b>,
+ req: &Request,
+) -> &'a mut fmt::DebugStruct<'a, 'b> {
+ f.field("method", &req.method)
+ .field("url", &req.url)
+ .field("headers", &req.headers)
+}
+
+impl TryFrom> for Request
+where
+ T: Into,
+{
+ type Error = crate::Error;
+
+ fn try_from(req: HttpRequest) -> crate::Result {
+ let (parts, body) = req.into_parts();
+ let Parts {
+ method,
+ uri,
+ headers,
+ ..
+ } = parts;
+ let url = Url::parse(&uri.to_string()).map_err(crate::error::builder)?;
+ Ok(Request {
+ method,
+ url,
+ headers,
+ body: Some(body.into()),
+ })
+ }
+}
+
+impl TryFrom for HttpRequest {
+ type Error = crate::Error;
+
+ fn try_from(req: Request) -> crate::Result {
+ let Request {
+ method,
+ url,
+ headers,
+ body,
+ ..
+ } = req;
+
+ let mut req = HttpRequest::builder()
+ .method(method)
+ .uri(url.as_str())
+ .body(body.unwrap_or_else(|| Body::from(Bytes::default())))
+ .map_err(crate::error::builder)?;
+
+ *req.headers_mut() = headers;
+ Ok(req)
+ }
+}
diff --git a/src/wasm/component/response.rs b/src/wasm/component/response.rs
new file mode 100644
index 000000000..8c9b6d2ed
--- /dev/null
+++ b/src/wasm/component/response.rs
@@ -0,0 +1,201 @@
+use std::{fmt, io::Read as _};
+
+use bytes::Bytes;
+use http::{HeaderMap, StatusCode, Version};
+#[cfg(feature = "json")]
+use serde::de::DeserializeOwned;
+use url::Url;
+
+/// A Response to a submitted `Request`.
+pub struct Response {
+ http: http::Response,
+ // Boxed to save space (11 words to 1 word), and it's not accessed
+ // frequently internally.
+ url: Box,
+ // The incoming body must be persisted if streaming to keep the stream open
+ incoming_body: Option,
+}
+
+impl Response {
+ pub(super) fn new(
+ res: http::Response,
+ url: Url,
+ ) -> Response {
+ Response {
+ http: res,
+ url: Box::new(url),
+ incoming_body: None,
+ }
+ }
+
+ /// Get the `StatusCode` of this `Response`.
+ #[inline]
+ pub fn status(&self) -> StatusCode {
+ self.http.status()
+ }
+
+ /// Get the `Headers` of this `Response`.
+ #[inline]
+ pub fn headers(&self) -> &HeaderMap {
+ self.http.headers()
+ }
+
+ /// Get a mutable reference to the `Headers` of this `Response`.
+ #[inline]
+ pub fn headers_mut(&mut self) -> &mut HeaderMap {
+ self.http.headers_mut()
+ }
+
+ /// Get the content-length of this response, if known.
+ ///
+ /// Reasons it may not be known:
+ ///
+ /// - The server didn't send a `content-length` header.
+ /// - The response is compressed and automatically decoded (thus changing
+ /// the actual decoded length).
+ pub fn content_length(&self) -> Option {
+ self.headers()
+ .get(http::header::CONTENT_LENGTH)?
+ .to_str()
+ .ok()?
+ .parse()
+ .ok()
+ }
+
+ /// Get the final `Url` of this `Response`.
+ #[inline]
+ pub fn url(&self) -> &Url {
+ &self.url
+ }
+
+ /// Get the HTTP `Version` of this `Response`.
+ #[inline]
+ pub fn version(&self) -> Version {
+ self.http.version()
+ }
+
+ /// Try to deserialize the response body as JSON.
+ #[cfg(feature = "json")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
+ pub async fn json(self) -> crate::Result {
+ let full = self.bytes().await?;
+
+ serde_json::from_slice(&full).map_err(crate::error::decode)
+ }
+
+ /// Get the response as text
+ pub async fn text(self) -> crate::Result {
+ self.bytes()
+ .await
+ .map(|s| String::from_utf8_lossy(&s).to_string())
+ }
+
+ /// Get the response as bytes
+ pub async fn bytes(self) -> crate::Result {
+ let response_body = self
+ .http
+ .body()
+ .consume()
+ .map_err(|_| crate::error::decode("failed to consume response body"))?;
+ let body = {
+ let mut buf = vec![];
+ let mut stream = response_body
+ .stream()
+ .map_err(|_| crate::error::decode("failed to stream response body"))?;
+ InputStreamReader::from(&mut stream)
+ .read_to_end(&mut buf)
+ .map_err(crate::error::decode_io)?;
+ buf
+ };
+ let _trailers = wasi::http::types::IncomingBody::finish(response_body);
+ Ok(body.into())
+ }
+
+ /// Convert the response into a [`wasi::http::types::IncomingBody`] resource which can
+ /// then be used to stream the body.
+ #[cfg(feature = "stream")]
+ pub fn bytes_stream(&mut self) -> crate::Result {
+ let response_body = self
+ .http
+ .body()
+ .consume()
+ .map_err(|_| crate::error::decode("failed to consume response body"))?;
+ let stream = response_body
+ .stream()
+ .map_err(|_| crate::error::decode("failed to stream response body"));
+ // Dropping the incoming body when the stream is present will trap as the
+ // stream is a child resource of the incoming body.
+ self.incoming_body = Some(response_body);
+ stream
+ }
+
+ /// Turn a response into an error if the server returned an error.
+ pub fn error_for_status(self) -> crate::Result {
+ let status = self.status();
+ if status.is_client_error() || status.is_server_error() {
+ Err(crate::error::status_code(*self.url, status))
+ } else {
+ Ok(self)
+ }
+ }
+
+ /// Turn a reference to a response into an error if the server returned an error.
+ pub fn error_for_status_ref(&self) -> crate::Result<&Self> {
+ let status = self.status();
+ if status.is_client_error() || status.is_server_error() {
+ Err(crate::error::status_code(*self.url.clone(), status))
+ } else {
+ Ok(self)
+ }
+ }
+}
+
+impl fmt::Debug for Response {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct("Response")
+ .field("url", self.url())
+ .field("status", &self.status())
+ .field("headers", self.headers())
+ .finish()
+ }
+}
+
+/// Implements `std::io::Read` for a `wasi::io::streams::InputStream`.
+pub struct InputStreamReader<'a> {
+ stream: &'a mut wasi::io::streams::InputStream,
+}
+
+impl<'a> From<&'a mut wasi::io::streams::InputStream> for InputStreamReader<'a> {
+ fn from(stream: &'a mut wasi::io::streams::InputStream) -> Self {
+ Self { stream }
+ }
+}
+
+impl std::io::Read for InputStreamReader<'_> {
+ fn read(&mut self, buf: &mut [u8]) -> std::io::Result {
+ use std::io;
+ use wasi::io::streams::StreamError;
+
+ let n = buf
+ .len()
+ .try_into()
+ .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
+ match self.stream.blocking_read(n) {
+ Ok(chunk) => {
+ let n = chunk.len();
+ if n > buf.len() {
+ return Err(io::Error::new(
+ io::ErrorKind::Other,
+ "more bytes read than requested",
+ ));
+ }
+ buf[..n].copy_from_slice(&chunk);
+ Ok(n)
+ }
+ Err(StreamError::Closed) => Ok(0),
+ Err(StreamError::LastOperationFailed(e)) => {
+ Err(io::Error::new(io::ErrorKind::Other, e.to_debug_string()))
+ }
+ }
+ }
+}
diff --git a/src/wasm/body.rs b/src/wasm/js/body.rs
similarity index 96%
rename from src/wasm/body.rs
rename to src/wasm/js/body.rs
index 241aa8173..fbc117b95 100644
--- a/src/wasm/body.rs
+++ b/src/wasm/js/body.rs
@@ -225,7 +225,7 @@ mod tests {
let js_req = web_sys::Request::new_with_str_and_init("", &init)
.expect("could not create JS request");
let text_promise = js_req.text().expect("could not get text promise");
- let text = crate::wasm::promise::(text_promise)
+ let text = crate::wasm::js::promise::(text_promise)
.await
.expect("could not get request body as text");
@@ -247,7 +247,7 @@ mod tests {
let js_req = web_sys::Request::new_with_str_and_init("", &init)
.expect("could not create JS request");
let text_promise = js_req.text().expect("could not get text promise");
- let text = crate::wasm::promise::(text_promise)
+ let text = crate::wasm::js::promise::(text_promise)
.await
.expect("could not get request body as text");
@@ -273,7 +273,7 @@ mod tests {
let array_buffer_promise = js_req
.array_buffer()
.expect("could not get array_buffer promise");
- let array_buffer = crate::wasm::promise::(array_buffer_promise)
+ let array_buffer = crate::wasm::js::promise::(array_buffer_promise)
.await
.expect("could not get request body as array buffer");
@@ -301,7 +301,7 @@ mod tests {
let array_buffer_promise = js_req
.array_buffer()
.expect("could not get array_buffer promise");
- let array_buffer = crate::wasm::promise::(array_buffer_promise)
+ let array_buffer = crate::wasm::js::promise::(array_buffer_promise)
.await
.expect("could not get request body as array buffer");
diff --git a/src/wasm/client.rs b/src/wasm/js/client.rs
similarity index 100%
rename from src/wasm/client.rs
rename to src/wasm/js/client.rs
diff --git a/src/wasm/js/mod.rs b/src/wasm/js/mod.rs
new file mode 100644
index 000000000..e99fb11fb
--- /dev/null
+++ b/src/wasm/js/mod.rs
@@ -0,0 +1,53 @@
+use wasm_bindgen::JsCast;
+use web_sys::{AbortController, AbortSignal};
+
+mod body;
+mod client;
+/// TODO
+#[cfg(feature = "multipart")]
+pub mod multipart;
+mod request;
+mod response;
+
+pub use self::body::Body;
+pub use self::client::{Client, ClientBuilder};
+pub use self::request::{Request, RequestBuilder};
+pub use self::response::Response;
+
+async fn promise(promise: js_sys::Promise) -> Result
+where
+ T: JsCast,
+{
+ use wasm_bindgen_futures::JsFuture;
+
+ let js_val = JsFuture::from(promise).await.map_err(crate::error::wasm)?;
+
+ js_val
+ .dyn_into::()
+ .map_err(|_js_val| "promise resolved to unexpected type".into())
+}
+
+/// A guard that cancels a fetch request when dropped.
+struct AbortGuard {
+ ctrl: AbortController,
+}
+
+impl AbortGuard {
+ fn new() -> crate::Result {
+ Ok(AbortGuard {
+ ctrl: AbortController::new()
+ .map_err(crate::error::wasm)
+ .map_err(crate::error::builder)?,
+ })
+ }
+
+ fn signal(&self) -> AbortSignal {
+ self.ctrl.signal()
+ }
+}
+
+impl Drop for AbortGuard {
+ fn drop(&mut self) {
+ self.ctrl.abort();
+ }
+}
diff --git a/src/wasm/multipart.rs b/src/wasm/js/multipart.rs
similarity index 97%
rename from src/wasm/multipart.rs
rename to src/wasm/js/multipart.rs
index 9b5b4c951..3d29a95b4 100644
--- a/src/wasm/multipart.rs
+++ b/src/wasm/js/multipart.rs
@@ -377,7 +377,7 @@ mod tests {
let form_data_promise = js_req.form_data().expect("could not get form_data promise");
- let form_data = crate::wasm::promise::(form_data_promise)
+ let form_data = crate::wasm::js::promise::(form_data_promise)
.await
.expect("could not get body as form data");
@@ -387,7 +387,7 @@ mod tests {
assert_eq!(text_file.type_(), text_file_type);
let text_promise = text_file.text();
- let text = crate::wasm::promise::(text_promise)
+ let text = crate::wasm::js::promise::(text_promise)
.await
.expect("could not get text body as text");
assert_eq!(
@@ -408,7 +408,7 @@ mod tests {
assert_eq!(string, string_content);
let binary_array_buffer_promise = binary_file.array_buffer();
- let array_buffer = crate::wasm::promise::(binary_array_buffer_promise)
+ let array_buffer = crate::wasm::js::promise::(binary_array_buffer_promise)
.await
.expect("could not get request body as array buffer");
diff --git a/src/wasm/request.rs b/src/wasm/js/request.rs
similarity index 100%
rename from src/wasm/request.rs
rename to src/wasm/js/request.rs
diff --git a/src/wasm/response.rs b/src/wasm/js/response.rs
similarity index 99%
rename from src/wasm/response.rs
rename to src/wasm/js/response.rs
index 47a90d04d..9a00f7356 100644
--- a/src/wasm/response.rs
+++ b/src/wasm/js/response.rs
@@ -5,7 +5,7 @@ use http::{HeaderMap, StatusCode};
use js_sys::Uint8Array;
use url::Url;
-use crate::wasm::AbortGuard;
+use crate::wasm::js::AbortGuard;
#[cfg(feature = "stream")]
use wasm_bindgen::JsCast;
diff --git a/src/wasm/mod.rs b/src/wasm/mod.rs
index e99fb11fb..874947dbb 100644
--- a/src/wasm/mod.rs
+++ b/src/wasm/mod.rs
@@ -1,53 +1,9 @@
-use wasm_bindgen::JsCast;
-use web_sys::{AbortController, AbortSignal};
-
-mod body;
-mod client;
-/// TODO
-#[cfg(feature = "multipart")]
-pub mod multipart;
-mod request;
-mod response;
-
-pub use self::body::Body;
-pub use self::client::{Client, ClientBuilder};
-pub use self::request::{Request, RequestBuilder};
-pub use self::response::Response;
-
-async fn promise(promise: js_sys::Promise) -> Result
-where
- T: JsCast,
-{
- use wasm_bindgen_futures::JsFuture;
-
- let js_val = JsFuture::from(promise).await.map_err(crate::error::wasm)?;
-
- js_val
- .dyn_into::()
- .map_err(|_js_val| "promise resolved to unexpected type".into())
-}
-
-/// A guard that cancels a fetch request when dropped.
-struct AbortGuard {
- ctrl: AbortController,
-}
-
-impl AbortGuard {
- fn new() -> crate::Result {
- Ok(AbortGuard {
- ctrl: AbortController::new()
- .map_err(crate::error::wasm)
- .map_err(crate::error::builder)?,
- })
- }
-
- fn signal(&self) -> AbortSignal {
- self.ctrl.signal()
- }
-}
-
-impl Drop for AbortGuard {
- fn drop(&mut self) {
- self.ctrl.abort();
- }
-}
+#[cfg(all(target_os = "wasi", target_env = "p2"))]
+pub mod component;
+#[cfg(all(target_os = "wasi", target_env = "p2"))]
+pub use component::*;
+
+#[cfg(not(all(target_os = "wasi", target_env = "p2")))]
+pub mod js;
+#[cfg(not(all(target_os = "wasi", target_env = "p2")))]
+pub use js::*;