1
0
Fork 0
mirror of https://github.com/actix/actix-web.git synced 2024-06-02 21:39:26 +00:00
actix-web/actix-http/src/client/connector.rs

533 lines
19 KiB
Rust
Raw Normal View History

use std::{
fmt,
future::Future,
net::IpAddr,
pin::Pin,
task::{Context, Poll},
time::Duration,
};
2018-11-12 07:12:54 +00:00
2018-12-11 02:08:33 +00:00
use actix_codec::{AsyncRead, AsyncWrite};
use actix_rt::net::TcpStream;
use actix_service::{apply_fn, Service, ServiceExt};
use actix_tls::connect::{
new_connector, Connect as TcpConnect, Connection as TcpConnection, Resolver,
2019-04-05 17:50:11 +00:00
};
2018-12-11 02:08:33 +00:00
use actix_utils::timeout::{TimeoutError, TimeoutService};
use futures_core::ready;
2019-03-14 18:52:52 +00:00
use http::Uri;
2018-11-12 07:12:54 +00:00
use super::config::ConnectorConfig;
use super::connection::{Connection, ConnectionIo, EitherIoConnection};
2019-03-13 21:41:40 +00:00
use super::error::ConnectError;
use super::pool::ConnectionPool;
use super::Connect;
use super::Protocol;
2018-11-12 07:12:54 +00:00
2019-11-18 14:40:10 +00:00
#[cfg(feature = "openssl")]
use actix_tls::connect::ssl::openssl::SslConnector as OpensslConnector;
2019-11-18 14:40:10 +00:00
#[cfg(feature = "rustls")]
use actix_tls::connect::ssl::rustls::ClientConfig;
2019-11-18 14:40:10 +00:00
#[cfg(feature = "rustls")]
use std::sync::Arc;
2019-11-18 14:40:10 +00:00
#[cfg(any(feature = "openssl", feature = "rustls"))]
enum SslConnector {
2019-11-18 14:40:10 +00:00
#[cfg(feature = "openssl")]
Openssl(OpensslConnector),
2019-11-18 14:40:10 +00:00
#[cfg(feature = "rustls")]
Rustls(Arc<ClientConfig>),
}
2019-11-18 14:40:10 +00:00
#[cfg(not(any(feature = "openssl", feature = "rustls")))]
2018-11-12 07:12:54 +00:00
type SslConnector = ();
2021-02-11 22:39:54 +00:00
/// Manages HTTP client network connectivity.
///
/// The `Connector` type uses a builder-like combinator pattern for service
/// construction that finishes by calling the `.finish()` method.
///
2019-04-05 23:46:44 +00:00
/// ```rust,ignore
/// use std::time::Duration;
/// use actix_http::client::Connector;
///
/// let connector = Connector::new()
2019-04-05 23:46:44 +00:00
/// .timeout(Duration::from_secs(5))
/// .finish();
/// ```
pub struct Connector<T> {
2019-03-13 21:41:40 +00:00
connector: T,
config: ConnectorConfig,
2018-11-12 07:12:54 +00:00
#[allow(dead_code)]
2019-03-13 21:41:40 +00:00
ssl: SslConnector,
2018-11-12 07:12:54 +00:00
}
impl Connector<()> {
2019-12-07 18:46:51 +00:00
#[allow(clippy::new_ret_no_self, clippy::let_unit_value)]
2019-03-13 21:41:40 +00:00
pub fn new() -> Connector<
impl Service<
TcpConnect<Uri>,
2019-03-14 18:52:52 +00:00
Response = TcpConnection<Uri, TcpStream>,
Error = actix_tls::connect::ConnectError,
2019-03-13 21:41:40 +00:00
> + Clone,
> {
2018-11-12 07:12:54 +00:00
Connector {
ssl: Self::build_ssl(vec![b"h2".to_vec(), b"http/1.1".to_vec()]),
connector: new_connector(resolver::resolver()),
config: ConnectorConfig::default(),
2018-11-12 07:12:54 +00:00
}
}
// Build Ssl connector with openssl, based on supplied alpn protocols
#[cfg(feature = "openssl")]
2020-03-07 15:52:39 +00:00
fn build_ssl(protocols: Vec<Vec<u8>>) -> SslConnector {
use actix_tls::connect::ssl::openssl::SslMethod;
use bytes::{BufMut, BytesMut};
let mut alpn = BytesMut::with_capacity(20);
for proto in protocols.iter() {
alpn.put_u8(proto.len() as u8);
alpn.put(proto.as_slice());
}
let mut ssl = OpensslConnector::builder(SslMethod::tls()).unwrap();
let _ = ssl
.set_alpn_protos(&alpn)
.map_err(|e| error!("Can not set alpn protocol: {:?}", e));
SslConnector::Openssl(ssl.build())
}
// Build Ssl connector with rustls, based on supplied alpn protocols
#[cfg(all(not(feature = "openssl"), feature = "rustls"))]
2020-03-07 15:52:39 +00:00
fn build_ssl(protocols: Vec<Vec<u8>>) -> SslConnector {
let mut config = ClientConfig::new();
config.set_protocols(&protocols);
config.root_store.add_server_trust_anchors(
&actix_tls::connect::ssl::rustls::TLS_SERVER_ROOTS,
);
SslConnector::Rustls(Arc::new(config))
}
// ssl turned off, provides empty ssl connector
#[cfg(not(any(feature = "openssl", feature = "rustls")))]
fn build_ssl(_: Vec<Vec<u8>>) -> SslConnector {}
2018-11-12 07:12:54 +00:00
}
impl<T> Connector<T> {
2019-03-13 21:41:40 +00:00
/// Use custom connector.
pub fn connector<T1, U1>(self, connector: T1) -> Connector<T1>
2019-03-13 21:41:40 +00:00
where
2019-11-18 12:42:27 +00:00
U1: AsyncRead + AsyncWrite + Unpin + fmt::Debug,
2019-03-13 21:41:40 +00:00
T1: Service<
TcpConnect<Uri>,
2019-03-14 18:52:52 +00:00
Response = TcpConnection<Uri, U1>,
Error = actix_tls::connect::ConnectError,
2019-03-13 21:41:40 +00:00
> + Clone,
{
Connector {
connector,
config: self.config,
2019-03-13 21:41:40 +00:00
ssl: self.ssl,
}
2018-11-12 07:12:54 +00:00
}
2019-03-13 21:41:40 +00:00
}
2018-11-12 07:12:54 +00:00
impl<T, U> Connector<T>
2019-03-13 21:41:40 +00:00
where
2019-11-18 12:42:27 +00:00
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static,
2019-03-13 21:41:40 +00:00
T: Service<
TcpConnect<Uri>,
2019-03-14 18:52:52 +00:00
Response = TcpConnection<Uri, U>,
Error = actix_tls::connect::ConnectError,
2019-11-18 14:40:10 +00:00
> + Clone
+ 'static,
2019-03-13 21:41:40 +00:00
{
2018-11-12 07:12:54 +00:00
/// Connection timeout, i.e. max time to connect to remote host including dns name resolution.
/// Set to 5 second by default.
2018-11-12 07:12:54 +00:00
pub fn timeout(mut self, timeout: Duration) -> Self {
self.config.timeout = timeout;
2018-11-12 07:12:54 +00:00
self
}
2019-11-18 14:40:10 +00:00
#[cfg(feature = "openssl")]
2018-11-12 07:12:54 +00:00
/// Use custom `SslConnector` instance.
pub fn ssl(mut self, connector: OpensslConnector) -> Self {
self.ssl = SslConnector::Openssl(connector);
self
}
2019-11-18 14:40:10 +00:00
#[cfg(feature = "rustls")]
/// Use custom `SslConnector` instance.
pub fn rustls(mut self, connector: Arc<ClientConfig>) -> Self {
self.ssl = SslConnector::Rustls(connector);
2018-11-12 07:12:54 +00:00
self
}
2021-02-11 22:39:54 +00:00
/// Maximum supported HTTP major version.
///
/// Supported versions are HTTP/1.1 and HTTP/2.
pub fn max_http_version(mut self, val: http::Version) -> Self {
let versions = match val {
http::Version::HTTP_11 => vec![b"http/1.1".to_vec()],
http::Version::HTTP_2 => vec![b"h2".to_vec(), b"http/1.1".to_vec()],
2020-03-07 15:52:39 +00:00
_ => {
unimplemented!("actix-http:client: supported versions http/1.1, http/2")
}
};
self.ssl = Connector::build_ssl(versions);
self
}
/// Indicates the initial window size (in octets) for
/// HTTP2 stream-level flow control for received data.
///
/// The default value is 65,535 and is good for APIs, but not for big objects.
pub fn initial_window_size(mut self, size: u32) -> Self {
self.config.stream_window_size = size;
self
}
/// Indicates the initial window size (in octets) for
/// HTTP2 connection-level flow control for received data.
///
/// The default value is 65,535 and is good for APIs, but not for big objects.
pub fn initial_connection_window_size(mut self, size: u32) -> Self {
self.config.conn_window_size = size;
self
}
2018-11-12 07:12:54 +00:00
/// Set total number of simultaneous connections per type of scheme.
///
/// If limit is 0, the connector has no limit.
/// The default limit size is 100.
pub fn limit(mut self, limit: usize) -> Self {
self.config.limit = limit;
2018-11-12 07:12:54 +00:00
self
}
/// Set keep-alive period for opened connection.
///
/// Keep-alive period is the period between connection usage. If
/// the delay between repeated usages of the same connection
/// exceeds this period, the connection is closed.
/// Default keep-alive period is 15 seconds.
pub fn conn_keep_alive(mut self, dur: Duration) -> Self {
self.config.conn_keep_alive = dur;
2018-11-12 07:12:54 +00:00
self
}
/// Set max lifetime period for connection.
///
/// Connection lifetime is max lifetime of any opened connection
/// until it is closed regardless of keep-alive period.
/// Default lifetime period is 75 seconds.
pub fn conn_lifetime(mut self, dur: Duration) -> Self {
self.config.conn_lifetime = dur;
2018-11-12 07:12:54 +00:00
self
}
/// Set server connection disconnect timeout in milliseconds.
///
/// Defines a timeout for disconnect connection. If a disconnect procedure does not complete
/// within this time, the socket get dropped. This timeout affects only secure connections.
///
/// To disable timeout set value to 0.
///
/// By default disconnect timeout is set to 3000 milliseconds.
pub fn disconnect_timeout(mut self, dur: Duration) -> Self {
self.config.disconnect_timeout = Some(dur);
2018-11-12 07:12:54 +00:00
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
}
2019-04-05 18:36:26 +00:00
/// Finish configuration process and create connector service.
/// The Connector builder always concludes by calling `finish()` last in
/// its combinator chain.
pub fn finish(
2018-11-12 07:12:54 +00:00
self,
) -> impl Service<Connect, Response = impl Connection, Error = ConnectError> {
let local_address = self.config.local_address;
let timeout = self.config.timeout;
2021-02-22 11:15:12 +00:00
let tcp_service = TimeoutService::new(
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)
2021-02-22 11:15:12 +00:00
})
.map_err(ConnectError::from)
.map(|stream| (stream.into_parts().0, Protocol::Http1)),
)
.map_err(|e| match e {
TimeoutError::Service(e) => e,
TimeoutError::Timeout => ConnectError::Timeout,
});
2019-11-18 14:40:10 +00:00
#[cfg(not(any(feature = "openssl", feature = "rustls")))]
2018-11-12 07:12:54 +00:00
{
2021-02-22 11:15:12 +00:00
// A dummy service for annotate tls pool's type signature.
pub type DummyService = Box<
dyn Service<
Connect,
Response = (Box<dyn ConnectionIo>, Protocol),
2021-02-22 11:15:12 +00:00
Error = ConnectError,
Future = futures_core::future::LocalBoxFuture<
'static,
Result<(Box<dyn ConnectionIo>, Protocol), ConnectError>,
2021-02-22 11:15:12 +00:00
>,
>,
>;
InnerConnector::<_, DummyService, _, Box<dyn ConnectionIo>> {
2018-11-12 07:12:54 +00:00
tcp_pool: ConnectionPool::new(
2021-02-22 11:15:12 +00:00
tcp_service,
self.config.no_disconnect_timeout(),
2018-11-12 07:12:54 +00:00
),
2021-02-22 11:15:12 +00:00
tls_pool: None,
2018-11-12 07:12:54 +00:00
}
}
2021-02-22 11:15:12 +00:00
2019-11-18 14:40:10 +00:00
#[cfg(any(feature = "openssl", feature = "rustls"))]
2018-11-12 07:12:54 +00:00
{
2019-02-06 19:44:15 +00:00
const H2: &[u8] = b"h2";
use actix_service::{boxed::service, pipeline};
2019-11-18 14:40:10 +00:00
#[cfg(feature = "openssl")]
use actix_tls::connect::ssl::openssl::OpensslConnector;
2019-11-18 14:40:10 +00:00
#[cfg(feature = "rustls")]
use actix_tls::connect::ssl::rustls::{RustlsConnector, Session};
2019-02-06 19:44:15 +00:00
2019-03-05 03:51:09 +00:00
let ssl_service = TimeoutService::new(
timeout,
2019-11-18 14:40:10 +00:00
pipeline(
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)
2019-11-19 12:54:19 +00:00
})
2019-11-18 14:40:10 +00:00
.map_err(ConnectError::from),
)
.and_then(match self.ssl {
2019-11-18 14:40:10 +00:00
#[cfg(feature = "openssl")]
2019-11-19 12:54:19 +00:00
SslConnector::Openssl(ssl) => service(
OpensslConnector::service(ssl)
.map(|stream| {
let sock = stream.into_parts().0;
let h2 = sock
.ssl()
.selected_alpn_protocol()
.map(|protos| protos.windows(2).any(|w| w == H2))
.unwrap_or(false);
if h2 {
(
Box::new(sock) as Box<dyn ConnectionIo>,
Protocol::Http2,
)
2019-11-19 12:54:19 +00:00
} else {
(Box::new(sock) as _, Protocol::Http1)
2019-11-19 12:54:19 +00:00
}
})
.map_err(ConnectError::from),
),
2019-11-18 14:40:10 +00:00
#[cfg(feature = "rustls")]
SslConnector::Rustls(ssl) => service(
2019-11-19 12:54:19 +00:00
RustlsConnector::service(ssl)
.map_err(ConnectError::from)
.map(|stream| {
let sock = stream.into_parts().0;
let h2 = sock
.get_ref()
.1
.get_alpn_protocol()
.map(|protos| protos.windows(2).any(|w| w == H2))
.unwrap_or(false);
if h2 {
(Box::new(sock) as _, Protocol::Http2)
} else {
(Box::new(sock) as _, Protocol::Http1)
}
}),
),
}),
2018-12-06 22:32:52 +00:00
)
.map_err(|e| match e {
2018-11-12 07:12:54 +00:00
TimeoutError::Service(e) => e,
2019-03-13 21:41:40 +00:00
TimeoutError::Timeout => ConnectError::Timeout,
2018-11-12 07:12:54 +00:00
});
2021-02-22 11:15:12 +00:00
InnerConnector {
2018-11-12 07:12:54 +00:00
tcp_pool: ConnectionPool::new(
tcp_service,
self.config.no_disconnect_timeout(),
2018-11-12 07:12:54 +00:00
),
2021-02-22 11:15:12 +00:00
tls_pool: Some(ConnectionPool::new(ssl_service, self.config)),
2018-11-12 07:12:54 +00:00
}
}
}
}
2021-02-22 11:15:12 +00:00
struct InnerConnector<S1, S2, Io1, Io2>
where
S1: Service<Connect, Response = (Io1, Protocol), Error = ConnectError> + 'static,
S2: Service<Connect, Response = (Io2, Protocol), Error = ConnectError> + 'static,
Io1: AsyncRead + AsyncWrite + Unpin + 'static,
Io2: AsyncRead + AsyncWrite + Unpin + 'static,
{
tcp_pool: ConnectionPool<S1, Io1>,
tls_pool: Option<ConnectionPool<S2, Io2>>,
}
2018-11-12 07:12:54 +00:00
2021-02-22 11:15:12 +00:00
impl<S1, S2, Io1, Io2> Service<Connect> for InnerConnector<S1, S2, Io1, Io2>
where
S1: Service<Connect, Response = (Io1, Protocol), Error = ConnectError> + 'static,
S2: Service<Connect, Response = (Io2, Protocol), Error = ConnectError> + 'static,
Io1: AsyncRead + AsyncWrite + Unpin + 'static,
Io2: AsyncRead + AsyncWrite + Unpin + 'static,
{
type Response = EitherIoConnection<Io1, Io2>;
type Error = ConnectError;
type Future = InnerConnectorResponse<S1, S2, Io1, Io2>;
2018-11-12 07:12:54 +00:00
2021-02-22 11:15:12 +00:00
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
ready!(self.tcp_pool.poll_ready(cx))?;
if let Some(ref tls_pool) = self.tls_pool {
ready!(tls_pool.poll_ready(cx))?;
}
Poll::Ready(Ok(()))
2018-11-12 07:12:54 +00:00
}
2021-02-22 11:15:12 +00:00
fn call(&self, req: Connect) -> Self::Future {
match req.uri.scheme_str() {
Some("https") | Some("wss") => match self.tls_pool {
None => InnerConnectorResponse::SslIsNotSupported,
Some(ref pool) => InnerConnectorResponse::Io2(pool.call(req)),
},
_ => InnerConnectorResponse::Io1(self.tcp_pool.call(req)),
2018-11-12 07:12:54 +00:00
}
}
2021-02-22 11:15:12 +00:00
}
2018-11-12 07:12:54 +00:00
2021-02-22 11:15:12 +00:00
#[pin_project::pin_project(project = InnerConnectorProj)]
enum InnerConnectorResponse<S1, S2, Io1, Io2>
where
S1: Service<Connect, Response = (Io1, Protocol), Error = ConnectError> + 'static,
S2: Service<Connect, Response = (Io2, Protocol), Error = ConnectError> + 'static,
Io1: AsyncRead + AsyncWrite + Unpin + 'static,
Io2: AsyncRead + AsyncWrite + Unpin + 'static,
{
Io1(#[pin] <ConnectionPool<S1, Io1> as Service<Connect>>::Future),
Io2(#[pin] <ConnectionPool<S2, Io2> as Service<Connect>>::Future),
SslIsNotSupported,
}
2018-11-12 07:12:54 +00:00
2021-02-22 11:15:12 +00:00
impl<S1, S2, Io1, Io2> Future for InnerConnectorResponse<S1, S2, Io1, Io2>
where
S1: Service<Connect, Response = (Io1, Protocol), Error = ConnectError> + 'static,
S2: Service<Connect, Response = (Io2, Protocol), Error = ConnectError> + 'static,
Io1: AsyncRead + AsyncWrite + Unpin + 'static,
Io2: AsyncRead + AsyncWrite + Unpin + 'static,
{
type Output = Result<EitherIoConnection<Io1, Io2>, ConnectError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.project() {
InnerConnectorProj::Io1(fut) => fut.poll(cx).map_ok(EitherIoConnection::A),
InnerConnectorProj::Io2(fut) => fut.poll(cx).map_ok(EitherIoConnection::B),
InnerConnectorProj::SslIsNotSupported => {
Poll::Ready(Err(ConnectError::SslIsNotSupported))
}
2018-11-12 07:12:54 +00:00
}
}
}
#[cfg(not(feature = "trust-dns"))]
mod resolver {
use super::*;
pub(super) fn resolver() -> Resolver {
Resolver::Default
}
}
#[cfg(feature = "trust-dns")]
mod resolver {
use std::{cell::RefCell, net::SocketAddr};
use actix_tls::connect::Resolve;
use futures_core::future::LocalBoxFuture;
use trust_dns_resolver::{
config::{ResolverConfig, ResolverOpts},
system_conf::read_system_conf,
TokioAsyncResolver,
};
use super::*;
pub(super) fn resolver() -> Resolver {
// new type for impl Resolve trait for TokioAsyncResolver.
struct TrustDnsResolver(TokioAsyncResolver);
impl Resolve for TrustDnsResolver {
fn lookup<'a>(
&'a self,
host: &'a str,
port: u16,
) -> LocalBoxFuture<'a, Result<Vec<SocketAddr>, Box<dyn std::error::Error>>>
{
Box::pin(async move {
let res = self
.0
.lookup_ip(host)
.await?
.iter()
.map(|ip| SocketAddr::new(ip, port))
.collect();
Ok(res)
})
}
}
// dns struct is cached in thread local.
// so new client constructor can reuse the existing dns resolver.
thread_local! {
static TRUST_DNS_RESOLVER: RefCell<Option<Resolver>> = RefCell::new(None);
}
// get from thread local or construct a new trust-dns resolver.
TRUST_DNS_RESOLVER.with(|local| {
let resolver = local.borrow().as_ref().map(Clone::clone);
match resolver {
Some(resolver) => resolver,
None => {
let (cfg, opts) = match read_system_conf() {
Ok((cfg, opts)) => (cfg, opts),
Err(e) => {
log::error!("TRust-DNS can not load system config: {}", e);
(ResolverConfig::default(), ResolverOpts::default())
}
};
let resolver = TokioAsyncResolver::tokio(cfg, opts).unwrap();
// box trust dns resolver and put it in thread local.
let resolver = Resolver::new_custom(TrustDnsResolver(resolver));
*local.borrow_mut() = Some(resolver.clone());
resolver
}
}
})
}
}