Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: seanmonstar/reqwest
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.12.14
Choose a base ref
...
head repository: seanmonstar/reqwest
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v0.12.15
Choose a head ref
  • 5 commits
  • 5 files changed
  • 3 contributors

Commits on Mar 16, 2025

  1. Support streaming response body in HTTP/3 (#2517)

    Closes #2603
    ducaale authored Mar 16, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    e9215fd View commit details

Commits on Mar 18, 2025

  1. Re-enable NO_PROXY envs on Windows (#2601)

    In #2559, the behavior of no proxy handling on windows was changed to
    effectively only read from the current user's `Internet Settings` in the
    registry on Windows. This meant that NO_PROXY env vars were no longer
    respected on Windows. This commit changes that behavior and at least
    brings it in line with dotnet and nuget behavior (two microsoft
    projects) of first checking env vars and only if they are not set, check
    the registry settings.
    
    This fix changes the behavior of `NoProxy::from_env` if `NO_PROXY` or
    `no_proxy` is set to an empty string. Previously, it would have returned
    `None`, which is not what the documentation says should happen. It now
    returns an `Some(NoProxy::default())` which should functionally be the
    same thing for the purposes of not matching anything with the proxy.
    
    This change was done due to potential interaction with previous commit
    fixing #2599. I would expect that setting `NO_PROXY` to an empty string
    would also prevent reqwest from reading the registry settings.
    
    Closes #2599
    drewkett authored Mar 18, 2025
    Copy the full SHA
    5fd3d5b View commit details
  2. Merge tag 'v0.12.14'

    seanmonstar committed Mar 18, 2025
    Copy the full SHA
    96a4fea View commit details
  3. upgrade h3-quinn (#2605)

    seanmonstar authored Mar 18, 2025
    Copy the full SHA
    e4bb3e6 View commit details
  4. v0.12.15

    seanmonstar committed Mar 18, 2025
    Copy the full SHA
    54376c3 View commit details
Showing with 93 additions and 29 deletions.
  1. +6 −0 CHANGELOG.md
  2. +8 −3 Cargo.toml
  3. +6 −13 examples/h3_simple.rs
  4. +49 −9 src/async_impl/h3_client/pool.rs
  5. +24 −4 src/proxy.rs
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## v0.12.15

- Fix Windows to support both `ProxyOverride` and `NO_PROXY`.
- Fix http3 to support streaming response bodies.
- Fix http3 dependency from public API misuse.

## v0.12.14

- Fix missing `fetch_mode_no_cors()`, marking as deprecated when not on WASM.
11 changes: 8 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "reqwest"
version = "0.12.14"
version = "0.12.15"
description = "higher level HTTP client library"
keywords = ["http", "request", "client"]
categories = ["web-programming::http-client", "wasm"]
@@ -161,8 +161,8 @@ tokio-socks = { version = "0.5.2", optional = true }
hickory-resolver = { version = "0.24", optional = true, features = ["tokio-runtime"] }

# HTTP/3 experimental support
h3 = { version = "0.0.6", optional = true }
h3-quinn = { version = "0.0.7", optional = true }
h3 = { version = "0.0.7", optional = true }
h3-quinn = { version = "0.0.9", optional = true }
quinn = { version = "0.11.1", default-features = false, features = ["rustls", "runtime-tokio"], optional = true }
slab = { version = "0.4.9", optional = true } # just to get minimal versions working with quinn
futures-channel = { version = "0.3", optional = true }
@@ -255,6 +255,11 @@ path = "examples/form.rs"
name = "simple"
path = "examples/simple.rs"

[[example]]
name = "h3_simple"
path = "examples/h3_simple.rs"
required-features = ["http3", "rustls-tls"]

[[example]]
name = "connect_via_lower_priority_tokio_runtime"
path = "examples/connect_via_lower_priority_tokio_runtime.rs"
19 changes: 6 additions & 13 deletions examples/h3_simple.rs
Original file line number Diff line number Diff line change
@@ -7,18 +7,7 @@
#[cfg(not(target_arch = "wasm32"))]
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
use http::Version;
use reqwest::{Client, IntoUrl, Response};

async fn get<T: IntoUrl + Clone>(url: T) -> reqwest::Result<Response> {
Client::builder()
.http3_prior_knowledge()
.build()?
.get(url)
.version(Version::HTTP_3)
.send()
.await
}
let client = reqwest::Client::builder().http3_prior_knowledge().build()?;

// Some simple CLI args requirements...
let url = match std::env::args().nth(1) {
@@ -31,7 +20,11 @@ async fn main() -> Result<(), reqwest::Error> {

eprintln!("Fetching {url:?}...");

let res = get(url).await?;
let res = client
.get(url)
.version(http::Version::HTTP_3)
.send()
.await?;

eprintln!("Response: {:?} {}", res.version(), res.status());
eprintln!("Headers: {:#?}\n", res.headers());
58 changes: 49 additions & 9 deletions src/async_impl/h3_client/pool.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use bytes::Bytes;
use std::collections::HashMap;
use std::future;
use std::pin::Pin;
use std::sync::mpsc::{Receiver, TryRecvError};
use std::sync::{Arc, Mutex};
use std::task::{Context, Poll};
use std::time::Duration;
use tokio::sync::watch;
use tokio::time::Instant;
@@ -206,7 +208,6 @@ impl PoolClient {
&mut self,
req: Request<Body>,
) -> Result<Response<ResponseBody>, BoxError> {
use http_body_util::{BodyExt, Full};
use hyper::body::Body as _;

let (head, req_body) = req.into_parts();
@@ -232,14 +233,7 @@ impl PoolClient {

let resp = stream.recv_response().await?;

let mut resp_body = Vec::new();
while let Some(chunk) = stream.recv_data().await? {
resp_body.extend(chunk.chunk())
}

let resp_body = Full::new(resp_body.into())
.map_err(|never| match never {})
.boxed();
let resp_body = crate::async_impl::body::boxed(Incoming::new(stream, resp.headers()));

Ok(resp.map(|_| resp_body))
}
@@ -275,6 +269,52 @@ impl PoolConnection {
}
}

struct Incoming<S, B> {
inner: h3::client::RequestStream<S, B>,
content_length: Option<u64>,
}

impl<S, B> Incoming<S, B> {
fn new(stream: h3::client::RequestStream<S, B>, headers: &http::header::HeaderMap) -> Self {
Self {
inner: stream,
content_length: headers
.get(http::header::CONTENT_LENGTH)
.and_then(|h| h.to_str().ok())
.and_then(|v| v.parse().ok()),
}
}
}

impl<S, B> http_body::Body for Incoming<S, B>
where
S: h3::quic::RecvStream,
{
type Data = Bytes;
type Error = crate::error::Error;

fn poll_frame(
mut self: Pin<&mut Self>,
cx: &mut Context,
) -> Poll<Option<Result<hyper::body::Frame<Self::Data>, Self::Error>>> {
match futures_core::ready!(self.inner.poll_recv_data(cx)) {
Ok(Some(mut b)) => Poll::Ready(Some(Ok(hyper::body::Frame::data(
b.copy_to_bytes(b.remaining()),
)))),
Ok(None) => Poll::Ready(None),
Err(e) => Poll::Ready(Some(Err(crate::error::body(e)))),
}
}

fn size_hint(&self) -> hyper::body::SizeHint {
if let Some(content_length) = self.content_length {
hyper::body::SizeHint::with_exact(content_length)
} else {
hyper::body::SizeHint::default()
}
}
}

pub(crate) fn extract_domain(uri: &mut Uri) -> Result<Key, Error> {
let uri_clone = uri.clone();
match (uri_clone.scheme(), uri_clone.authority()) {
28 changes: 24 additions & 4 deletions src/proxy.rs
Original file line number Diff line number Diff line change
@@ -286,8 +286,13 @@ impl Proxy {

#[cfg(target_os = "windows")]
{
let win_exceptions: String = get_windows_proxy_exceptions();
proxy.no_proxy = NoProxy::from_string(&win_exceptions);
// Only read from windows registry proxy settings if not available from an enviroment
// variable. This is in line with the stated behavior of both dotnot and nuget on
// windows. <https://github.com/seanmonstar/reqwest/issues/2599>
if proxy.no_proxy.is_none() {
let win_exceptions: String = get_windows_proxy_exceptions();
proxy.no_proxy = NoProxy::from_string(&win_exceptions);
}
}

proxy
@@ -451,9 +456,12 @@ impl NoProxy {
pub fn from_env() -> Option<NoProxy> {
let raw = env::var("NO_PROXY")
.or_else(|_| env::var("no_proxy"))
.unwrap_or_default();
.ok()?;

Self::from_string(&raw)
// Per the docs, this returns `None` if no environment variable is set. We can only reach
// here if an env var is set, so we return `Some(NoProxy::default)` if `from_string`
// returns None, which occurs with an empty string.
Some(Self::from_string(&raw).unwrap_or_default())
}

/// Returns a new no-proxy configuration based on a `no_proxy` string (or `None` if no variables
@@ -1628,6 +1636,18 @@ mod tests {
// everything should go through proxy, "effectively" nothing is in no_proxy
assert_eq!(intercepted_uri(&p, "http://hyper.rs"), target);

// Also test the behavior of `NO_PROXY` being an empty string.
env::set_var("NO_PROXY", "");

// Manually construct this so we aren't use the cache
let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
p.no_proxy = NoProxy::from_env();
// In the case of an empty string `NoProxy::from_env()` should return `Some(..)`
assert!(p.no_proxy.is_some());

// everything should go through proxy, "effectively" nothing is in no_proxy
assert_eq!(intercepted_uri(&p, "http://hyper.rs"), target);

// reset user setting when guards drop
drop(_g1);
drop(_g2);