mirror of https://github.com/fafhrd91/actix-web
feat(awc): add HTTP proxy support via environment variables
Add support for HTTP_PROXY/HTTPS_PROXY/NO_PROXY environment variables to the awc HTTP client, matching the behavior of reqwest and other HTTP clients. Proxied requests use absolute-form URIs per RFC 7230 section 5.3.2. Changes: - actix-http: Add PROXY_REQUEST flag and absolute-form URI encoding - awc: Add ProxyConfig, ClientBuilder::proxy()/no_proxy() methods - awc: DefaultConnector routes connections through proxy when configured - Proxy env vars are read by default; use no_proxy() to disable
This commit is contained in:
parent
7ded7ee478
commit
c885158718
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
- Add `RequestHead::set_proxy_request()` / `is_proxy_request()` to support absolute-form URI encoding for HTTP proxy requests (RFC 7230 section 5.3.2).
|
||||||
- When configured, gracefully close HTTP/1 connections after early responses to unread request bodies. [#3967]
|
- When configured, gracefully close HTTP/1 connections after early responses to unread request bodies. [#3967]
|
||||||
- Wake HTTP/1 payload receivers with an incomplete-payload error when the sender is dropped before EOF. [#3100]
|
- Wake HTTP/1 payload receivers with an incomplete-payload error when the sender is dropped before EOF. [#3100]
|
||||||
- Update `foldhash` dependency to `0.2`.
|
- Update `foldhash` dependency to `0.2`.
|
||||||
|
|
|
||||||
|
|
@ -305,11 +305,23 @@ impl MessageType for RequestHeadType {
|
||||||
fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()> {
|
fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()> {
|
||||||
let head = self.as_ref();
|
let head = self.as_ref();
|
||||||
dst.reserve(256 + head.headers.len() * AVERAGE_HEADER_SIZE);
|
dst.reserve(256 + head.headers.len() * AVERAGE_HEADER_SIZE);
|
||||||
|
|
||||||
|
// RFC 7230 section 5.3.2: use absolute-form URI for proxy requests
|
||||||
|
let request_target = if head.is_proxy_request() {
|
||||||
|
head.uri.to_string()
|
||||||
|
} else {
|
||||||
|
head.uri
|
||||||
|
.path_and_query()
|
||||||
|
.map(|u| u.as_str())
|
||||||
|
.unwrap_or("/")
|
||||||
|
.to_owned()
|
||||||
|
};
|
||||||
|
|
||||||
write!(
|
write!(
|
||||||
helpers::MutWriter(dst),
|
helpers::MutWriter(dst),
|
||||||
"{} {} {}",
|
"{} {} {}",
|
||||||
head.method,
|
head.method,
|
||||||
head.uri.path_and_query().map(|u| u.as_str()).unwrap_or("/"),
|
request_target,
|
||||||
match head.version {
|
match head.version {
|
||||||
Version::HTTP_09 => "HTTP/0.9",
|
Version::HTTP_09 => "HTTP/0.9",
|
||||||
Version::HTTP_10 => "HTTP/1.0",
|
Version::HTTP_10 => "HTTP/1.0",
|
||||||
|
|
@ -664,6 +676,42 @@ mod tests {
|
||||||
assert!(data.contains("date: date\r\n"));
|
assert!(data.contains("date: date\r\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_proxy_request_absolute_uri() {
|
||||||
|
let mut bytes = BytesMut::with_capacity(2048);
|
||||||
|
let mut head = RequestHead::default();
|
||||||
|
head.uri = "http://example.com/foo?bar=1".parse().unwrap();
|
||||||
|
head.method = http::Method::GET;
|
||||||
|
head.set_proxy_request(true);
|
||||||
|
|
||||||
|
let mut head = RequestHeadType::Owned(head);
|
||||||
|
head.encode_status(&mut bytes).unwrap();
|
||||||
|
|
||||||
|
let data = String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
|
||||||
|
assert!(
|
||||||
|
data.starts_with("GET http://example.com/foo?bar=1 HTTP/1.1"),
|
||||||
|
"Expected absolute-form URI for proxy request, got: {data}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_non_proxy_request_origin_form() {
|
||||||
|
let mut bytes = BytesMut::with_capacity(2048);
|
||||||
|
let mut head = RequestHead::default();
|
||||||
|
head.uri = "http://example.com/foo?bar=1".parse().unwrap();
|
||||||
|
head.method = http::Method::GET;
|
||||||
|
// proxy flag is NOT set
|
||||||
|
|
||||||
|
let mut head = RequestHeadType::Owned(head);
|
||||||
|
head.encode_status(&mut bytes).unwrap();
|
||||||
|
|
||||||
|
let data = String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
|
||||||
|
assert!(
|
||||||
|
data.starts_with("GET /foo?bar=1 HTTP/1.1"),
|
||||||
|
"Expected origin-form URI for non-proxy request, got: {data}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_no_content_length() {
|
async fn test_no_content_length() {
|
||||||
let mut bytes = BytesMut::with_capacity(2048);
|
let mut bytes = BytesMut::with_capacity(2048);
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,8 @@ bitflags! {
|
||||||
const UPGRADE = 0b0000_0100;
|
const UPGRADE = 0b0000_0100;
|
||||||
const EXPECT = 0b0000_1000;
|
const EXPECT = 0b0000_1000;
|
||||||
const NO_CHUNKING = 0b0001_0000;
|
const NO_CHUNKING = 0b0001_0000;
|
||||||
const CAMEL_CASE = 0b0010_0000;
|
const CAMEL_CASE = 0b0010_0000;
|
||||||
|
const PROXY_REQUEST = 0b0100_0000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -144,6 +144,25 @@ impl RequestHead {
|
||||||
pub(crate) fn set_expect(&mut self) {
|
pub(crate) fn set_expect(&mut self) {
|
||||||
self.flags.insert(Flags::EXPECT);
|
self.flags.insert(Flags::EXPECT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns whether this request should use absolute-form URI (for proxy requests).
|
||||||
|
#[inline]
|
||||||
|
pub fn is_proxy_request(&self) -> bool {
|
||||||
|
self.flags.contains(Flags::PROXY_REQUEST)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets whether this request should use absolute-form URI in the request line.
|
||||||
|
///
|
||||||
|
/// When enabled, the full URI (including scheme and authority) is sent in the request line,
|
||||||
|
/// as required by HTTP/1.1 for requests to proxies (RFC 7230 section 5.3.2).
|
||||||
|
#[inline]
|
||||||
|
pub fn set_proxy_request(&mut self, val: bool) {
|
||||||
|
if val {
|
||||||
|
self.flags.insert(Flags::PROXY_REQUEST);
|
||||||
|
} else {
|
||||||
|
self.flags.remove(Flags::PROXY_REQUEST);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::large_enum_variant)]
|
#[allow(clippy::large_enum_variant)]
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
- Add HTTP proxy support via `HTTP_PROXY`/`HTTPS_PROXY`/`NO_PROXY` environment variables, read by default.
|
||||||
|
- Add `ProxyConfig` type and `ClientBuilder::proxy()` / `ClientBuilder::no_proxy()` methods for explicit proxy configuration.
|
||||||
- Add camel-case header controls to `WebsocketsRequest` via `camel_case_headers()` and `set_camel_case_headers()`. [#3953]
|
- Add camel-case header controls to `WebsocketsRequest` via `camel_case_headers()` and `set_camel_case_headers()`. [#3953]
|
||||||
- Update `hickory-resolver` dependency to `0.26.1`.
|
- Update `hickory-resolver` dependency to `0.26.1`.
|
||||||
- Update `rand` dependency to `0.10`.
|
- Update `rand` dependency to `0.10`.
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,8 @@ use base64::prelude::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
client::{
|
client::{
|
||||||
ClientConfig, ConnectInfo, Connector, ConnectorService, TcpConnectError, TcpConnection,
|
proxy::ProxyConfig, ClientConfig, ConnectInfo, Connector, ConnectorService,
|
||||||
|
TcpConnectError, TcpConnection,
|
||||||
},
|
},
|
||||||
connect::DefaultConnector,
|
connect::DefaultConnector,
|
||||||
error::SendRequestError,
|
error::SendRequestError,
|
||||||
|
|
@ -34,6 +35,7 @@ pub struct ClientBuilder<S = (), M = ()> {
|
||||||
middleware: M,
|
middleware: M,
|
||||||
local_address: Option<IpAddr>,
|
local_address: Option<IpAddr>,
|
||||||
max_redirects: u8,
|
max_redirects: u8,
|
||||||
|
proxy: ProxyConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientBuilder {
|
impl ClientBuilder {
|
||||||
|
|
@ -63,6 +65,7 @@ impl ClientBuilder {
|
||||||
middleware: (),
|
middleware: (),
|
||||||
local_address: None,
|
local_address: None,
|
||||||
max_redirects: 10,
|
max_redirects: 10,
|
||||||
|
proxy: ProxyConfig::from_env(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -93,6 +96,7 @@ where
|
||||||
stream_window_size: self.stream_window_size,
|
stream_window_size: self.stream_window_size,
|
||||||
conn_window_size: self.conn_window_size,
|
conn_window_size: self.conn_window_size,
|
||||||
max_redirects: self.max_redirects,
|
max_redirects: self.max_redirects,
|
||||||
|
proxy: self.proxy,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -159,6 +163,22 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set proxy configuration.
|
||||||
|
///
|
||||||
|
/// By default, proxy settings are read from environment variables
|
||||||
|
/// (`HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`). Use this method to override with
|
||||||
|
/// an explicit configuration.
|
||||||
|
pub fn proxy(mut self, proxy: ProxyConfig) -> Self {
|
||||||
|
self.proxy = proxy;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disable proxy usage, ignoring environment variables.
|
||||||
|
pub fn no_proxy(mut self) -> Self {
|
||||||
|
self.proxy = ProxyConfig::default();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Do not add fundamental default request headers.
|
/// Do not add fundamental default request headers.
|
||||||
///
|
///
|
||||||
/// By default `Date` and `User-Agent` headers are set.
|
/// By default `Date` and `User-Agent` headers are set.
|
||||||
|
|
@ -245,6 +265,7 @@ where
|
||||||
connector: self.connector,
|
connector: self.connector,
|
||||||
local_address: self.local_address,
|
local_address: self.local_address,
|
||||||
max_redirects: self.max_redirects,
|
max_redirects: self.max_redirects,
|
||||||
|
proxy: self.proxy,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -284,7 +305,7 @@ where
|
||||||
connector = connector.local_address(val);
|
connector = connector.local_address(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
let connector = DefaultConnector::new(connector.finish());
|
let connector = DefaultConnector::new(connector.finish()).with_proxy(self.proxy);
|
||||||
let connector = boxed::rc_service(self.middleware.new_transform(connector));
|
let connector = boxed::rc_service(self.middleware.new_transform(connector));
|
||||||
|
|
||||||
Client(ClientConfig {
|
Client(ClientConfig {
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,13 @@ mod error;
|
||||||
mod h1proto;
|
mod h1proto;
|
||||||
mod h2proto;
|
mod h2proto;
|
||||||
mod pool;
|
mod pool;
|
||||||
|
pub(crate) mod proxy;
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
connection::{Connection, ConnectionIo},
|
connection::{Connection, ConnectionIo},
|
||||||
connector::{Connector, ConnectorService},
|
connector::{Connector, ConnectorService},
|
||||||
error::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError},
|
error::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError},
|
||||||
|
proxy::ProxyConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,223 @@
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
use http::Uri;
|
||||||
|
|
||||||
|
/// HTTP proxy configuration for the client connector.
|
||||||
|
///
|
||||||
|
/// Determines which proxy (if any) should be used for a given request URI,
|
||||||
|
/// based on explicit configuration or environment variables (`HTTP_PROXY`,
|
||||||
|
/// `HTTPS_PROXY`, `NO_PROXY`).
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ProxyConfig {
|
||||||
|
http_proxy: Option<Uri>,
|
||||||
|
https_proxy: Option<Uri>,
|
||||||
|
no_proxy: Option<NoProxy>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProxyConfig {
|
||||||
|
/// Creates a proxy configuration by reading standard environment variables.
|
||||||
|
///
|
||||||
|
/// Reads `HTTP_PROXY` (or `http_proxy`), `HTTPS_PROXY` (or `https_proxy`),
|
||||||
|
/// and `NO_PROXY` (or `no_proxy`).
|
||||||
|
pub fn from_env() -> Self {
|
||||||
|
let http_proxy = env_var_uri("HTTP_PROXY").or_else(|| env_var_uri("http_proxy"));
|
||||||
|
let https_proxy = env_var_uri("HTTPS_PROXY").or_else(|| env_var_uri("https_proxy"));
|
||||||
|
let no_proxy = env::var("NO_PROXY")
|
||||||
|
.or_else(|_| env::var("no_proxy"))
|
||||||
|
.ok()
|
||||||
|
.map(|s| NoProxy::parse(&s));
|
||||||
|
|
||||||
|
Self {
|
||||||
|
http_proxy,
|
||||||
|
https_proxy,
|
||||||
|
no_proxy,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a proxy configuration with an explicit HTTP proxy URI.
|
||||||
|
pub fn with_http_proxy(mut self, proxy: Uri) -> Self {
|
||||||
|
self.http_proxy = Some(proxy);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a proxy configuration with an explicit HTTPS proxy URI.
|
||||||
|
pub fn with_https_proxy(mut self, proxy: Uri) -> Self {
|
||||||
|
self.https_proxy = Some(proxy);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the no-proxy list (comma-separated hostnames/domains).
|
||||||
|
pub fn with_no_proxy(mut self, no_proxy: &str) -> Self {
|
||||||
|
self.no_proxy = Some(NoProxy::parse(no_proxy));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the proxy URI to use for the given target URI, or `None` if no proxy applies.
|
||||||
|
pub fn proxy_for_uri(&self, uri: &Uri) -> Option<&Uri> {
|
||||||
|
if let Some(ref no_proxy) = self.no_proxy {
|
||||||
|
if let Some(host) = uri.host() {
|
||||||
|
if no_proxy.matches(host) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match uri.scheme_str() {
|
||||||
|
Some("https") | Some("wss") => self.https_proxy.as_ref(),
|
||||||
|
_ => self.http_proxy.as_ref(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if any proxy is configured.
|
||||||
|
pub fn has_proxy(&self) -> bool {
|
||||||
|
self.http_proxy.is_some() || self.https_proxy.is_some()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ProxyConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
http_proxy: None,
|
||||||
|
https_proxy: None,
|
||||||
|
no_proxy: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn env_var_uri(name: &str) -> Option<Uri> {
|
||||||
|
env::var(name)
|
||||||
|
.ok()
|
||||||
|
.and_then(|val| {
|
||||||
|
if val.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
match val.parse::<Uri>() {
|
||||||
|
Ok(uri) => Some(uri),
|
||||||
|
Err(err) => {
|
||||||
|
log::warn!("Invalid proxy URI in {name}: {err}");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parsed representation of the `NO_PROXY` environment variable.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct NoProxy {
|
||||||
|
entries: Vec<String>,
|
||||||
|
match_all: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NoProxy {
|
||||||
|
fn parse(value: &str) -> Self {
|
||||||
|
let trimmed = value.trim();
|
||||||
|
if trimmed == "*" {
|
||||||
|
return Self {
|
||||||
|
entries: Vec::new(),
|
||||||
|
match_all: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let entries = trimmed
|
||||||
|
.split(',')
|
||||||
|
.map(|s| s.trim().to_lowercase())
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
entries,
|
||||||
|
match_all: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn matches(&self, host: &str) -> bool {
|
||||||
|
if self.match_all {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let host = host.to_lowercase();
|
||||||
|
|
||||||
|
self.entries.iter().any(|entry| {
|
||||||
|
if entry.starts_with('.') {
|
||||||
|
// ".example.com" matches "foo.example.com" and "example.com"
|
||||||
|
host.ends_with(entry.as_str()) || host == entry[1..]
|
||||||
|
} else {
|
||||||
|
host == *entry || host.ends_with(&format!(".{entry}"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_proxy_wildcard() {
|
||||||
|
let np = NoProxy::parse("*");
|
||||||
|
assert!(np.matches("anything.example.com"));
|
||||||
|
assert!(np.matches("localhost"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_proxy_exact_match() {
|
||||||
|
let np = NoProxy::parse("localhost, example.com");
|
||||||
|
assert!(np.matches("localhost"));
|
||||||
|
assert!(np.matches("example.com"));
|
||||||
|
assert!(!np.matches("other.com"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_proxy_subdomain_match() {
|
||||||
|
let np = NoProxy::parse("example.com");
|
||||||
|
assert!(np.matches("example.com"));
|
||||||
|
assert!(np.matches("sub.example.com"));
|
||||||
|
assert!(!np.matches("notexample.com"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_proxy_dot_prefix() {
|
||||||
|
let np = NoProxy::parse(".example.com");
|
||||||
|
assert!(np.matches("example.com"));
|
||||||
|
assert!(np.matches("sub.example.com"));
|
||||||
|
assert!(!np.matches("notexample.com"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_proxy_case_insensitive() {
|
||||||
|
let np = NoProxy::parse("Example.COM");
|
||||||
|
assert!(np.matches("example.com"));
|
||||||
|
assert!(np.matches("EXAMPLE.COM"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn proxy_for_uri_http() {
|
||||||
|
let config = ProxyConfig::default()
|
||||||
|
.with_http_proxy("http://proxy:8080".parse().unwrap());
|
||||||
|
let uri: Uri = "http://example.com/foo".parse().unwrap();
|
||||||
|
assert!(config.proxy_for_uri(&uri).is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn proxy_for_uri_no_proxy() {
|
||||||
|
let config = ProxyConfig::default()
|
||||||
|
.with_http_proxy("http://proxy:8080".parse().unwrap())
|
||||||
|
.with_no_proxy("example.com");
|
||||||
|
let uri: Uri = "http://example.com/foo".parse().unwrap();
|
||||||
|
assert!(config.proxy_for_uri(&uri).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn proxy_for_uri_https() {
|
||||||
|
let config = ProxyConfig::default()
|
||||||
|
.with_https_proxy("http://proxy:8080".parse().unwrap());
|
||||||
|
let uri: Uri = "https://example.com/foo".parse().unwrap();
|
||||||
|
assert!(config.proxy_for_uri(&uri).is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_proxy_empty_string() {
|
||||||
|
let np = NoProxy::parse("");
|
||||||
|
assert!(!np.matches("anything"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,7 +13,10 @@ use futures_core::{future::LocalBoxFuture, ready};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
any_body::AnyBody,
|
any_body::AnyBody,
|
||||||
client::{Connect as ClientConnect, ConnectError, Connection, ConnectionIo, SendRequestError},
|
client::{
|
||||||
|
proxy::ProxyConfig, Connect as ClientConnect, ConnectError, Connection, ConnectionIo,
|
||||||
|
SendRequestError,
|
||||||
|
},
|
||||||
ClientResponse,
|
ClientResponse,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -82,11 +85,20 @@ impl ConnectResponse {
|
||||||
|
|
||||||
pub struct DefaultConnector<S> {
|
pub struct DefaultConnector<S> {
|
||||||
connector: S,
|
connector: S,
|
||||||
|
proxy: ProxyConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> DefaultConnector<S> {
|
impl<S> DefaultConnector<S> {
|
||||||
pub(crate) fn new(connector: S) -> Self {
|
pub(crate) fn new(connector: S) -> Self {
|
||||||
Self { connector }
|
Self {
|
||||||
|
connector,
|
||||||
|
proxy: ProxyConfig::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn with_proxy(mut self, proxy: ProxyConfig) -> Self {
|
||||||
|
self.proxy = proxy;
|
||||||
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,19 +113,33 @@ where
|
||||||
|
|
||||||
actix_service::forward_ready!(connector);
|
actix_service::forward_ready!(connector);
|
||||||
|
|
||||||
fn call(&self, req: ConnectRequest) -> Self::Future {
|
fn call(&self, mut req: ConnectRequest) -> Self::Future {
|
||||||
// connect to the host
|
let (target_uri, addr) = match req {
|
||||||
let fut = match req {
|
ConnectRequest::Client(ref head, .., addr) => (head.as_ref().uri.clone(), addr),
|
||||||
ConnectRequest::Client(ref head, .., addr) => self.connector.call(ClientConnect {
|
ConnectRequest::Tunnel(ref head, addr) => (head.uri.clone(), addr),
|
||||||
uri: head.as_ref().uri.clone(),
|
|
||||||
addr,
|
|
||||||
}),
|
|
||||||
ConnectRequest::Tunnel(ref head, addr) => self.connector.call(ClientConnect {
|
|
||||||
uri: head.uri.clone(),
|
|
||||||
addr,
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let connect_uri = if let Some(proxy_uri) = self.proxy.proxy_for_uri(&target_uri) {
|
||||||
|
// Set the proxy flag on the request head so the encoder uses absolute-form URI
|
||||||
|
match req {
|
||||||
|
ConnectRequest::Client(ref mut head, ..) => match head {
|
||||||
|
RequestHeadType::Owned(ref mut h) => h.set_proxy_request(true),
|
||||||
|
RequestHeadType::Rc(_, _) => {
|
||||||
|
log::warn!("Cannot set proxy flag on Rc<RequestHead>; request will use origin-form URI");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ConnectRequest::Tunnel(..) => {}
|
||||||
|
}
|
||||||
|
proxy_uri.clone()
|
||||||
|
} else {
|
||||||
|
target_uri
|
||||||
|
};
|
||||||
|
|
||||||
|
let fut = self.connector.call(ClientConnect {
|
||||||
|
uri: connect_uri,
|
||||||
|
addr,
|
||||||
|
});
|
||||||
|
|
||||||
ConnectRequestFuture::Connection {
|
ConnectRequestFuture::Connection {
|
||||||
fut,
|
fut,
|
||||||
req: Some(req),
|
req: Some(req),
|
||||||
|
|
|
||||||
|
|
@ -138,7 +138,7 @@ pub mod http {
|
||||||
pub use self::responses::{ClientResponse, JsonBody, MessageBody, ResponseBody};
|
pub use self::responses::{ClientResponse, JsonBody, MessageBody, ResponseBody};
|
||||||
pub use self::{
|
pub use self::{
|
||||||
builder::ClientBuilder,
|
builder::ClientBuilder,
|
||||||
client::{Client, Connect, Connector},
|
client::{Client, Connect, Connector, ProxyConfig},
|
||||||
connect::{BoxConnectorService, BoxedSocket, ConnectRequest, ConnectResponse},
|
connect::{BoxConnectorService, BoxedSocket, ConnectRequest, ConnectResponse},
|
||||||
frozen::{FrozenClientRequest, FrozenSendBuilder},
|
frozen::{FrozenClientRequest, FrozenSendBuilder},
|
||||||
request::ClientRequest,
|
request::ClientRequest,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue