Skip to content

Commit

Permalink
fix(local-http): HttpClient removes Host from URI only for HTTP/1.x
Browse files Browse the repository at this point in the history
  • Loading branch information
zonyitoo committed Oct 24, 2024
1 parent 38855a3 commit fefdef2
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 30 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "shadowsocks-rust"
version = "1.21.1"
version = "1.21.2"
authors = ["Shadowsocks Contributors"]
description = "shadowsocks is a fast tunnel proxy that helps you bypass firewalls."
repository = "https://github.com/shadowsocks/shadowsocks-rust"
Expand Down Expand Up @@ -248,7 +248,7 @@ jemallocator = { version = "0.5", optional = true }
snmalloc-rs = { version = "0.3", optional = true }
rpmalloc = { version = "0.2", optional = true }

shadowsocks-service = { version = "1.21.1", path = "./crates/shadowsocks-service" }
shadowsocks-service = { version = "1.21.2", path = "./crates/shadowsocks-service" }

windows-service = { version = "0.7", optional = true }

Expand Down
2 changes: 1 addition & 1 deletion crates/shadowsocks-service/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "shadowsocks-service"
version = "1.21.1"
version = "1.21.2"
authors = ["Shadowsocks Contributors"]
description = "shadowsocks is a fast tunnel proxy that helps you bypass firewalls."
repository = "https://github.com/shadowsocks/shadowsocks-rust"
Expand Down
90 changes: 65 additions & 25 deletions crates/shadowsocks-service/src/local/http/http_client.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! HTTP Client

use std::{
borrow::Cow,
collections::VecDeque,
fmt::Debug,
future::Future,
Expand All @@ -11,7 +12,8 @@ use std::{
time::{Duration, Instant},
};

use http::Uri;
use bson::doc;
use http::{header::InvalidHeaderValue, HeaderValue, Method as HttpMethod, Uri, Version as HttpVersion};
use hyper::{
body::{self, Body},
client::conn::{http1, http2},
Expand Down Expand Up @@ -47,6 +49,9 @@ pub enum HttpClientError {
/// Errors from http
#[error("{0}")]
Http(#[from] http::Error),
/// Errors from http header
#[error("{0}")]
InvalidHeaderValue(#[from] InvalidHeaderValue),
}

#[derive(Clone, Debug)]
Expand Down Expand Up @@ -137,20 +142,32 @@ where
pub async fn send_request(
&self,
context: Arc<ServiceContext>,
req: Request<B>,
mut req: Request<B>,
balancer: Option<&PingBalancer>,
) -> Result<Response<body::Incoming>, HttpClientError> {
let host = match host_addr(req.uri()) {
Some(h) => h,
None => panic!("URI missing host: {}", req.uri()),
};

// Set Host header if it was missing in the Request
{
let headers = req.headers_mut();
if !headers.contains_key("Host") {
let host_value = match host {
Address::DomainNameAddress(ref domain, _) => HeaderValue::from_str(domain)?,
Address::SocketAddress(ref saddr) => HeaderValue::from_str(saddr.ip().to_string().as_str())?,
};
headers.insert("Host", host_value);
}
}

// 1. Check if there is an available client
//
// FIXME: If the cached connection is closed unexpectedly, this request will fail immediately.
if let Some(c) = self.get_cached_connection(&host).await {
trace!("HTTP client for host: {} taken from cache", host);
return self.send_request_conn(host, c, req).await
return self.send_request_conn(host, c, req).await;
}

// 2. If no. Make a new connection
Expand All @@ -159,13 +176,12 @@ where
None => &Scheme::HTTP,
};

let domain = req
.uri()
.host()
.unwrap()
.trim_start_matches('[')
.trim_start_matches(']');
let c = match HttpConnection::connect(context.clone(), scheme, host.clone(), domain, balancer).await {
let domain = match host {
Address::DomainNameAddress(ref domain, _) => Cow::Borrowed(domain.as_str()),
Address::SocketAddress(ref saddr) => Cow::Owned(saddr.ip().to_string()),
};

let c = match HttpConnection::connect(context.clone(), scheme, host.clone(), &domain, balancer).await {
Ok(c) => c,
Err(err) => {
error!("failed to connect to host: {}, error: {}", host, err);
Expand Down Expand Up @@ -196,19 +212,8 @@ where
&self,
host: Address,
mut c: HttpConnection<B>,
mut req: Request<B>,
req: Request<B>,
) -> Result<Response<body::Incoming>, HttpClientError> {
// Remove Scheme, Host part from URI
if req.uri().scheme().is_some() || req.uri().authority().is_some() {
let mut builder = Uri::builder();
if let Some(path_and_query) = req.uri().path_and_query() {
builder = builder.path_and_query(path_and_query.as_str());
} else {
builder = builder.path_and_query("/");
}
*(req.uri_mut()) = builder.build()?;
}

trace!("HTTP making request to host: {}, request: {:?}", host, req);
let response = c.send_request(req).await?;
trace!("HTTP received response from host: {}, response: {:?}", host, response);
Expand Down Expand Up @@ -351,10 +356,45 @@ where
}

#[inline]
pub async fn send_request(&mut self, req: Request<B>) -> hyper::Result<Response<body::Incoming>> {
pub async fn send_request(&mut self, mut req: Request<B>) -> Result<Response<body::Incoming>, HttpClientError> {
match self {
HttpConnection::Http1(r) => r.send_request(req).await,
HttpConnection::Http2(r) => r.send_request(req).await,
HttpConnection::Http1(r) => {
if !matches!(
req.version(),
HttpVersion::HTTP_09 | HttpVersion::HTTP_10 | HttpVersion::HTTP_11
) {
trace!(
"HTTP client changed Request.version to HTTP/1.1 from {:?}",
req.version()
);

*req.version_mut() = HttpVersion::HTTP_11;
}

// Remove Scheme, Host part from URI
if req.method() != HttpMethod::CONNECT
&& (req.uri().scheme().is_some() || req.uri().authority().is_some())
{
let mut builder = Uri::builder();
if let Some(path_and_query) = req.uri().path_and_query() {
builder = builder.path_and_query(path_and_query.as_str());
} else {
builder = builder.path_and_query("/");
}
*(req.uri_mut()) = builder.build()?;
}

r.send_request(req).await.map_err(Into::into)
}
HttpConnection::Http2(r) => {
if !matches!(req.version(), HttpVersion::HTTP_2) {
trace!("HTTP client changed Request.version to HTTP/2 from {:?}", req.version());

*req.version_mut() = HttpVersion::HTTP_2;
}

r.send_request(req).await.map_err(Into::into)
}
}
}

Expand Down
4 changes: 4 additions & 0 deletions crates/shadowsocks-service/src/local/http/http_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ impl HttpService {
error!("failed to make request to host: {}, error: {}", host, err);
return make_bad_request();
}
Err(HttpClientError::InvalidHeaderValue(err)) => {
error!("failed to make request to host: {}, error: {}", host, err);
return make_bad_request();
}
};

trace!("received {} <- {} {:?}", self.peer_addr, host, res);
Expand Down

0 comments on commit fefdef2

Please sign in to comment.