From badae2f8fd31908ac0aa905b2d6bcfbca8a0369c Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sat, 27 Feb 2021 14:31:14 -0800 Subject: [PATCH] add local_address bind for client builder (#2024) --- Cargo.toml | 2 +- actix-files/Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http/Cargo.toml | 2 +- actix-http/src/client/config.rs | 3 ++ actix-http/src/client/connector.rs | 48 ++++++++++++++++++++++-------- actix-multipart/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- awc/CHANGES.md | 2 ++ awc/Cargo.toml | 2 +- awc/src/builder.rs | 13 ++++++++ awc/tests/test_client.rs | 32 ++++++++++++++++++++ 13 files changed, 94 insertions(+), 20 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3795bc7d..bd758ab1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,7 +80,7 @@ required-features = ["rustls"] actix-codec = "0.4.0-beta.1" actix-macros = "0.2.0" actix-router = "0.2.7" -actix-rt = "2" +actix-rt = "2.1" actix-server = "2.0.0-beta.3" actix-service = "2.0.0-beta.4" actix-utils = "3.0.0-beta.2" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 08b7b36f..8f1a9ec5 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -33,5 +33,5 @@ mime_guess = "2.0.1" percent-encoding = "2.1" [dev-dependencies] -actix-rt = "2" +actix-rt = "2.1" actix-web = "4.0.0-beta.3" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index c880cba7..90096021 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -33,7 +33,7 @@ actix-service = "2.0.0-beta.4" actix-codec = "0.4.0-beta.1" actix-tls = "3.0.0-beta.4" actix-utils = "3.0.0-beta.2" -actix-rt = "2" +actix-rt = "2.1" actix-server = "2.0.0-beta.3" awc = { version = "3.0.0-beta.2", default-features = false } diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 14a9ff1e..96967e18 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -47,7 +47,7 @@ trust-dns = ["trust-dns-resolver"] actix-service = "2.0.0-beta.4" actix-codec = "0.4.0-beta.1" actix-utils = "3.0.0-beta.2" -actix-rt = "2" +actix-rt = "2.1" actix-tls = "3.0.0-beta.4" ahash = "0.7" diff --git a/actix-http/src/client/config.rs b/actix-http/src/client/config.rs index fad902d0..0d54e1b4 100644 --- a/actix-http/src/client/config.rs +++ b/actix-http/src/client/config.rs @@ -1,3 +1,4 @@ +use std::net::IpAddr; use std::time::Duration; const DEFAULT_H2_CONN_WINDOW: u32 = 1024 * 1024 * 2; // 2MB @@ -13,6 +14,7 @@ pub(crate) struct ConnectorConfig { pub(crate) limit: usize, pub(crate) conn_window_size: u32, pub(crate) stream_window_size: u32, + pub(crate) local_address: Option, } impl Default for ConnectorConfig { @@ -25,6 +27,7 @@ impl Default for ConnectorConfig { limit: 100, conn_window_size: DEFAULT_H2_CONN_WINDOW, stream_window_size: DEFAULT_H2_STREAM_WINDOW, + local_address: None, } } } diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 8aa5b131..1a926fd6 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -1,9 +1,12 @@ -use std::fmt; -use std::future::Future; -use std::marker::PhantomData; -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::time::Duration; +use std::{ + fmt, + future::Future, + marker::PhantomData, + net::IpAddr, + pin::Pin, + task::{Context, Poll}, + time::Duration, +}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_rt::net::TcpStream; @@ -240,6 +243,12 @@ where self } + /// Set local IP Address the connector would use for establishing connection. + pub fn local_address(mut self, addr: IpAddr) -> Self { + self.config.local_address = Some(addr); + self + } + /// Finish configuration process and create connector service. /// The Connector builder always concludes by calling `finish()` last in /// its combinator chain. @@ -247,10 +256,19 @@ where self, ) -> impl Service + Clone { + let local_address = self.config.local_address; + let timeout = self.config.timeout; + let tcp_service = TimeoutService::new( - self.config.timeout, - apply_fn(self.connector.clone(), |msg: Connect, srv| { - srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr)) + timeout, + apply_fn(self.connector.clone(), move |msg: Connect, srv| { + let mut req = TcpConnect::new(msg.uri).set_addr(msg.addr); + + if let Some(local_addr) = local_address { + req = req.set_local_addr(local_addr); + } + + srv.call(req) }) .map_err(ConnectError::from) .map(|stream| (stream.into_parts().0, Protocol::Http1)), @@ -294,10 +312,16 @@ where use actix_tls::connect::ssl::rustls::{RustlsConnector, Session}; let ssl_service = TimeoutService::new( - self.config.timeout, + timeout, pipeline( - apply_fn(self.connector.clone(), |msg: Connect, srv| { - srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr)) + apply_fn(self.connector.clone(), move |msg: Connect, srv| { + let mut req = TcpConnect::new(msg.uri).set_addr(msg.addr); + + if let Some(local_addr) = local_address { + req = req.set_local_addr(local_addr); + } + + srv.call(req) }) .map_err(ConnectError::from), ) diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index f3bb9233..fcbadde3 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -28,7 +28,7 @@ mime = "0.3" twoway = "0.2" [dev-dependencies] -actix-rt = "2" +actix-rt = "2.1" actix-http = "3.0.0-beta.3" tokio = { version = "1", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 698ff942..58508071 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -28,6 +28,6 @@ pin-project = "1.0.0" tokio = { version = "1", features = ["sync"] } [dev-dependencies] -actix-rt = "2" +actix-rt = "2.1" env_logger = "0.8" futures-util = { version = "0.3.7", default-features = false } diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 886d9ac3..e43e91e2 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -19,7 +19,7 @@ syn = { version = "1", features = ["full", "parsing"] } proc-macro2 = "1" [dev-dependencies] -actix-rt = "2" +actix-rt = "2.1" actix-web = "4.0.0-beta.3" futures-util = { version = "0.3.7", default-features = false } trybuild = "1" diff --git a/awc/CHANGES.md b/awc/CHANGES.md index e6ead2cc..9fbc6d04 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -3,6 +3,7 @@ ## Unreleased - 2021-xx-xx ### Added * `ClientResponse::timeout` for set the timeout of collecting response body. [#1931] +* `ClientBuilder::local_address` for bind to a local ip address for this client. [#2024] ### Changed * Feature `cookies` is now optional and enabled by default. [#1981] @@ -15,6 +16,7 @@ [#1931]: https://github.com/actix/actix-web/pull/1931 [#1981]: https://github.com/actix/actix-web/pull/1981 [#2008]: https://github.com/actix/actix-web/pull/2008 +[#2024]: https://github.com/actix/actix-web/pull/2024 ## 3.0.0-beta.2 - 2021-02-10 ### Added diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 45b355ab..8cbba432 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -47,7 +47,7 @@ trust-dns = ["actix-http/trust-dns"] actix-codec = "0.4.0-beta.1" actix-service = "2.0.0-beta.4" actix-http = "3.0.0-beta.3" -actix-rt = "2" +actix-rt = "2.1" base64 = "0.13" bytes = "1" diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 4495b39f..b7cdefd4 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -1,5 +1,6 @@ use std::convert::TryFrom; use std::fmt; +use std::net::IpAddr; use std::rc::Rc; use std::time::Duration; @@ -25,6 +26,7 @@ pub struct ClientBuilder { conn_window_size: Option, headers: HeaderMap, timeout: Option, + local_address: Option, connector: Connector, } @@ -42,6 +44,7 @@ impl ClientBuilder { default_headers: true, headers: HeaderMap::new(), timeout: Some(Duration::from_secs(5)), + local_address: None, connector: Connector::new(), max_http_version: None, stream_window_size: None, @@ -72,6 +75,7 @@ where default_headers: self.default_headers, headers: self.headers, timeout: self.timeout, + local_address: None, connector, max_http_version: self.max_http_version, stream_window_size: self.stream_window_size, @@ -94,6 +98,12 @@ where self } + /// Set local IP Address the connector would use for establishing connection. + pub fn local_address(mut self, addr: IpAddr) -> Self { + self.local_address = Some(addr); + self + } + /// Maximum supported HTTP major version. /// /// Supported versions are HTTP/1.1 and HTTP/2. @@ -184,6 +194,9 @@ where if let Some(val) = self.stream_window_size { connector = connector.initial_window_size(val) }; + if let Some(val) = self.local_address { + connector = connector.local_address(val); + } let config = ClientConfig { headers: self.headers, diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index a41a8dac..c7fa82de 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; use std::io::{Read, Write}; +use std::net::{IpAddr, Ipv4Addr}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use std::time::Duration; @@ -871,3 +872,34 @@ async fn client_bearer_auth() { let response = request.send().await.unwrap(); assert!(response.status().is_success()); } + +#[actix_rt::test] +async fn test_local_address() { + let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); + + let srv = test::start(move || { + App::new().service(web::resource("/").route(web::to( + move |req: HttpRequest| async move { + assert_eq!(req.peer_addr().unwrap().ip(), ip); + Ok::<_, Error>(HttpResponse::Ok()) + }, + ))) + }); + let client = awc::Client::builder().local_address(ip).finish(); + + let res = client.get(srv.url("/")).send().await.unwrap(); + + assert_eq!(res.status(), 200); + + let client = awc::Client::builder() + .connector( + // connector local address setting should always be override by client builder. + awc::Connector::new().local_address(IpAddr::V4(Ipv4Addr::new(128, 0, 0, 1))), + ) + .local_address(ip) + .finish(); + + let res = client.get(srv.url("/")).send().await.unwrap(); + + assert_eq!(res.status(), 200); +}