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

fix: Ensure HTTP version negotiation for non-TLS requests #397

Merged
merged 2 commits into from
Feb 5, 2025
Merged
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
2 changes: 1 addition & 1 deletion examples/request_with_version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ async fn main() -> Result<(), rquest::Error> {
.send()
.await?;

println!("{:?}", resp.version());
assert_eq!(resp.version(), Version::HTTP_11);
println!("{}", resp.text().await?);

Ok(())
Expand Down
26 changes: 12 additions & 14 deletions src/util/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,32 +177,32 @@ impl Dst {
.map_err(|_| e!(UserAbsoluteUriRequired))
}

/// Get the URI
#[inline(always)]
pub(crate) fn uri(&self) -> &Uri {
&self.inner.uri
}

/// Set the next destination of the request (for proxy)
#[inline(always)]
pub(crate) fn set_uri(&mut self, mut uri: Uri) {
let inner = Arc::make_mut(&mut self.inner);
std::mem::swap(&mut inner.uri, &mut uri);
}

/// Get the alpn protos
#[inline(always)]
pub(crate) fn alpn_protos(&self) -> Option<AlpnProtos> {
self.alpn_protos
}

/// Take network scheme for iface
#[inline(always)]
pub(crate) fn is_h2(&self) -> bool {
self.alpn_protos == Some(AlpnProtos::Http2)
}

#[inline(always)]
pub(crate) fn take_addresses(&mut self) -> (Option<Ipv4Addr>, Option<Ipv6Addr>) {
Arc::make_mut(&mut self.inner).network.take_addresses()
}

/// Take the network scheme for iface
#[inline(always)]
pub(crate) fn take_interface(&mut self) -> Option<std::borrow::Cow<'static, str>> {
cfg_bindable_device! {
Expand All @@ -212,7 +212,6 @@ impl Dst {
cfg_non_bindable_device!(None)
}

/// Take the network scheme for proxy
#[inline(always)]
pub(crate) fn take_proxy_scheme(&mut self) -> Option<ProxyScheme> {
Arc::make_mut(&mut self.inner).network.take_proxy_scheme()
Expand Down Expand Up @@ -325,7 +324,7 @@ where
/// # fn main() {}
/// ```
pub fn request(&self, req: InnerRequest<B>) -> ResponseFuture {
let (mut req, network_scheme, http_version_pref) = req.pieces();
let (mut req, network_scheme, alpn_protos) = req.pieces();
let is_http_connect = req.method() == Method::CONNECT;
match req.version() {
Version::HTTP_10 => {
Expand All @@ -339,12 +338,7 @@ where
other => return ResponseFuture::error_version(other),
};

let ctx = match Dst::new(
req.uri_mut(),
is_http_connect,
network_scheme,
http_version_pref,
) {
let ctx = match Dst::new(req.uri_mut(), is_http_connect, network_scheme, alpn_protos) {
Ok(s) => s,
Err(err) => {
return ResponseFuture::new(future::err(err));
Expand Down Expand Up @@ -619,7 +613,11 @@ where

let h1_builder = self.h1_builder.clone();
let h2_builder = self.h2_builder.clone();
let ver = self.config.ver;
let ver = if dst.is_h2() {
Ver::Http2
} else {
self.config.ver
};
let is_ver_h2 = ver == Ver::Http2;
let connector = self.connector.clone();
hyper_lazy(move || {
Expand Down
71 changes: 51 additions & 20 deletions tests/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ mod support;

use support::server;

use http::header::{CONTENT_LENGTH, CONTENT_TYPE, TRANSFER_ENCODING};
use http::{
header::{CONTENT_LENGTH, CONTENT_TYPE, TRANSFER_ENCODING},
Version,
};
#[cfg(feature = "json")]
use std::collections::HashMap;

Expand Down Expand Up @@ -322,25 +325,6 @@ async fn overridden_dns_resolution_with_hickory_dns_multiple() {
assert_eq!("Hello", text);
}

#[tokio::test]
#[ignore = "Needs TLS support in the test server"]
async fn http2_upgrade() {
let server = server::http(move |_| async move { http::Response::default() });

let url = format!("https://localhost:{}", server.addr().port());
let res = rquest::Client::builder()
.danger_accept_invalid_certs(true)
.build()
.expect("client builder")
.get(&url)
.send()
.await
.expect("request");

assert_eq!(res.status(), rquest::StatusCode::OK);
assert_eq!(res.version(), rquest::Version::HTTP_2);
}

#[test]
#[cfg(feature = "json")]
fn add_json_default_content_type_if_not_set_manually() {
Expand Down Expand Up @@ -526,3 +510,50 @@ async fn test_client_os_spoofing() {

assert_eq!(res.status(), rquest::StatusCode::OK);
}

#[tokio::test]
async fn default_http_version() {
let server = server::http(move |_| async move { http::Response::default() });

let resp = rquest::Client::builder()
.build()
.unwrap()
.get(format!("http://{}", server.addr()))
.send()
.await
.unwrap();

assert_eq!(resp.version(), rquest::Version::HTTP_11);
}

#[tokio::test]
async fn http1_version() {
let server = server::http(move |_| async move { http::Response::default() });

let resp = rquest::Client::builder()
.build()
.unwrap()
.get(format!("http://{}", server.addr()))
.version(Version::HTTP_11)
.send()
.await
.unwrap();

assert_eq!(resp.version(), rquest::Version::HTTP_11);
}

#[tokio::test]
async fn http2_version() {
let server = server::http(move |_| async move { http::Response::default() });

let resp = rquest::Client::builder()
.build()
.unwrap()
.get(format!("http://{}", server.addr()))
.version(Version::HTTP_2)
.send()
.await
.unwrap();

assert_eq!(resp.version(), rquest::Version::HTTP_2);
}
Loading