Skip to content

Commit

Permalink
Impl timeouts for reqwest-wrap
Browse files Browse the repository at this point in the history
  • Loading branch information
kettlebell committed Apr 16, 2022
1 parent f84ddd5 commit f0027c6
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 1 deletion.
2 changes: 2 additions & 0 deletions reqwest-wrap/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ features = [
"Request",
"RequestInit",
"RequestMode",
"AbortController",
"AbortSignal",
"Response",
"Window",
"FormData",
Expand Down
31 changes: 31 additions & 0 deletions reqwest-wrap/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,35 @@ if_wasm! {
pub use http::{StatusCode, Version};
pub use url::Url;
pub use self::wasm::{Body, Client, ClientBuilder, Request, RequestBuilder, Response};

/// Shortcut method to quickly make a `GET` request.
///
/// See also the methods on the [`reqwest::Response`](./struct.Response.html)
/// type.
///
/// **NOTE**: This function creates a new internal `Client` on each call,
/// and so should not be used if making many requests. Create a
/// [`Client`](./struct.Client.html) instead.
///
/// # Examples
///
/// ```rust
/// # async fn run() -> Result<(), reqwest::Error> {
/// let body = reqwest::get("https://www.rust-lang.org").await?
/// .text().await?;
/// # Ok(())
/// # }
/// ```
///
/// # Errors
///
/// This function fails if:
///
/// - native TLS backend cannot be initialized
/// - supplied `Url` cannot be parsed
/// - there was an error while sending request
/// - redirect limit was exhausted
pub async fn get<T: IntoUrl>(url: T) -> crate::Result<Response> {
Client::builder().build()?.get(url).send().await
}
}
27 changes: 26 additions & 1 deletion reqwest-wrap/src/wasm/client.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use http::{HeaderMap, Method};
use js_sys::{Promise, JSON};
use std::rc::Rc;
use std::{fmt, future::Future, sync::Arc};
use url::Url;
use wasm_bindgen::prelude::{wasm_bindgen, UnwrapThrowExt as _};
use wasm_bindgen::prelude::{wasm_bindgen, Closure, UnwrapThrowExt as _};
use wasm_bindgen::JsCast;

use super::{Request, RequestBuilder, Response};
use crate::IntoUrl;
Expand All @@ -11,6 +13,9 @@ use crate::IntoUrl;
extern "C" {
#[wasm_bindgen(js_name = fetch)]
fn fetch_with_request(input: &web_sys::Request) -> Promise;

#[wasm_bindgen(js_namespace = console, js_name = log)]
fn debug(s: &str);
}

fn js_fetch(req: &web_sys::Request) -> Promise {
Expand Down Expand Up @@ -183,6 +188,9 @@ impl fmt::Debug for ClientBuilder {
async fn fetch(req: Request) -> crate::Result<Response> {
// Build the js Request
let mut init = web_sys::RequestInit::new();
let abort_controller = Rc::new(web_sys::AbortController::new().unwrap());
let abort_signal = Rc::clone(&abort_controller).signal();
let window = web_sys::window().expect("should have a window in this context");
init.method(req.method().as_str());

// convert HeaderMap to Headers
Expand Down Expand Up @@ -216,6 +224,23 @@ async fn fetch(req: Request) -> crate::Result<Response> {
}
}

if let Some(duration) = req.timeout() {
let abort_request_cb = Closure::wrap(Box::new(move || {
abort_controller.abort();
}) as Box<dyn Fn()>);

init.signal(Some(&abort_signal));

window
.set_timeout_with_callback_and_timeout_and_arguments_0(
abort_request_cb.as_ref().unchecked_ref(),
duration.as_millis() as i32,
)
.expect("timeout was set");

abort_request_cb.forget();
}

let js_req = web_sys::Request::new_with_str_and_init(req.url().as_str(), &init)
.map_err(crate::error::wasm)
.map_err(crate::error::builder)?;
Expand Down
29 changes: 29 additions & 0 deletions reqwest-wrap/src/wasm/request.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::convert::TryFrom;
use std::fmt;
use std::time::Duration;

use bytes::Bytes;
use http::{request::Parts, Method, Request as HttpRequest};
Expand All @@ -20,6 +21,7 @@ pub struct Request {
body: Option<Body>,
pub(super) cors: bool,
pub(super) credentials: Option<RequestCredentials>,
timeout: Option<Duration>,
}

/// A builder to construct the properties of a `Request`.
Expand All @@ -39,6 +41,7 @@ impl Request {
body: None,
cors: true,
credentials: None,
timeout: None,
}
}

Expand Down Expand Up @@ -90,6 +93,18 @@ impl Request {
&mut self.body
}

/// Get the timeout.
#[inline]
pub fn timeout(&self) -> Option<&Duration> {
self.timeout.as_ref()
}

/// Get a mutable reference to the timeout.
#[inline]
pub fn timeout_mut(&mut self) -> &mut Option<Duration> {
&mut self.timeout
}

/// Attempts to clone the `Request`.
///
/// None is returned if a body is which can not be cloned.
Expand All @@ -106,6 +121,7 @@ impl Request {
body,
cors: self.cors,
credentials: self.credentials,
timeout: self.timeout,
})
}
}
Expand Down Expand Up @@ -215,6 +231,18 @@ impl RequestBuilder {
self.header(crate::header::AUTHORIZATION, header_value)
}

/// Enables a request timeout.
///
/// The timeout is applied from when the request starts connecting until the
/// response body has finished. It affects only this request and overrides
/// the timeout configured using `ClientBuilder::timeout()`.
pub fn timeout(mut self, timeout: Duration) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
*req.timeout_mut() = Some(timeout);
}
self
}

/// Set the request body.
pub fn body<T: Into<Body>>(mut self, body: T) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
Expand Down Expand Up @@ -441,6 +469,7 @@ where
body: Some(body.into()),
cors: true,
credentials: None,
timeout: None,
})
}
}
Expand Down
41 changes: 41 additions & 0 deletions reqwest-wrap/tests/wasm_simple.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#![cfg(target_arch = "wasm32")]

use reqwest_wrap as reqwest;
use std::time::Duration;
use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*;
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);

#[wasm_bindgen]
extern "C" {
// Use `js_namespace` here to bind `console.log(..)` instead of just
// `log(..)`
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);

}
#[wasm_bindgen_test]
async fn simple_example() {
let res = reqwest::get("https://hyper.rs")
.await
.expect("http get example");

log(&format!("Status: {}", res.status()));

let body = res.text().await.expect("response to utf-8 text");
log(&format!("Body:\n\n{}", body));
}

#[wasm_bindgen_test]
async fn client_with_timeout() {
let client = reqwest::Client::new();
let err = client
.get("https://hyper.rs")
.timeout(Duration::from_millis(10))
.send()
.await
.expect_err("Expected error from aborted request");

assert!(err.is_request());
assert!(format!("{:?}", err).contains("The operation was aborted."));
}

0 comments on commit f0027c6

Please sign in to comment.