add ClientBuilder.default_headers() for wasm32 target (#1084)

This commit is contained in:
stevelr
2020-11-16 21:09:47 +00:00
committed by GitHub
parent 2dec3b725f
commit 4fe07d81cf
12 changed files with 210 additions and 16 deletions

View File

@@ -132,6 +132,7 @@ winreg = "0.7"
js-sys = "0.3.45" js-sys = "0.3.45"
wasm-bindgen = { version = "0.2.68", features = ["serde-serialize"] } wasm-bindgen = { version = "0.2.68", features = ["serde-serialize"] }
wasm-bindgen-futures = "0.4.18" wasm-bindgen-futures = "0.4.18"
wasm-bindgen-test = "0.3"
[target.'cfg(target_arch = "wasm32")'.dependencies.web-sys] [target.'cfg(target_arch = "wasm32")'.dependencies.web-sys]
version = "0.3.25" version = "0.3.25"
@@ -168,6 +169,14 @@ name = "tor_socks"
path = "examples/tor_socks.rs" path = "examples/tor_socks.rs"
required-features = ["socks"] required-features = ["socks"]
[[example]]
name = "form"
path = "examples/form.rs"
[[example]]
name = "simple"
path = "examples/simple.rs"
[[test]] [[test]]
name = "blocking" name = "blocking"
path = "tests/blocking.rs" path = "tests/blocking.rs"

View File

@@ -1,9 +1,20 @@
// Short example of a POST request with form data.
//
#[cfg(not(target_arch = "wasm32"))]
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
reqwest::Client::new() let response = reqwest::Client::new()
.post("http://www.baidu.com") .post("http://www.baidu.com")
.form(&[("one", "1")]) .form(&[("one", "1")])
.send() .send()
.await .await
.unwrap(); .expect("send");
println!("Response status {}", response.status());
} }
// The [cfg(not(target_arch = "wasm32"))] above prevent building the tokio::main function
// for wasm32 target, because tokio isn't compatible with wasm32.
// If you aren't building for wasm32, you don't need that line.
// The two lines below avoid the "'main' function not found" error when building for wasm32 target.
#[cfg(target_arch = "wasm32")]
fn main() {}

View File

@@ -3,6 +3,7 @@
// This is using the `tokio` runtime. You'll need the following dependency: // This is using the `tokio` runtime. You'll need the following dependency:
// //
// `tokio = { version = "0.2", features = ["macros"] }` // `tokio = { version = "0.2", features = ["macros"] }`
#[cfg(not(target_arch = "wasm32"))]
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), reqwest::Error> { async fn main() -> Result<(), reqwest::Error> {
let res = reqwest::get("https://hyper.rs").await?; let res = reqwest::get("https://hyper.rs").await?;
@@ -15,3 +16,10 @@ async fn main() -> Result<(), reqwest::Error> {
Ok(()) Ok(())
} }
// The [cfg(not(target_arch = "wasm32"))] above prevent building the tokio::main function
// for wasm32 target, because tokio isn't compatible with wasm32.
// If you aren't building for wasm32, you don't need that line.
// The two lines below avoid the "'main' function not found" error when building for wasm32 target.
#[cfg(target_arch = "wasm32")]
fn main() {}

View File

@@ -1,8 +1,8 @@
use http::Method; use http::{HeaderMap, Method};
use std::future::Future;
use wasm_bindgen::prelude::{wasm_bindgen, UnwrapThrowExt as _};
use js_sys::Promise; use js_sys::Promise;
use std::{fmt, future::Future, sync::Arc};
use url::Url; use url::Url;
use wasm_bindgen::prelude::{wasm_bindgen, UnwrapThrowExt as _};
use super::{Request, RequestBuilder, Response}; use super::{Request, RequestBuilder, Response};
use crate::IntoUrl; use crate::IntoUrl;
@@ -30,12 +30,15 @@ fn js_fetch(req: &web_sys::Request) -> Promise {
} }
/// dox /// dox
#[derive(Clone, Debug)] #[derive(Clone)]
pub struct Client(()); pub struct Client {
config: Arc<Config>,
}
/// dox /// dox
#[derive(Debug)] pub struct ClientBuilder {
pub struct ClientBuilder(()); config: Config,
}
impl Client { impl Client {
/// dox /// dox
@@ -134,10 +137,24 @@ impl Client {
self.execute_request(request) self.execute_request(request)
} }
// merge request headers with Client default_headers, prior to external http fetch
fn merge_headers(&self, req: &mut Request) {
use http::header::Entry;
let headers: &mut HeaderMap = req.headers_mut();
// insert default headers in the request headers
// without overwriting already appended headers.
for (key, value) in self.config.headers.iter() {
if let Entry::Vacant(entry) = headers.entry(key) {
entry.insert(value.clone());
}
}
}
pub(super) fn execute_request( pub(super) fn execute_request(
&self, &self,
req: Request, mut req: Request,
) -> impl Future<Output = crate::Result<Response>> { ) -> impl Future<Output = crate::Result<Response>> {
self.merge_headers(&mut req);
fetch(req) fetch(req)
} }
} }
@@ -148,11 +165,28 @@ impl Default for Client {
} }
} }
impl fmt::Debug for Client {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut builder = f.debug_struct("Client");
self.config.fmt_fields(&mut builder);
builder.finish()
}
}
impl fmt::Debug for ClientBuilder {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut builder = f.debug_struct("ClientBuilder");
self.config.fmt_fields(&mut builder);
builder.finish()
}
}
async fn fetch(req: Request) -> crate::Result<Response> { async fn fetch(req: Request) -> crate::Result<Response> {
// Build the js Request // Build the js Request
let mut init = web_sys::RequestInit::new(); let mut init = web_sys::RequestInit::new();
init.method(req.method().as_str()); init.method(req.method().as_str());
// convert HeaderMap to Headers
let js_headers = web_sys::Headers::new() let js_headers = web_sys::Headers::new()
.map_err(crate::error::wasm) .map_err(crate::error::wasm)
.map_err(crate::error::builder)?; .map_err(crate::error::builder)?;
@@ -190,8 +224,7 @@ async fn fetch(req: Request) -> crate::Result<Response> {
.map_err(crate::error::request)?; .map_err(crate::error::request)?;
// Convert from the js Response // Convert from the js Response
let mut resp = http::Response::builder() let mut resp = http::Response::builder().status(js_resp.status());
.status(js_resp.status());
let url = Url::parse(&js_resp.url()).expect_throw("url parse"); let url = Url::parse(&js_resp.url()).expect_throw("url parse");
@@ -219,12 +252,25 @@ async fn fetch(req: Request) -> crate::Result<Response> {
impl ClientBuilder { impl ClientBuilder {
/// dox /// dox
pub fn new() -> Self { pub fn new() -> Self {
ClientBuilder(()) ClientBuilder {
config: Config::default(),
}
} }
/// dox /// Returns a 'Client' that uses this ClientBuilder configuration
pub fn build(self) -> Result<Client, crate::Error> { pub fn build(mut self) -> Result<Client, crate::Error> {
Ok(Client(())) let config = std::mem::take(&mut self.config);
Ok(Client {
config: Arc::new(config),
})
}
/// Sets the default headers for every request
pub fn default_headers(mut self, headers: HeaderMap) -> ClientBuilder {
for (key, value) in headers.iter() {
self.config.headers.insert(key, value.clone());
}
self
} }
} }
@@ -233,3 +279,91 @@ impl Default for ClientBuilder {
Self::new() Self::new()
} }
} }
#[derive(Clone, Debug)]
struct Config {
headers: HeaderMap,
}
impl Default for Config {
fn default() -> Config {
Config {
headers: HeaderMap::new(),
}
}
}
impl Config {
fn fmt_fields(&self, f: &mut fmt::DebugStruct<'_, '_>) {
f.field("default_headers", &self.headers);
}
}
use wasm_bindgen_test::*;
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
#[wasm_bindgen_test]
async fn default_headers() {
use crate::header::{HeaderMap, HeaderValue, CONTENT_TYPE};
let mut headers = HeaderMap::new();
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
headers.insert("x-custom", HeaderValue::from_static("flibbertigibbet"));
let client = crate::Client::builder()
.default_headers(headers)
.build()
.expect("client");
let mut req = client
.get("https://www.example.com")
.build()
.expect("request");
// merge headers as if client were about to issue fetch
client.merge_headers(&mut req);
let test_headers = req.headers();
assert!(test_headers.get(CONTENT_TYPE).is_some(), "content-type");
assert!(test_headers.get("x-custom").is_some(), "custom header");
assert!(test_headers.get("accept").is_none(), "no accept header");
}
#[wasm_bindgen_test]
async fn default_headers_clone() {
use crate::header::{HeaderMap, HeaderValue, CONTENT_TYPE};
let mut headers = HeaderMap::new();
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
headers.insert("x-custom", HeaderValue::from_static("flibbertigibbet"));
let client = crate::Client::builder()
.default_headers(headers)
.build()
.expect("client");
let mut req = client
.get("https://www.example.com")
.header(CONTENT_TYPE, "text/plain")
.build()
.expect("request");
client.merge_headers(&mut req);
let headers1 = req.headers();
// confirm that request headers override defaults
assert_eq!(
headers1.get(CONTENT_TYPE).unwrap(),
"text/plain",
"request headers override defaults"
);
// confirm that request headers don't change client defaults
let mut req2 = client
.get("https://www.example.com/x")
.build()
.expect("req 2");
client.merge_headers(&mut req2);
let headers2 = req2.headers();
assert_eq!(
headers2.get(CONTENT_TYPE).unwrap(),
"application/json",
"request headers don't change client defaults"
);
}

View File

@@ -1,3 +1,5 @@
#![cfg(not(target_arch = "wasm32"))]
#[cfg(feature = "__tls")] #[cfg(feature = "__tls")]
#[tokio::test] #[tokio::test]
async fn test_badssl_modern() { async fn test_badssl_modern() {

View File

@@ -1,3 +1,4 @@
#![cfg(not(target_arch = "wasm32"))]
mod support; mod support;
use futures_util::stream::StreamExt; use futures_util::stream::StreamExt;
use support::*; use support::*;

View File

@@ -1,3 +1,4 @@
#![cfg(not(target_arch = "wasm32"))]
mod support; mod support;
use futures_util::stream::StreamExt; use futures_util::stream::StreamExt;
use support::*; use support::*;

View File

@@ -1,3 +1,4 @@
#![cfg(not(target_arch = "wasm32"))]
mod support; mod support;
use support::*; use support::*;

View File

@@ -1,3 +1,4 @@
#![cfg(not(target_arch = "wasm32"))]
mod support; mod support;
use futures_util::stream::StreamExt; use futures_util::stream::StreamExt;
use support::*; use support::*;

View File

@@ -1,3 +1,4 @@
#![cfg(not(target_arch = "wasm32"))]
use std::convert::Infallible; use std::convert::Infallible;
use std::future::Future; use std::future::Future;
use std::net; use std::net;

View File

@@ -1,3 +1,4 @@
#![cfg(not(target_arch = "wasm32"))]
mod support; mod support;
use support::*; use support::*;

24
tests/wasm_simple.rs Normal file
View File

@@ -0,0 +1,24 @@
#![cfg(target_arch = "wasm32")]
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));
}