Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WASM cookies #2360

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ sync_wrapper = { version = "1.0", features = ["futures"] }

# Optional deps...

## cookies
cookie_crate = { version = "0.18.0", package = "cookie", optional = true }
cookie_store = { version = "0.21.0", optional = true }
## json
serde_json = { version = "1.0", optional = true }
## multipart
Expand Down Expand Up @@ -145,10 +148,6 @@ tokio-rustls = { version = "0.26", optional = true, default-features = false, fe
webpki-roots = { version = "0.26.0", optional = true }
rustls-native-certs = { version = "0.7", optional = true }

## cookies
cookie_crate = { version = "0.18.0", package = "cookie", optional = true }
cookie_store = { version = "0.21.0", optional = true }

## compression
async-compression = { version = "0.4.0", default-features = false, features = ["tokio"], optional = true }
tokio-util = { version = "0.7.9", default-features = false, features = ["codec", "io"], optional = true }
Expand Down Expand Up @@ -189,6 +188,7 @@ system-configuration = { version = "0.6.0", optional = true }
# wasm

[target.'cfg(target_arch = "wasm32")'.dependencies]
hyper = { version = "1.1.0", default-features = false }
js-sys = "0.3.45"
serde_json = "1.0"
wasm-bindgen = "0.2.68"
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,8 @@ if_hyper! {
if_wasm! {
mod wasm;
mod util;
#[cfg(feature = "cookies")]
pub mod cookie;

pub use self::wasm::{Body, Client, ClientBuilder, Request, RequestBuilder, Response};
#[cfg(feature = "multipart")]
Expand Down
91 changes: 86 additions & 5 deletions src/wasm/client.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#[cfg(feature = "cookies")]
use crate::cookie;
use http::header::USER_AGENT;
use http::{HeaderMap, HeaderValue, Method};
use js_sys::{Promise, JSON};
Expand Down Expand Up @@ -156,7 +158,20 @@ impl Client {
mut req: Request,
) -> impl Future<Output = crate::Result<Response>> {
self.merge_headers(&mut req);
fetch(req)

// TODO Onè: We do not insert during polling like in non-wasm code. Test if this is covered by fetch.
// Add cookies from the cookie store.
#[cfg(feature = "cookies")]
{
if let Some(cookie_store) = self.config.cookie_store.as_ref() {
if req.headers().get(crate::header::COOKIE).is_none() {
let url = req.url().clone(); // TODO Onè: Revisit and determine if clone is needed
add_cookie_header(req.headers_mut(), &**cookie_store, &url);
}
}
}

fetch(req, self.clone())
}
}

Expand Down Expand Up @@ -186,7 +201,8 @@ impl fmt::Debug for ClientBuilder {
// > `init.method(m)` to `init.set_method(m)`
// For now, ignore their deprecation.
#[allow(deprecated)]
async fn fetch(req: Request) -> crate::Result<Response> {
#[cfg_attr(not(feature = "cookies"), allow(unused_variables))] // client only used if cookies are enabled
async fn fetch(req: Request, client: Client) -> crate::Result<Response> {
// Build the js Request
let mut init = web_sys::RequestInit::new();
init.method(req.method().as_str());
Expand Down Expand Up @@ -255,9 +271,25 @@ async fn fetch(req: Request) -> crate::Result<Response> {
resp = resp.header(&name, &value);
}

resp.body(js_resp)
let resp = resp
.body(js_resp)
.map(|resp| Response::new(resp, url, abort))
.map_err(crate::error::request)
.map_err(crate::error::request);

#[cfg(feature = "cookies")]
{
if let Some(ref cookie_store) = client.config.cookie_store {
if let Ok(resp) = resp.as_ref() {
let mut cookies =
cookie::extract_response_cookie_headers(&resp.headers()).peekable();
if cookies.peek().is_some() {
cookie_store.set_cookies(&mut cookies, resp.url());
}
}
}
}

resp
}

// ===== impl ClientBuilder =====
Expand Down Expand Up @@ -306,6 +338,29 @@ impl ClientBuilder {
}
self
}

/// dox
#[cfg(feature = "cookies")]
#[cfg_attr(docsrs, doc(cfg(feature = "cookies")))]
pub fn cookie_store(mut self, enable: bool) -> ClientBuilder {
if enable {
self.cookie_provider(Arc::new(cookie::Jar::default()))
} else {
self.config.cookie_store = None;
self
}
}

/// dox
#[cfg(feature = "cookies")]
#[cfg_attr(docsrs, doc(cfg(feature = "cookies")))]
pub fn cookie_provider<C: cookie::CookieStore + 'static>(
mut self,
cookie_store: Arc<C>,
) -> ClientBuilder {
self.config.cookie_store = Some(cookie_store as _);
self
}
}

impl Default for ClientBuilder {
Expand All @@ -314,27 +369,53 @@ impl Default for ClientBuilder {
}
}

#[derive(Debug)]
struct Config {
// NOTE: When adding a new field, update `fmt_fields` in impl Config
headers: HeaderMap,
#[cfg(feature = "cookies")]
cookie_store: Option<Arc<dyn cookie::CookieStore>>,
error: Option<crate::Error>,
}

impl fmt::Debug for Config {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut builder = f.debug_struct("Config");
self.fmt_fields(&mut builder);
builder.finish()
}
}

impl Default for Config {
fn default() -> Config {
Config {
headers: HeaderMap::new(),
#[cfg(feature = "cookies")]
cookie_store: None,
error: None,
}
}
}

impl Config {
fn fmt_fields(&self, f: &mut fmt::DebugStruct<'_, '_>) {
#[cfg(feature = "cookies")]
{
if self.cookie_store.is_some() {
f.field("cookie_store", &true);
}
}

f.field("default_headers", &self.headers);
}
}

#[cfg(feature = "cookies")]
fn add_cookie_header(headers: &mut HeaderMap, cookie_store: &dyn cookie::CookieStore, url: &Url) {
if let Some(header) = cookie_store.cookies(url) {
headers.insert(crate::header::COOKIE, header);
}
}

#[cfg(test)]
mod tests {
use wasm_bindgen_test::*;
Expand Down
15 changes: 15 additions & 0 deletions src/wasm/response.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::fmt;

#[cfg(feature = "cookies")]
use crate::cookie;
use bytes::Bytes;
use http::{HeaderMap, StatusCode};
use js_sys::Uint8Array;
Expand Down Expand Up @@ -72,6 +74,19 @@ impl Response {
.ok()
}

/// Retrieve the cookies contained in the response.
///
/// Note that invalid 'Set-Cookie' headers will be ignored.
///
/// # Optional
///
/// This requires the optional `cookies` feature to be enabled.
#[cfg(feature = "cookies")]
#[cfg_attr(docsrs, doc(cfg(feature = "cookies")))]
pub fn cookies<'a>(&'a self) -> impl Iterator<Item = cookie::Cookie<'a>> + 'a {
cookie::extract_response_cookies(self.http.headers()).filter_map(Result::ok)
}

/// Get the final `Url` of this `Response`.
#[inline]
pub fn url(&self) -> &Url {
Expand Down