mirror of
https://github.com/fafhrd91/actix-net
synced 2024-11-27 19:12:56 +01:00
actix-tls release candidate prep (#422)
This commit is contained in:
parent
5556afd524
commit
5dc2bfcb01
@ -1,6 +1,33 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
### Added
|
||||
* Derive `Debug` for `connect::Connection`. [#422]
|
||||
* Implement `Display` for `accept::TlsError`. [#422]
|
||||
* Implement `Error` for `accept::TlsError` where both types also implement `Error`. [#422]
|
||||
* Implement `Default` for `connect::Resolver`. [#422]
|
||||
* Implement `Error` for `connect::ConnectError`. [#422]
|
||||
|
||||
### Changed
|
||||
* There are now no default features. [#422]
|
||||
* Useful re-exports from underlying TLS crates are exposed in a `reexports` modules in all acceptors and connectors.
|
||||
* Convert `connect::ResolverService` from enum to struct. [#422]
|
||||
* Make `ConnectAddrsIter` private. [#422]
|
||||
* Rename `accept::native_tls::{NativeTlsAcceptorService => AcceptorService}`. [#422]
|
||||
* Rename `connect::{Address => Host}` trait. [#422]
|
||||
* Rename method `connect::Connection::{host => hostname}`. [#422]
|
||||
* Rename struct `connect::{Connect => ConnectInfo}`. [#422]
|
||||
* Rename struct `connect::{ConnectService => ConnectorService}`. [#422]
|
||||
* Rename struct `connect::{ConnectServiceFactory => Connector}`. [#422]
|
||||
* Rename TLS acceptor service future types and hide from docs. [#422]
|
||||
* Unbox some service futures types. [#422]
|
||||
|
||||
### Removed
|
||||
* Remove `connect::{new_connector, new_connector_factory, default_connector, default_connector_factory}` methods. [#422]
|
||||
* Remove `connect::native_tls::Connector::service` method. [#422]
|
||||
* Remove redundant `connect::Connection::from_parts` method. [#422]
|
||||
|
||||
[#422]: https://github.com/actix/actix-net/pull/422
|
||||
|
||||
|
||||
## 3.0.0-beta.9 - 2021-11-22
|
||||
@ -41,8 +68,7 @@
|
||||
* Remove `connect::ssl::openssl::OpensslConnectService`. [#297]
|
||||
* Add `connect::ssl::native_tls` module for native tls support. [#295]
|
||||
* Rename `accept::{nativetls => native_tls}`. [#295]
|
||||
* Remove `connect::TcpConnectService` type. service caller expect a `TcpStream` should use
|
||||
`connect::ConnectService` instead and call `Connection<T, TcpStream>::into_parts`. [#299]
|
||||
* Remove `connect::TcpConnectService` type. Service caller expecting a `TcpStream` should use `connect::ConnectService` instead and call `Connection<T, TcpStream>::into_parts`. [#299]
|
||||
|
||||
[#295]: https://github.com/actix/actix-net/pull/295
|
||||
[#296]: https://github.com/actix/actix-net/pull/296
|
||||
|
@ -13,14 +13,15 @@ license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["openssl", "rustls", "native-tls", "accept", "connect", "uri"]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[lib]
|
||||
name = "actix_tls"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = ["accept", "connect", "uri"]
|
||||
default = []
|
||||
|
||||
# enable acceptor services
|
||||
accept = []
|
||||
@ -48,11 +49,13 @@ actix-utils = "3.0.0"
|
||||
|
||||
derive_more = "0.99.5"
|
||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||
http = { version = "0.2.3", optional = true }
|
||||
log = "0.4"
|
||||
pin-project-lite = "0.2.7"
|
||||
tokio-util = { version = "0.6.3", default-features = false }
|
||||
|
||||
# uri
|
||||
http = { version = "0.2.3", optional = true }
|
||||
|
||||
# openssl
|
||||
tls-openssl = { package = "openssl", version = "0.10.9", optional = true }
|
||||
tokio-openssl = { version = "0.6", optional = true }
|
||||
|
@ -1,4 +1,4 @@
|
||||
//! TLS acceptor services.
|
||||
//! TLS connection acceptor services.
|
||||
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
@ -6,14 +6,18 @@ use std::{
|
||||
};
|
||||
|
||||
use actix_utils::counter::Counter;
|
||||
use derive_more::{Display, Error};
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "openssl")))]
|
||||
pub mod openssl;
|
||||
|
||||
#[cfg(feature = "rustls")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
|
||||
pub mod rustls;
|
||||
|
||||
#[cfg(feature = "native-tls")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
|
||||
pub mod native_tls;
|
||||
|
||||
pub(crate) static MAX_CONN: AtomicUsize = AtomicUsize::new(256);
|
||||
@ -41,15 +45,18 @@ pub fn max_concurrent_tls_connect(num: usize) {
|
||||
/// All TLS acceptors from this crate will return the `SvcErr` type parameter as [`Infallible`],
|
||||
/// which can be cast to your own service type, inferred or otherwise,
|
||||
/// using [`into_service_error`](Self::into_service_error).
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Display, Error)]
|
||||
pub enum TlsError<TlsErr, SvcErr> {
|
||||
/// TLS handshake has timed-out.
|
||||
#[display(fmt = "TLS handshake has timed-out")]
|
||||
Timeout,
|
||||
|
||||
/// Wraps TLS service errors.
|
||||
#[display(fmt = "TLS handshake error")]
|
||||
Tls(TlsErr),
|
||||
|
||||
/// Wraps inner service errors.
|
||||
/// Wraps service errors.
|
||||
#[display(fmt = "Service error")]
|
||||
Service(SvcErr),
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,10 @@
|
||||
//! `native-tls` based TLS connection acceptor service.
|
||||
//!
|
||||
//! See [`Acceptor`] for main service factory docs.
|
||||
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
io::{self, IoSlice},
|
||||
ops::{Deref, DerefMut},
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
time::Duration,
|
||||
@ -13,37 +16,27 @@ use actix_rt::{
|
||||
time::timeout,
|
||||
};
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use actix_utils::counter::Counter;
|
||||
use actix_utils::{
|
||||
counter::Counter,
|
||||
future::{ready, Ready as FutReady},
|
||||
};
|
||||
use derive_more::{Deref, DerefMut, From};
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
|
||||
pub use tokio_native_tls::{native_tls::Error, TlsAcceptor};
|
||||
use tokio_native_tls::{native_tls::Error, TlsAcceptor};
|
||||
|
||||
use super::{TlsError, DEFAULT_TLS_HANDSHAKE_TIMEOUT, MAX_CONN_COUNTER};
|
||||
|
||||
/// Wrapper type for `tokio_native_tls::TlsStream` in order to impl `ActixStream` trait.
|
||||
pub struct TlsStream<T>(tokio_native_tls::TlsStream<T>);
|
||||
pub mod reexports {
|
||||
//! Re-exports from `native-tls` that are useful for acceptors.
|
||||
|
||||
impl<T> From<tokio_native_tls::TlsStream<T>> for TlsStream<T> {
|
||||
fn from(stream: tokio_native_tls::TlsStream<T>) -> Self {
|
||||
Self(stream)
|
||||
}
|
||||
pub use tokio_native_tls::{native_tls::Error, TlsAcceptor};
|
||||
}
|
||||
|
||||
impl<T: ActixStream> Deref for TlsStream<T> {
|
||||
type Target = tokio_native_tls::TlsStream<T>;
|
||||
/// Wraps a `native-tls` based async TLS stream in order to implement [`ActixStream`].
|
||||
#[derive(Deref, DerefMut, From)]
|
||||
pub struct TlsStream<IO>(tokio_native_tls::TlsStream<IO>);
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ActixStream> DerefMut for TlsStream<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ActixStream> AsyncRead for TlsStream<T> {
|
||||
impl<IO: ActixStream> AsyncRead for TlsStream<IO> {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
@ -53,7 +46,7 @@ impl<T: ActixStream> AsyncRead for TlsStream<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ActixStream> AsyncWrite for TlsStream<T> {
|
||||
impl<IO: ActixStream> AsyncWrite for TlsStream<IO> {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
@ -83,27 +76,24 @@ impl<T: ActixStream> AsyncWrite for TlsStream<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ActixStream> ActixStream for TlsStream<T> {
|
||||
impl<IO: ActixStream> ActixStream for TlsStream<IO> {
|
||||
fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
|
||||
T::poll_read_ready((&**self).get_ref().get_ref().get_ref(), cx)
|
||||
IO::poll_read_ready((&**self).get_ref().get_ref().get_ref(), cx)
|
||||
}
|
||||
|
||||
fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
|
||||
T::poll_write_ready((&**self).get_ref().get_ref().get_ref(), cx)
|
||||
IO::poll_write_ready((&**self).get_ref().get_ref().get_ref(), cx)
|
||||
}
|
||||
}
|
||||
|
||||
/// Accept TLS connections via `native-tls` package.
|
||||
///
|
||||
/// `native-tls` feature enables this `Acceptor` type.
|
||||
/// Accept TLS connections via the `native-tls` crate.
|
||||
pub struct Acceptor {
|
||||
acceptor: TlsAcceptor,
|
||||
handshake_timeout: Duration,
|
||||
}
|
||||
|
||||
impl Acceptor {
|
||||
/// Create `native-tls` based `Acceptor` service factory.
|
||||
#[inline]
|
||||
/// Constructs `native-tls` based `Acceptor` service factory.
|
||||
pub fn new(acceptor: TlsAcceptor) -> Self {
|
||||
Acceptor {
|
||||
acceptor,
|
||||
@ -130,35 +120,36 @@ impl Clone for Acceptor {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ActixStream + 'static> ServiceFactory<T> for Acceptor {
|
||||
type Response = TlsStream<T>;
|
||||
impl<IO: ActixStream + 'static> ServiceFactory<IO> for Acceptor {
|
||||
type Response = TlsStream<IO>;
|
||||
type Error = TlsError<Error, Infallible>;
|
||||
type Config = ();
|
||||
type Service = NativeTlsAcceptorService;
|
||||
type Service = AcceptorService;
|
||||
type InitError = ();
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
||||
type Future = FutReady<Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
let res = MAX_CONN_COUNTER.with(|conns| {
|
||||
Ok(NativeTlsAcceptorService {
|
||||
Ok(AcceptorService {
|
||||
acceptor: self.acceptor.clone(),
|
||||
conns: conns.clone(),
|
||||
handshake_timeout: self.handshake_timeout,
|
||||
})
|
||||
});
|
||||
|
||||
Box::pin(async { res })
|
||||
ready(res)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NativeTlsAcceptorService {
|
||||
/// Native-TLS based acceptor service.
|
||||
pub struct AcceptorService {
|
||||
acceptor: TlsAcceptor,
|
||||
conns: Counter,
|
||||
handshake_timeout: Duration,
|
||||
}
|
||||
|
||||
impl<T: ActixStream + 'static> Service<T> for NativeTlsAcceptorService {
|
||||
type Response = TlsStream<T>;
|
||||
impl<IO: ActixStream + 'static> Service<IO> for AcceptorService {
|
||||
type Response = TlsStream<IO>;
|
||||
type Error = TlsError<Error, Infallible>;
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||
|
||||
@ -170,7 +161,7 @@ impl<T: ActixStream + 'static> Service<T> for NativeTlsAcceptorService {
|
||||
}
|
||||
}
|
||||
|
||||
fn call(&self, io: T) -> Self::Future {
|
||||
fn call(&self, io: IO) -> Self::Future {
|
||||
let guard = self.conns.get();
|
||||
let acceptor = self.acceptor.clone();
|
||||
|
||||
|
@ -1,8 +1,11 @@
|
||||
//! `openssl` based TLS acceptor service.
|
||||
//!
|
||||
//! See [`Acceptor`] for main service factory docs.
|
||||
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
future::Future,
|
||||
io::{self, IoSlice},
|
||||
ops::{Deref, DerefMut},
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
time::Duration,
|
||||
@ -14,40 +17,29 @@ use actix_rt::{
|
||||
time::{sleep, Sleep},
|
||||
};
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use actix_utils::counter::{Counter, CounterGuard};
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
|
||||
pub use openssl::ssl::{
|
||||
AlpnError, Error as SslError, HandshakeError, Ssl, SslAcceptor, SslAcceptorBuilder,
|
||||
use actix_utils::{
|
||||
counter::{Counter, CounterGuard},
|
||||
future::{ready, Ready as FutReady},
|
||||
};
|
||||
use derive_more::{Deref, DerefMut, From};
|
||||
use openssl::ssl::{Error, Ssl, SslAcceptor};
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use super::{TlsError, DEFAULT_TLS_HANDSHAKE_TIMEOUT, MAX_CONN_COUNTER};
|
||||
|
||||
/// Wrapper type for `tokio_openssl::SslStream` in order to impl `ActixStream` trait.
|
||||
pub struct TlsStream<T>(tokio_openssl::SslStream<T>);
|
||||
pub mod reexports {
|
||||
//! Re-exports from `openssl` that are useful for acceptors.
|
||||
|
||||
impl<T> From<tokio_openssl::SslStream<T>> for TlsStream<T> {
|
||||
fn from(stream: tokio_openssl::SslStream<T>) -> Self {
|
||||
Self(stream)
|
||||
}
|
||||
pub use openssl::ssl::{
|
||||
AlpnError, Error, HandshakeError, Ssl, SslAcceptor, SslAcceptorBuilder,
|
||||
};
|
||||
}
|
||||
|
||||
impl<T> Deref for TlsStream<T> {
|
||||
type Target = tokio_openssl::SslStream<T>;
|
||||
/// Wraps an `openssl` based async TLS stream in order to implement [`ActixStream`].
|
||||
#[derive(Deref, DerefMut, From)]
|
||||
pub struct TlsStream<IO>(tokio_openssl::SslStream<IO>);
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for TlsStream<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ActixStream> AsyncRead for TlsStream<T> {
|
||||
impl<IO: ActixStream> AsyncRead for TlsStream<IO> {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
@ -57,7 +49,7 @@ impl<T: ActixStream> AsyncRead for TlsStream<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ActixStream> AsyncWrite for TlsStream<T> {
|
||||
impl<IO: ActixStream> AsyncWrite for TlsStream<IO> {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
@ -87,19 +79,17 @@ impl<T: ActixStream> AsyncWrite for TlsStream<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ActixStream> ActixStream for TlsStream<T> {
|
||||
impl<IO: ActixStream> ActixStream for TlsStream<IO> {
|
||||
fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
|
||||
T::poll_read_ready((&**self).get_ref(), cx)
|
||||
IO::poll_read_ready((&**self).get_ref(), cx)
|
||||
}
|
||||
|
||||
fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
|
||||
T::poll_write_ready((&**self).get_ref(), cx)
|
||||
IO::poll_write_ready((&**self).get_ref(), cx)
|
||||
}
|
||||
}
|
||||
|
||||
/// Accept TLS connections via `openssl` package.
|
||||
///
|
||||
/// `openssl` feature enables this `Acceptor` type.
|
||||
/// Accept TLS connections via the `openssl` crate.
|
||||
pub struct Acceptor {
|
||||
acceptor: SslAcceptor,
|
||||
handshake_timeout: Duration,
|
||||
@ -134,13 +124,13 @@ impl Clone for Acceptor {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ActixStream> ServiceFactory<T> for Acceptor {
|
||||
type Response = TlsStream<T>;
|
||||
type Error = TlsError<SslError, Infallible>;
|
||||
impl<IO: ActixStream> ServiceFactory<IO> for Acceptor {
|
||||
type Response = TlsStream<IO>;
|
||||
type Error = TlsError<Error, Infallible>;
|
||||
type Config = ();
|
||||
type Service = AcceptorService;
|
||||
type InitError = ();
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
||||
type Future = FutReady<Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
let res = MAX_CONN_COUNTER.with(|conns| {
|
||||
@ -151,20 +141,21 @@ impl<T: ActixStream> ServiceFactory<T> for Acceptor {
|
||||
})
|
||||
});
|
||||
|
||||
Box::pin(async { res })
|
||||
ready(res)
|
||||
}
|
||||
}
|
||||
|
||||
/// OpenSSL based acceptor service.
|
||||
pub struct AcceptorService {
|
||||
acceptor: SslAcceptor,
|
||||
conns: Counter,
|
||||
handshake_timeout: Duration,
|
||||
}
|
||||
|
||||
impl<T: ActixStream> Service<T> for AcceptorService {
|
||||
type Response = TlsStream<T>;
|
||||
type Error = TlsError<SslError, Infallible>;
|
||||
type Future = AcceptorServiceResponse<T>;
|
||||
impl<IO: ActixStream> Service<IO> for AcceptorService {
|
||||
type Response = TlsStream<IO>;
|
||||
type Error = TlsError<Error, Infallible>;
|
||||
type Future = AcceptFut<IO>;
|
||||
|
||||
fn poll_ready(&self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
if self.conns.available(ctx) {
|
||||
@ -174,11 +165,11 @@ impl<T: ActixStream> Service<T> for AcceptorService {
|
||||
}
|
||||
}
|
||||
|
||||
fn call(&self, io: T) -> Self::Future {
|
||||
fn call(&self, io: IO) -> Self::Future {
|
||||
let ssl_ctx = self.acceptor.context();
|
||||
let ssl = Ssl::new(ssl_ctx).expect("Provided SSL acceptor was invalid.");
|
||||
|
||||
AcceptorServiceResponse {
|
||||
AcceptFut {
|
||||
_guard: self.conns.get(),
|
||||
timeout: sleep(self.handshake_timeout),
|
||||
stream: Some(tokio_openssl::SslStream::new(ssl, io).unwrap()),
|
||||
@ -187,16 +178,18 @@ impl<T: ActixStream> Service<T> for AcceptorService {
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
pub struct AcceptorServiceResponse<T: ActixStream> {
|
||||
stream: Option<tokio_openssl::SslStream<T>>,
|
||||
/// Accept future for OpenSSL service.
|
||||
#[doc(hidden)]
|
||||
pub struct AcceptFut<IO: ActixStream> {
|
||||
stream: Option<tokio_openssl::SslStream<IO>>,
|
||||
#[pin]
|
||||
timeout: Sleep,
|
||||
_guard: CounterGuard,
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ActixStream> Future for AcceptorServiceResponse<T> {
|
||||
type Output = Result<TlsStream<T>, TlsError<SslError, Infallible>>;
|
||||
impl<IO: ActixStream> Future for AcceptFut<IO> {
|
||||
type Output = Result<TlsStream<IO>, TlsError<Error, Infallible>>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
|
@ -1,8 +1,11 @@
|
||||
//! `rustls` based TLS connection acceptor service.
|
||||
//!
|
||||
//! See [`Acceptor`] for main service factory docs.
|
||||
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
future::Future,
|
||||
io::{self, IoSlice},
|
||||
ops::{Deref, DerefMut},
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
@ -15,39 +18,28 @@ use actix_rt::{
|
||||
time::{sleep, Sleep},
|
||||
};
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use actix_utils::counter::{Counter, CounterGuard};
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use actix_utils::{
|
||||
counter::{Counter, CounterGuard},
|
||||
future::{ready, Ready as FutReady},
|
||||
};
|
||||
use derive_more::{Deref, DerefMut, From};
|
||||
use pin_project_lite::pin_project;
|
||||
use tokio_rustls::rustls::ServerConfig;
|
||||
use tokio_rustls::{Accept, TlsAcceptor};
|
||||
|
||||
pub use tokio_rustls::rustls::ServerConfig;
|
||||
|
||||
use super::{TlsError, DEFAULT_TLS_HANDSHAKE_TIMEOUT, MAX_CONN_COUNTER};
|
||||
|
||||
/// Wrapper type for `tokio_openssl::SslStream` in order to impl `ActixStream` trait.
|
||||
pub struct TlsStream<T>(tokio_rustls::server::TlsStream<T>);
|
||||
pub mod reexports {
|
||||
//! Re-exports from `rustls` that are useful for acceptors.
|
||||
|
||||
impl<T> From<tokio_rustls::server::TlsStream<T>> for TlsStream<T> {
|
||||
fn from(stream: tokio_rustls::server::TlsStream<T>) -> Self {
|
||||
Self(stream)
|
||||
}
|
||||
pub use tokio_rustls::rustls::ServerConfig;
|
||||
}
|
||||
|
||||
impl<T> Deref for TlsStream<T> {
|
||||
type Target = tokio_rustls::server::TlsStream<T>;
|
||||
/// Wraps a `rustls` based async TLS stream in order to implement [`ActixStream`].
|
||||
#[derive(Deref, DerefMut, From)]
|
||||
pub struct TlsStream<IO>(tokio_rustls::server::TlsStream<IO>);
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for TlsStream<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ActixStream> AsyncRead for TlsStream<T> {
|
||||
impl<IO: ActixStream> AsyncRead for TlsStream<IO> {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
@ -57,7 +49,7 @@ impl<T: ActixStream> AsyncRead for TlsStream<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ActixStream> AsyncWrite for TlsStream<T> {
|
||||
impl<IO: ActixStream> AsyncWrite for TlsStream<IO> {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
@ -87,27 +79,24 @@ impl<T: ActixStream> AsyncWrite for TlsStream<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ActixStream> ActixStream for TlsStream<T> {
|
||||
impl<IO: ActixStream> ActixStream for TlsStream<IO> {
|
||||
fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
|
||||
T::poll_read_ready((&**self).get_ref().0, cx)
|
||||
IO::poll_read_ready((&**self).get_ref().0, cx)
|
||||
}
|
||||
|
||||
fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
|
||||
T::poll_write_ready((&**self).get_ref().0, cx)
|
||||
IO::poll_write_ready((&**self).get_ref().0, cx)
|
||||
}
|
||||
}
|
||||
|
||||
/// Accept TLS connections via `rustls` package.
|
||||
///
|
||||
/// `rustls` feature enables this `Acceptor` type.
|
||||
/// Accept TLS connections via the `rustls` crate.
|
||||
pub struct Acceptor {
|
||||
config: Arc<ServerConfig>,
|
||||
handshake_timeout: Duration,
|
||||
}
|
||||
|
||||
impl Acceptor {
|
||||
/// Create Rustls based `Acceptor` service factory.
|
||||
#[inline]
|
||||
/// Constructs Rustls based acceptor service factory.
|
||||
pub fn new(config: ServerConfig) -> Self {
|
||||
Acceptor {
|
||||
config: Arc::new(config),
|
||||
@ -125,7 +114,6 @@ impl Acceptor {
|
||||
}
|
||||
|
||||
impl Clone for Acceptor {
|
||||
#[inline]
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
config: self.config.clone(),
|
||||
@ -134,13 +122,13 @@ impl Clone for Acceptor {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ActixStream> ServiceFactory<T> for Acceptor {
|
||||
type Response = TlsStream<T>;
|
||||
impl<IO: ActixStream> ServiceFactory<IO> for Acceptor {
|
||||
type Response = TlsStream<IO>;
|
||||
type Error = TlsError<io::Error, Infallible>;
|
||||
type Config = ();
|
||||
type Service = AcceptorService;
|
||||
type InitError = ();
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
||||
type Future = FutReady<Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
let res = MAX_CONN_COUNTER.with(|conns| {
|
||||
@ -151,21 +139,21 @@ impl<T: ActixStream> ServiceFactory<T> for Acceptor {
|
||||
})
|
||||
});
|
||||
|
||||
Box::pin(async { res })
|
||||
ready(res)
|
||||
}
|
||||
}
|
||||
|
||||
/// Rustls based `Acceptor` service
|
||||
/// Rustls based acceptor service.
|
||||
pub struct AcceptorService {
|
||||
acceptor: TlsAcceptor,
|
||||
conns: Counter,
|
||||
handshake_timeout: Duration,
|
||||
}
|
||||
|
||||
impl<T: ActixStream> Service<T> for AcceptorService {
|
||||
type Response = TlsStream<T>;
|
||||
impl<IO: ActixStream> Service<IO> for AcceptorService {
|
||||
type Response = TlsStream<IO>;
|
||||
type Error = TlsError<io::Error, Infallible>;
|
||||
type Future = AcceptorServiceFut<T>;
|
||||
type Future = AcceptFut<IO>;
|
||||
|
||||
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
if self.conns.available(cx) {
|
||||
@ -175,8 +163,8 @@ impl<T: ActixStream> Service<T> for AcceptorService {
|
||||
}
|
||||
}
|
||||
|
||||
fn call(&self, req: T) -> Self::Future {
|
||||
AcceptorServiceFut {
|
||||
fn call(&self, req: IO) -> Self::Future {
|
||||
AcceptFut {
|
||||
fut: self.acceptor.accept(req),
|
||||
timeout: sleep(self.handshake_timeout),
|
||||
_guard: self.conns.get(),
|
||||
@ -185,16 +173,18 @@ impl<T: ActixStream> Service<T> for AcceptorService {
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
pub struct AcceptorServiceFut<T: ActixStream> {
|
||||
fut: Accept<T>,
|
||||
/// Accept future for Rustls service.
|
||||
#[doc(hidden)]
|
||||
pub struct AcceptFut<IO: ActixStream> {
|
||||
fut: Accept<IO>,
|
||||
#[pin]
|
||||
timeout: Sleep,
|
||||
_guard: CounterGuard,
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ActixStream> Future for AcceptorServiceFut<T> {
|
||||
type Output = Result<TlsStream<T>, TlsError<io::Error, Infallible>>;
|
||||
impl<IO: ActixStream> Future for AcceptFut<IO> {
|
||||
type Output = Result<TlsStream<IO>, TlsError<io::Error, Infallible>>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let mut this = self.project();
|
||||
|
@ -1,361 +0,0 @@
|
||||
use std::{
|
||||
collections::{vec_deque, VecDeque},
|
||||
fmt,
|
||||
iter::{self, FromIterator as _},
|
||||
mem,
|
||||
net::{IpAddr, SocketAddr},
|
||||
};
|
||||
|
||||
/// Parse a host into parts (hostname and port).
|
||||
pub trait Address: Unpin + 'static {
|
||||
/// Get hostname part.
|
||||
fn hostname(&self) -> &str;
|
||||
|
||||
/// Get optional port part.
|
||||
fn port(&self) -> Option<u16> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Address for String {
|
||||
fn hostname(&self) -> &str {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Address for &'static str {
|
||||
fn hostname(&self) -> &str {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Hash)]
|
||||
pub(crate) enum ConnectAddrs {
|
||||
None,
|
||||
One(SocketAddr),
|
||||
Multi(VecDeque<SocketAddr>),
|
||||
}
|
||||
|
||||
impl ConnectAddrs {
|
||||
pub(crate) fn is_none(&self) -> bool {
|
||||
matches!(self, Self::None)
|
||||
}
|
||||
|
||||
pub(crate) fn is_some(&self) -> bool {
|
||||
!self.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ConnectAddrs {
|
||||
fn default() -> Self {
|
||||
Self::None
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<SocketAddr>> for ConnectAddrs {
|
||||
fn from(addr: Option<SocketAddr>) -> Self {
|
||||
match addr {
|
||||
Some(addr) => ConnectAddrs::One(addr),
|
||||
None => ConnectAddrs::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Connection info.
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Connect<R> {
|
||||
pub(crate) req: R,
|
||||
pub(crate) port: u16,
|
||||
pub(crate) addr: ConnectAddrs,
|
||||
pub(crate) local_addr: Option<IpAddr>,
|
||||
}
|
||||
|
||||
impl<R: Address> Connect<R> {
|
||||
/// Create `Connect` instance by splitting the string by ':' and convert the second part to u16
|
||||
pub fn new(req: R) -> Connect<R> {
|
||||
let (_, port) = parse_host(req.hostname());
|
||||
|
||||
Connect {
|
||||
req,
|
||||
port: port.unwrap_or(0),
|
||||
addr: ConnectAddrs::None,
|
||||
local_addr: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create new `Connect` instance from host and address. Connector skips name resolution stage
|
||||
/// for such connect messages.
|
||||
pub fn with_addr(req: R, addr: SocketAddr) -> Connect<R> {
|
||||
Connect {
|
||||
req,
|
||||
port: 0,
|
||||
addr: ConnectAddrs::One(addr),
|
||||
local_addr: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Use port if address does not provide one.
|
||||
///
|
||||
/// Default value is 0.
|
||||
pub fn set_port(mut self, port: u16) -> Self {
|
||||
self.port = port;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set address.
|
||||
pub fn set_addr(mut self, addr: Option<SocketAddr>) -> Self {
|
||||
self.addr = ConnectAddrs::from(addr);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set list of addresses.
|
||||
pub fn set_addrs<I>(mut self, addrs: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = SocketAddr>,
|
||||
{
|
||||
let mut addrs = VecDeque::from_iter(addrs);
|
||||
self.addr = if addrs.len() < 2 {
|
||||
ConnectAddrs::from(addrs.pop_front())
|
||||
} else {
|
||||
ConnectAddrs::Multi(addrs)
|
||||
};
|
||||
self
|
||||
}
|
||||
|
||||
/// Set local_addr of connect.
|
||||
pub fn set_local_addr(mut self, addr: impl Into<IpAddr>) -> Self {
|
||||
self.local_addr = Some(addr.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Get hostname.
|
||||
pub fn hostname(&self) -> &str {
|
||||
self.req.hostname()
|
||||
}
|
||||
|
||||
/// Get request port.
|
||||
pub fn port(&self) -> u16 {
|
||||
self.req.port().unwrap_or(self.port)
|
||||
}
|
||||
|
||||
/// Get resolved request addresses.
|
||||
pub fn addrs(&self) -> ConnectAddrsIter<'_> {
|
||||
match self.addr {
|
||||
ConnectAddrs::None => ConnectAddrsIter::None,
|
||||
ConnectAddrs::One(addr) => ConnectAddrsIter::One(addr),
|
||||
ConnectAddrs::Multi(ref addrs) => ConnectAddrsIter::Multi(addrs.iter()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Take resolved request addresses.
|
||||
pub fn take_addrs(&mut self) -> ConnectAddrsIter<'static> {
|
||||
match mem::take(&mut self.addr) {
|
||||
ConnectAddrs::None => ConnectAddrsIter::None,
|
||||
ConnectAddrs::One(addr) => ConnectAddrsIter::One(addr),
|
||||
ConnectAddrs::Multi(addrs) => ConnectAddrsIter::MultiOwned(addrs.into_iter()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the connection request.
|
||||
pub fn request(&self) -> &R {
|
||||
&self.req
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Address> From<R> for Connect<R> {
|
||||
fn from(addr: R) -> Self {
|
||||
Connect::new(addr)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Address> fmt::Display for Connect<R> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}:{}", self.hostname(), self.port())
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator over addresses in a [`Connect`] request.
|
||||
#[derive(Clone)]
|
||||
pub enum ConnectAddrsIter<'a> {
|
||||
None,
|
||||
One(SocketAddr),
|
||||
Multi(vec_deque::Iter<'a, SocketAddr>),
|
||||
MultiOwned(vec_deque::IntoIter<SocketAddr>),
|
||||
}
|
||||
|
||||
impl Iterator for ConnectAddrsIter<'_> {
|
||||
type Item = SocketAddr;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match *self {
|
||||
Self::None => None,
|
||||
Self::One(addr) => {
|
||||
*self = Self::None;
|
||||
Some(addr)
|
||||
}
|
||||
Self::Multi(ref mut iter) => iter.next().copied(),
|
||||
Self::MultiOwned(ref mut iter) => iter.next(),
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
match *self {
|
||||
Self::None => (0, Some(0)),
|
||||
Self::One(_) => (1, Some(1)),
|
||||
Self::Multi(ref iter) => iter.size_hint(),
|
||||
Self::MultiOwned(ref iter) => iter.size_hint(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ConnectAddrsIter<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_list().entries(self.clone()).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl iter::ExactSizeIterator for ConnectAddrsIter<'_> {}
|
||||
|
||||
impl iter::FusedIterator for ConnectAddrsIter<'_> {}
|
||||
|
||||
pub struct Connection<T, U> {
|
||||
io: U,
|
||||
req: T,
|
||||
}
|
||||
|
||||
impl<T, U> Connection<T, U> {
|
||||
pub fn new(io: U, req: T) -> Self {
|
||||
Self { io, req }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Connection<T, U> {
|
||||
/// Reconstruct from a parts.
|
||||
pub fn from_parts(io: U, req: T) -> Self {
|
||||
Self { io, req }
|
||||
}
|
||||
|
||||
/// Deconstruct into a parts.
|
||||
pub fn into_parts(self) -> (U, T) {
|
||||
(self.io, self.req)
|
||||
}
|
||||
|
||||
/// Replace inclosed object, return new Stream and old object
|
||||
pub fn replace_io<Y>(self, io: Y) -> (U, Connection<T, Y>) {
|
||||
(self.io, Connection { io, req: self.req })
|
||||
}
|
||||
|
||||
/// Returns a shared reference to the underlying stream.
|
||||
pub fn io_ref(&self) -> &U {
|
||||
&self.io
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying stream.
|
||||
pub fn io_mut(&mut self) -> &mut U {
|
||||
&mut self.io
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Address, U> Connection<T, U> {
|
||||
/// Get hostname.
|
||||
pub fn host(&self) -> &str {
|
||||
self.req.hostname()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> std::ops::Deref for Connection<T, U> {
|
||||
type Target = U;
|
||||
|
||||
fn deref(&self) -> &U {
|
||||
&self.io
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> std::ops::DerefMut for Connection<T, U> {
|
||||
fn deref_mut(&mut self) -> &mut U {
|
||||
&mut self.io
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U: fmt::Debug> fmt::Debug for Connection<T, U> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Stream {{{:?}}}", self.io)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_host(host: &str) -> (&str, Option<u16>) {
|
||||
let mut parts_iter = host.splitn(2, ':');
|
||||
|
||||
match parts_iter.next() {
|
||||
Some(hostname) => {
|
||||
let port_str = parts_iter.next().unwrap_or("");
|
||||
let port = port_str.parse::<u16>().ok();
|
||||
(hostname, port)
|
||||
}
|
||||
|
||||
None => (host, None),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_host_parser() {
|
||||
assert_eq!(parse_host("example.com"), ("example.com", None));
|
||||
assert_eq!(parse_host("example.com:8080"), ("example.com", Some(8080)));
|
||||
assert_eq!(parse_host("example:8080"), ("example", Some(8080)));
|
||||
assert_eq!(parse_host("example.com:false"), ("example.com", None));
|
||||
assert_eq!(parse_host("example.com:false:false"), ("example.com", None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_addr_iter_multi() {
|
||||
let localhost = SocketAddr::from((IpAddr::from(Ipv4Addr::LOCALHOST), 8080));
|
||||
let unspecified = SocketAddr::from((IpAddr::from(Ipv4Addr::UNSPECIFIED), 8080));
|
||||
|
||||
let mut addrs = VecDeque::new();
|
||||
addrs.push_back(localhost);
|
||||
addrs.push_back(unspecified);
|
||||
|
||||
let mut iter = ConnectAddrsIter::Multi(addrs.iter());
|
||||
assert_eq!(iter.next(), Some(localhost));
|
||||
assert_eq!(iter.next(), Some(unspecified));
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
let mut iter = ConnectAddrsIter::MultiOwned(addrs.into_iter());
|
||||
assert_eq!(iter.next(), Some(localhost));
|
||||
assert_eq!(iter.next(), Some(unspecified));
|
||||
assert_eq!(iter.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_addr_iter_single() {
|
||||
let localhost = SocketAddr::from((IpAddr::from(Ipv4Addr::LOCALHOST), 8080));
|
||||
|
||||
let mut iter = ConnectAddrsIter::One(localhost);
|
||||
assert_eq!(iter.next(), Some(localhost));
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
let mut iter = ConnectAddrsIter::None;
|
||||
assert_eq!(iter.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_local_addr() {
|
||||
let conn = Connect::new("hello").set_local_addr([127, 0, 0, 1]);
|
||||
assert_eq!(
|
||||
conn.local_addr.unwrap(),
|
||||
IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_ref() {
|
||||
let conn = Connect::new("hello");
|
||||
assert_eq!(conn.request(), &"hello")
|
||||
}
|
||||
}
|
82
actix-tls/src/connect/connect_addrs.rs
Normal file
82
actix-tls/src/connect/connect_addrs.rs
Normal file
@ -0,0 +1,82 @@
|
||||
use std::{
|
||||
collections::{vec_deque, VecDeque},
|
||||
fmt, iter,
|
||||
net::SocketAddr,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub(crate) enum ConnectAddrs {
|
||||
None,
|
||||
One(SocketAddr),
|
||||
// TODO: consider using smallvec
|
||||
Multi(VecDeque<SocketAddr>),
|
||||
}
|
||||
|
||||
impl ConnectAddrs {
|
||||
pub(crate) fn is_unresolved(&self) -> bool {
|
||||
matches!(self, Self::None)
|
||||
}
|
||||
|
||||
pub(crate) fn is_resolved(&self) -> bool {
|
||||
!self.is_unresolved()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ConnectAddrs {
|
||||
fn default() -> Self {
|
||||
Self::None
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<SocketAddr>> for ConnectAddrs {
|
||||
fn from(addr: Option<SocketAddr>) -> Self {
|
||||
match addr {
|
||||
Some(addr) => ConnectAddrs::One(addr),
|
||||
None => ConnectAddrs::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator over addresses in a [`Connect`] request.
|
||||
#[derive(Clone)]
|
||||
pub(crate) enum ConnectAddrsIter<'a> {
|
||||
None,
|
||||
One(SocketAddr),
|
||||
Multi(vec_deque::Iter<'a, SocketAddr>),
|
||||
MultiOwned(vec_deque::IntoIter<SocketAddr>),
|
||||
}
|
||||
|
||||
impl Iterator for ConnectAddrsIter<'_> {
|
||||
type Item = SocketAddr;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match *self {
|
||||
Self::None => None,
|
||||
Self::One(addr) => {
|
||||
*self = Self::None;
|
||||
Some(addr)
|
||||
}
|
||||
Self::Multi(ref mut iter) => iter.next().copied(),
|
||||
Self::MultiOwned(ref mut iter) => iter.next(),
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
match *self {
|
||||
Self::None => (0, Some(0)),
|
||||
Self::One(_) => (1, Some(1)),
|
||||
Self::Multi(ref iter) => iter.size_hint(),
|
||||
Self::MultiOwned(ref iter) => iter.size_hint(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ConnectAddrsIter<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_list().entries(self.clone()).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl iter::ExactSizeIterator for ConnectAddrsIter<'_> {}
|
||||
|
||||
impl iter::FusedIterator for ConnectAddrsIter<'_> {}
|
54
actix-tls/src/connect/connection.rs
Normal file
54
actix-tls/src/connect/connection.rs
Normal file
@ -0,0 +1,54 @@
|
||||
use derive_more::{Deref, DerefMut};
|
||||
|
||||
use super::Host;
|
||||
|
||||
/// Wraps underlying I/O and the connection request that initiated it.
|
||||
#[derive(Debug, Deref, DerefMut)]
|
||||
pub struct Connection<R, IO> {
|
||||
pub(crate) req: R,
|
||||
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
pub(crate) io: IO,
|
||||
}
|
||||
|
||||
impl<R, IO> Connection<R, IO> {
|
||||
/// Construct new `Connection` from request and IO parts.
|
||||
pub(crate) fn new(req: R, io: IO) -> Self {
|
||||
Self { req, io }
|
||||
}
|
||||
}
|
||||
|
||||
impl<R, IO> Connection<R, IO> {
|
||||
/// Deconstructs into IO and request parts.
|
||||
pub fn into_parts(self) -> (IO, R) {
|
||||
(self.io, self.req)
|
||||
}
|
||||
|
||||
/// Replaces underlying IO, returning old IO and new `Connection`.
|
||||
pub fn replace_io<IO2>(self, io: IO2) -> (IO, Connection<R, IO2>) {
|
||||
(self.io, Connection { io, req: self.req })
|
||||
}
|
||||
|
||||
/// Returns a shared reference to the underlying IO.
|
||||
pub fn io_ref(&self) -> &IO {
|
||||
&self.io
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying IO.
|
||||
pub fn io_mut(&mut self) -> &mut IO {
|
||||
&mut self.io
|
||||
}
|
||||
|
||||
/// Returns a reference to the connection request.
|
||||
pub fn request(&self) -> &R {
|
||||
&self.req
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Host, IO> Connection<R, IO> {
|
||||
/// Returns hostname.
|
||||
pub fn hostname(&self) -> &str {
|
||||
self.req.hostname()
|
||||
}
|
||||
}
|
229
actix-tls/src/connect/connector.rs
Executable file → Normal file
229
actix-tls/src/connect/connector.rs
Executable file → Normal file
@ -1,194 +1,127 @@
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
future::Future,
|
||||
io,
|
||||
net::{IpAddr, SocketAddr, SocketAddrV4, SocketAddrV6},
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use actix_rt::net::{TcpSocket, TcpStream};
|
||||
use actix_rt::net::TcpStream;
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use futures_core::{future::LocalBoxFuture, ready};
|
||||
use log::{error, trace};
|
||||
use tokio_util::sync::ReusableBoxFuture;
|
||||
use actix_utils::future::{ok, Ready};
|
||||
use futures_core::ready;
|
||||
|
||||
use super::connect::{Address, Connect, ConnectAddrs, Connection};
|
||||
use super::error::ConnectError;
|
||||
use super::{
|
||||
error::ConnectError,
|
||||
resolver::{Resolver, ResolverService},
|
||||
tcp::{TcpConnector, TcpConnectorService},
|
||||
ConnectInfo, Connection, Host,
|
||||
};
|
||||
|
||||
/// TCP connector service factory
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct TcpConnectorFactory;
|
||||
/// Combined resolver and TCP connector service factory.
|
||||
///
|
||||
/// Used to create [`ConnectorService`]s which receive connection information, resolve DNS if
|
||||
/// required, and return a TCP stream.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Connector {
|
||||
resolver: Resolver,
|
||||
}
|
||||
|
||||
impl TcpConnectorFactory {
|
||||
/// Create TCP connector service
|
||||
pub fn service(&self) -> TcpConnector {
|
||||
TcpConnector
|
||||
impl Connector {
|
||||
/// Constructs new connector factory with the given resolver.
|
||||
pub fn new(resolver: Resolver) -> Self {
|
||||
Connector { resolver }
|
||||
}
|
||||
|
||||
/// Build connector service.
|
||||
pub fn service(&self) -> ConnectorService {
|
||||
ConnectorService {
|
||||
tcp: TcpConnector.service(),
|
||||
resolver: self.resolver.service(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Address> ServiceFactory<Connect<T>> for TcpConnectorFactory {
|
||||
type Response = Connection<T, TcpStream>;
|
||||
impl<R: Host> ServiceFactory<ConnectInfo<R>> for Connector {
|
||||
type Response = Connection<R, TcpStream>;
|
||||
type Error = ConnectError;
|
||||
type Config = ();
|
||||
type Service = TcpConnector;
|
||||
type Service = ConnectorService;
|
||||
type InitError = ();
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
||||
type Future = Ready<Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
let service = self.service();
|
||||
Box::pin(async move { Ok(service) })
|
||||
ok(self.service())
|
||||
}
|
||||
}
|
||||
|
||||
/// TCP connector service
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct TcpConnector;
|
||||
/// Combined resolver and TCP connector service.
|
||||
///
|
||||
/// Service implementation receives connection information, resolves DNS if required, and returns
|
||||
/// a TCP stream.
|
||||
#[derive(Clone)]
|
||||
pub struct ConnectorService {
|
||||
tcp: TcpConnectorService,
|
||||
resolver: ResolverService,
|
||||
}
|
||||
|
||||
impl<T: Address> Service<Connect<T>> for TcpConnector {
|
||||
type Response = Connection<T, TcpStream>;
|
||||
impl<R: Host> Service<ConnectInfo<R>> for ConnectorService {
|
||||
type Response = Connection<R, TcpStream>;
|
||||
type Error = ConnectError;
|
||||
type Future = TcpConnectorResponse<T>;
|
||||
type Future = ConnectServiceResponse<R>;
|
||||
|
||||
actix_service::always_ready!();
|
||||
|
||||
fn call(&self, req: Connect<T>) -> Self::Future {
|
||||
let port = req.port();
|
||||
let Connect {
|
||||
req,
|
||||
addr,
|
||||
local_addr,
|
||||
..
|
||||
} = req;
|
||||
|
||||
TcpConnectorResponse::new(req, port, local_addr, addr)
|
||||
fn call(&self, req: ConnectInfo<R>) -> Self::Future {
|
||||
ConnectServiceResponse {
|
||||
fut: ConnectFut::Resolve(self.resolver.call(req)),
|
||||
tcp: self.tcp,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// TCP stream connector response future
|
||||
pub enum TcpConnectorResponse<T> {
|
||||
Response {
|
||||
req: Option<T>,
|
||||
port: u16,
|
||||
local_addr: Option<IpAddr>,
|
||||
addrs: Option<VecDeque<SocketAddr>>,
|
||||
stream: ReusableBoxFuture<Result<TcpStream, io::Error>>,
|
||||
},
|
||||
Error(Option<ConnectError>),
|
||||
/// Helper enum to generic over futures of resolve and connect steps.
|
||||
pub(crate) enum ConnectFut<R: Host> {
|
||||
Resolve(<ResolverService as Service<ConnectInfo<R>>>::Future),
|
||||
Connect(<TcpConnectorService as Service<ConnectInfo<R>>>::Future),
|
||||
}
|
||||
|
||||
impl<T: Address> TcpConnectorResponse<T> {
|
||||
pub(crate) fn new(
|
||||
req: T,
|
||||
port: u16,
|
||||
local_addr: Option<IpAddr>,
|
||||
addr: ConnectAddrs,
|
||||
) -> TcpConnectorResponse<T> {
|
||||
if addr.is_none() {
|
||||
error!("TCP connector: unresolved connection address");
|
||||
return TcpConnectorResponse::Error(Some(ConnectError::Unresolved));
|
||||
/// Helper enum to contain the future output of `ConnectFuture`.
|
||||
pub(crate) enum ConnectOutput<R: Host> {
|
||||
Resolved(ConnectInfo<R>),
|
||||
Connected(Connection<R, TcpStream>),
|
||||
}
|
||||
|
||||
trace!(
|
||||
"TCP connector: connecting to {} on port {}",
|
||||
req.hostname(),
|
||||
port
|
||||
);
|
||||
|
||||
match addr {
|
||||
ConnectAddrs::None => unreachable!("none variant already checked"),
|
||||
|
||||
ConnectAddrs::One(addr) => TcpConnectorResponse::Response {
|
||||
req: Some(req),
|
||||
port,
|
||||
local_addr,
|
||||
addrs: None,
|
||||
stream: ReusableBoxFuture::new(connect(addr, local_addr)),
|
||||
},
|
||||
|
||||
// when resolver returns multiple socket addr for request they would be popped from
|
||||
// front end of queue and returns with the first successful tcp connection.
|
||||
ConnectAddrs::Multi(mut addrs) => {
|
||||
let addr = addrs.pop_front().unwrap();
|
||||
|
||||
TcpConnectorResponse::Response {
|
||||
req: Some(req),
|
||||
port,
|
||||
local_addr,
|
||||
addrs: Some(addrs),
|
||||
stream: ReusableBoxFuture::new(connect(addr, local_addr)),
|
||||
impl<R: Host> ConnectFut<R> {
|
||||
fn poll_connect(
|
||||
&mut self,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Result<ConnectOutput<R>, ConnectError>> {
|
||||
match self {
|
||||
ConnectFut::Resolve(ref mut fut) => {
|
||||
Pin::new(fut).poll(cx).map_ok(ConnectOutput::Resolved)
|
||||
}
|
||||
ConnectFut::Connect(ref mut fut) => {
|
||||
Pin::new(fut).poll(cx).map_ok(ConnectOutput::Connected)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Address> Future for TcpConnectorResponse<T> {
|
||||
type Output = Result<Connection<T, TcpStream>, ConnectError>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
match self.get_mut() {
|
||||
TcpConnectorResponse::Error(err) => Poll::Ready(Err(err.take().unwrap())),
|
||||
|
||||
TcpConnectorResponse::Response {
|
||||
req,
|
||||
port,
|
||||
local_addr,
|
||||
addrs,
|
||||
stream,
|
||||
} => loop {
|
||||
match ready!(stream.poll(cx)) {
|
||||
Ok(sock) => {
|
||||
let req = req.take().unwrap();
|
||||
trace!(
|
||||
"TCP connector: successfully connected to {:?} - {:?}",
|
||||
req.hostname(),
|
||||
sock.peer_addr()
|
||||
);
|
||||
return Poll::Ready(Ok(Connection::new(sock, req)));
|
||||
pub struct ConnectServiceResponse<R: Host> {
|
||||
fut: ConnectFut<R>,
|
||||
tcp: TcpConnectorService,
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
trace!(
|
||||
"TCP connector: failed to connect to {:?} port: {}",
|
||||
req.as_ref().unwrap().hostname(),
|
||||
port,
|
||||
);
|
||||
impl<R: Host> Future for ConnectServiceResponse<R> {
|
||||
type Output = Result<Connection<R, TcpStream>, ConnectError>;
|
||||
|
||||
if let Some(addr) = addrs.as_mut().and_then(|addrs| addrs.pop_front()) {
|
||||
stream.set(connect(addr, *local_addr));
|
||||
} else {
|
||||
return Poll::Ready(Err(ConnectError::Io(err)));
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
loop {
|
||||
match ready!(self.fut.poll_connect(cx))? {
|
||||
ConnectOutput::Resolved(res) => {
|
||||
self.fut = ConnectFut::Connect(self.tcp.call(res));
|
||||
}
|
||||
ConnectOutput::Connected(res) => return Poll::Ready(Ok(res)),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn connect(addr: SocketAddr, local_addr: Option<IpAddr>) -> io::Result<TcpStream> {
|
||||
// use local addr if connect asks for it.
|
||||
match local_addr {
|
||||
Some(ip_addr) => {
|
||||
let socket = match ip_addr {
|
||||
IpAddr::V4(ip_addr) => {
|
||||
let socket = TcpSocket::new_v4()?;
|
||||
let addr = SocketAddr::V4(SocketAddrV4::new(ip_addr, 0));
|
||||
socket.bind(addr)?;
|
||||
socket
|
||||
}
|
||||
IpAddr::V6(ip_addr) => {
|
||||
let socket = TcpSocket::new_v6()?;
|
||||
let addr = SocketAddr::V6(SocketAddrV6::new(ip_addr, 0, 0, 0));
|
||||
socket.bind(addr)?;
|
||||
socket
|
||||
}
|
||||
};
|
||||
|
||||
socket.connect(addr).await
|
||||
}
|
||||
|
||||
None => TcpStream::connect(addr).await,
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,16 @@
|
||||
use std::io;
|
||||
use std::{error::Error, io};
|
||||
|
||||
use derive_more::Display;
|
||||
|
||||
/// Errors that can result from using a connector service.
|
||||
#[derive(Debug, Display)]
|
||||
pub enum ConnectError {
|
||||
/// Failed to resolve the hostname
|
||||
#[display(fmt = "Failed resolving hostname: {}", _0)]
|
||||
#[display(fmt = "Failed resolving hostname")]
|
||||
Resolver(Box<dyn std::error::Error>),
|
||||
|
||||
/// No dns records
|
||||
#[display(fmt = "No dns records found for the input")]
|
||||
/// No DNS records
|
||||
#[display(fmt = "No DNS records found for the input")]
|
||||
NoRecords,
|
||||
|
||||
/// Invalid input
|
||||
@ -23,3 +24,13 @@ pub enum ConnectError {
|
||||
#[display(fmt = "{}", _0)]
|
||||
Io(io::Error),
|
||||
}
|
||||
|
||||
impl Error for ConnectError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
match self {
|
||||
Self::Resolver(err) => Some(&**err),
|
||||
Self::Io(err) => Some(err),
|
||||
Self::NoRecords | Self::InvalidInput | Self::Unresolved => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
71
actix-tls/src/connect/host.rs
Normal file
71
actix-tls/src/connect/host.rs
Normal file
@ -0,0 +1,71 @@
|
||||
//! The [`Host`] trait.
|
||||
|
||||
/// An interface for types where host parts (hostname and port) can be derived.
|
||||
///
|
||||
/// The [WHATWG URL Standard] defines the terminology used for this trait and its methods.
|
||||
///
|
||||
/// ```plain
|
||||
/// +------------------------+
|
||||
/// | host |
|
||||
/// +-----------------+------+
|
||||
/// | hostname | port |
|
||||
/// | | |
|
||||
/// | sub.example.com : 8080 |
|
||||
/// +-----------------+------+
|
||||
/// ```
|
||||
///
|
||||
/// [WHATWG URL Standard]: https://url.spec.whatwg.org/
|
||||
pub trait Host: Unpin + 'static {
|
||||
/// Extract hostname.
|
||||
fn hostname(&self) -> &str;
|
||||
|
||||
/// Extract optional port.
|
||||
fn port(&self) -> Option<u16> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Host for String {
|
||||
fn hostname(&self) -> &str {
|
||||
self.split_once(':')
|
||||
.map(|(hostname, _)| hostname)
|
||||
.unwrap_or(self)
|
||||
}
|
||||
|
||||
fn port(&self) -> Option<u16> {
|
||||
self.split_once(':').and_then(|(_, port)| port.parse().ok())
|
||||
}
|
||||
}
|
||||
|
||||
impl Host for &'static str {
|
||||
fn hostname(&self) -> &str {
|
||||
self.split_once(':')
|
||||
.map(|(hostname, _)| hostname)
|
||||
.unwrap_or(self)
|
||||
}
|
||||
|
||||
fn port(&self) -> Option<u16> {
|
||||
self.split_once(':').and_then(|(_, port)| port.parse().ok())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
macro_rules! assert_connection_info_eq {
|
||||
($req:expr, $hostname:expr, $port:expr) => {{
|
||||
assert_eq!($req.hostname(), $hostname);
|
||||
assert_eq!($req.port(), $port);
|
||||
}};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn host_parsing() {
|
||||
assert_connection_info_eq!("example.com", "example.com", None);
|
||||
assert_connection_info_eq!("example.com:8080", "example.com", Some(8080));
|
||||
assert_connection_info_eq!("example:8080", "example", Some(8080));
|
||||
assert_connection_info_eq!("example.com:false", "example.com", None);
|
||||
assert_connection_info_eq!("example.com:false:false", "example.com", None);
|
||||
}
|
||||
}
|
249
actix-tls/src/connect/info.rs
Normal file
249
actix-tls/src/connect/info.rs
Normal file
@ -0,0 +1,249 @@
|
||||
//! Connection info struct.
|
||||
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
fmt,
|
||||
iter::{self, FromIterator as _},
|
||||
mem,
|
||||
net::{IpAddr, SocketAddr},
|
||||
};
|
||||
|
||||
use super::{
|
||||
connect_addrs::{ConnectAddrs, ConnectAddrsIter},
|
||||
Host,
|
||||
};
|
||||
|
||||
/// Connection request information.
|
||||
///
|
||||
/// May contain known/pre-resolved socket address(es) or a host that needs resolving with DNS.
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ConnectInfo<R> {
|
||||
pub(crate) request: R,
|
||||
pub(crate) port: u16,
|
||||
pub(crate) addr: ConnectAddrs,
|
||||
pub(crate) local_addr: Option<IpAddr>,
|
||||
}
|
||||
|
||||
impl<R: Host> ConnectInfo<R> {
|
||||
/// Constructs new connection info using a request.
|
||||
pub fn new(request: R) -> ConnectInfo<R> {
|
||||
let port = request.port();
|
||||
|
||||
ConnectInfo {
|
||||
request,
|
||||
port: port.unwrap_or(0),
|
||||
addr: ConnectAddrs::None,
|
||||
local_addr: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs new connection info from request and known socket address.
|
||||
///
|
||||
/// Since socket address is known, [`Connector`](super::Connector) will skip the DNS
|
||||
/// resolution step.
|
||||
pub fn with_addr(request: R, addr: SocketAddr) -> ConnectInfo<R> {
|
||||
ConnectInfo {
|
||||
request,
|
||||
port: 0,
|
||||
addr: ConnectAddrs::One(addr),
|
||||
local_addr: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set connection port.
|
||||
///
|
||||
/// If request provided a port, this will override it.
|
||||
pub fn set_port(mut self, port: u16) -> Self {
|
||||
self.port = port;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set connection socket address.
|
||||
pub fn set_addr(mut self, addr: impl Into<Option<SocketAddr>>) -> Self {
|
||||
self.addr = ConnectAddrs::from(addr.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set list of addresses.
|
||||
pub fn set_addrs<I>(mut self, addrs: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = SocketAddr>,
|
||||
{
|
||||
let mut addrs = VecDeque::from_iter(addrs);
|
||||
self.addr = if addrs.len() < 2 {
|
||||
ConnectAddrs::from(addrs.pop_front())
|
||||
} else {
|
||||
ConnectAddrs::Multi(addrs)
|
||||
};
|
||||
self
|
||||
}
|
||||
|
||||
/// Set local address to connection with.
|
||||
///
|
||||
/// Useful in situations where the IP address bound to a particular network interface is known.
|
||||
/// This would make sure the socket is opened through that interface.
|
||||
pub fn set_local_addr(mut self, addr: impl Into<IpAddr>) -> Self {
|
||||
self.local_addr = Some(addr.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns a reference to the connection request.
|
||||
pub fn request(&self) -> &R {
|
||||
&self.request
|
||||
}
|
||||
|
||||
/// Returns request hostname.
|
||||
pub fn hostname(&self) -> &str {
|
||||
self.request.hostname()
|
||||
}
|
||||
|
||||
/// Returns request port.
|
||||
pub fn port(&self) -> u16 {
|
||||
self.request.port().unwrap_or(self.port)
|
||||
}
|
||||
|
||||
/// Get borrowed iterator of resolved request addresses.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use std::net::SocketAddr;
|
||||
/// # use actix_tls::connect::ConnectInfo;
|
||||
/// let addr = SocketAddr::from(([127, 0, 0, 1], 4242));
|
||||
///
|
||||
/// let conn = ConnectInfo::new("localhost");
|
||||
/// let mut addrs = conn.addrs();
|
||||
/// assert!(addrs.next().is_none());
|
||||
///
|
||||
/// let conn = ConnectInfo::with_addr("localhost", addr);
|
||||
/// let mut addrs = conn.addrs();
|
||||
/// assert_eq!(addrs.next().unwrap(), addr);
|
||||
/// ```
|
||||
pub fn addrs(
|
||||
&self,
|
||||
) -> impl Iterator<Item = SocketAddr>
|
||||
+ ExactSizeIterator
|
||||
+ iter::FusedIterator
|
||||
+ Clone
|
||||
+ fmt::Debug
|
||||
+ '_ {
|
||||
match self.addr {
|
||||
ConnectAddrs::None => ConnectAddrsIter::None,
|
||||
ConnectAddrs::One(addr) => ConnectAddrsIter::One(addr),
|
||||
ConnectAddrs::Multi(ref addrs) => ConnectAddrsIter::Multi(addrs.iter()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Take owned iterator resolved request addresses.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use std::net::SocketAddr;
|
||||
/// # use actix_tls::connect::ConnectInfo;
|
||||
/// let addr = SocketAddr::from(([127, 0, 0, 1], 4242));
|
||||
///
|
||||
/// let mut conn = ConnectInfo::new("localhost");
|
||||
/// let mut addrs = conn.take_addrs();
|
||||
/// assert!(addrs.next().is_none());
|
||||
///
|
||||
/// let mut conn = ConnectInfo::with_addr("localhost", addr);
|
||||
/// let mut addrs = conn.take_addrs();
|
||||
/// assert_eq!(addrs.next().unwrap(), addr);
|
||||
/// ```
|
||||
pub fn take_addrs(
|
||||
&mut self,
|
||||
) -> impl Iterator<Item = SocketAddr>
|
||||
+ ExactSizeIterator
|
||||
+ iter::FusedIterator
|
||||
+ Clone
|
||||
+ fmt::Debug
|
||||
+ 'static {
|
||||
match mem::take(&mut self.addr) {
|
||||
ConnectAddrs::None => ConnectAddrsIter::None,
|
||||
ConnectAddrs::One(addr) => ConnectAddrsIter::One(addr),
|
||||
ConnectAddrs::Multi(addrs) => ConnectAddrsIter::MultiOwned(addrs.into_iter()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Host> From<R> for ConnectInfo<R> {
|
||||
fn from(addr: R) -> Self {
|
||||
ConnectInfo::new(addr)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Host> fmt::Display for ConnectInfo<R> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}:{}", self.hostname(), self.port())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_addr_iter_multi() {
|
||||
let localhost = SocketAddr::from((IpAddr::from(Ipv4Addr::LOCALHOST), 8080));
|
||||
let unspecified = SocketAddr::from((IpAddr::from(Ipv4Addr::UNSPECIFIED), 8080));
|
||||
|
||||
let mut addrs = VecDeque::new();
|
||||
addrs.push_back(localhost);
|
||||
addrs.push_back(unspecified);
|
||||
|
||||
let mut iter = ConnectAddrsIter::Multi(addrs.iter());
|
||||
assert_eq!(iter.next(), Some(localhost));
|
||||
assert_eq!(iter.next(), Some(unspecified));
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
let mut iter = ConnectAddrsIter::MultiOwned(addrs.into_iter());
|
||||
assert_eq!(iter.next(), Some(localhost));
|
||||
assert_eq!(iter.next(), Some(unspecified));
|
||||
assert_eq!(iter.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_addr_iter_single() {
|
||||
let localhost = SocketAddr::from((IpAddr::from(Ipv4Addr::LOCALHOST), 8080));
|
||||
|
||||
let mut iter = ConnectAddrsIter::One(localhost);
|
||||
assert_eq!(iter.next(), Some(localhost));
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
let mut iter = ConnectAddrsIter::None;
|
||||
assert_eq!(iter.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_local_addr() {
|
||||
let conn = ConnectInfo::new("hello").set_local_addr([127, 0, 0, 1]);
|
||||
assert_eq!(
|
||||
conn.local_addr.unwrap(),
|
||||
IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_ref() {
|
||||
let conn = ConnectInfo::new("hello");
|
||||
assert_eq!(conn.request(), &"hello")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_connect_addr_into_option() {
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], 4242));
|
||||
|
||||
let conn = ConnectInfo::new("hello").set_addr(None);
|
||||
let mut addrs = conn.addrs();
|
||||
assert!(addrs.next().is_none());
|
||||
|
||||
let conn = ConnectInfo::new("hello").set_addr(addr);
|
||||
let mut addrs = conn.addrs();
|
||||
assert_eq!(addrs.next().unwrap(), addr);
|
||||
|
||||
let conn = ConnectInfo::new("hello").set_addr(Some(addr));
|
||||
let mut addrs = conn.addrs();
|
||||
assert_eq!(addrs.next().unwrap(), addr);
|
||||
}
|
||||
}
|
@ -1,35 +1,46 @@
|
||||
//! TCP and TLS connector services.
|
||||
//!
|
||||
//! # Stages of the TCP connector service:
|
||||
//! - Resolve [`Address`] with given [`Resolver`] and collect list of socket addresses.
|
||||
//! - Establish TCP connection and return [`TcpStream`].
|
||||
//! 1. Resolve [`Host`] (if needed) with given [`Resolver`] and collect list of socket addresses.
|
||||
//! 1. Establish TCP connection and return [`TcpStream`].
|
||||
//!
|
||||
//! # Stages of TLS connector services:
|
||||
//! - Establish [`TcpStream`] with connector service.
|
||||
//! - Wrap the stream and perform connect handshake with remote peer.
|
||||
//! - Return certain stream type that impls `AsyncRead` and `AsyncWrite`.
|
||||
//! 1. Resolve DNS and establish a [`TcpStream`] with the TCP connector service.
|
||||
//! 1. Wrap the stream and perform connect handshake with remote peer.
|
||||
//! 1. Return wrapped stream type that implements `AsyncRead` and `AsyncWrite`.
|
||||
//!
|
||||
//! [`TcpStream`]: actix_rt::net::TcpStream
|
||||
|
||||
#[allow(clippy::module_inception)]
|
||||
mod connect;
|
||||
mod connect_addrs;
|
||||
mod connection;
|
||||
mod connector;
|
||||
mod error;
|
||||
mod host;
|
||||
mod info;
|
||||
mod resolve;
|
||||
mod service;
|
||||
pub mod tls;
|
||||
// TODO: remove `ssl` mod re-export in next break change
|
||||
#[doc(hidden)]
|
||||
pub use tls as ssl;
|
||||
mod tcp;
|
||||
mod resolver;
|
||||
pub mod tcp;
|
||||
|
||||
#[cfg(feature = "uri")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "uri")))]
|
||||
mod uri;
|
||||
|
||||
pub use self::connect::{Address, Connect, Connection};
|
||||
pub use self::connector::{TcpConnector, TcpConnectorFactory};
|
||||
#[cfg(feature = "openssl")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "openssl")))]
|
||||
pub mod openssl;
|
||||
|
||||
#[cfg(feature = "rustls")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
|
||||
pub mod rustls;
|
||||
|
||||
#[cfg(feature = "native-tls")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
|
||||
pub mod native_tls;
|
||||
|
||||
pub use self::connection::Connection;
|
||||
pub use self::connector::{Connector, ConnectorService};
|
||||
pub use self::error::ConnectError;
|
||||
pub use self::resolve::{Resolve, Resolver, ResolverFactory};
|
||||
pub use self::service::{ConnectService, ConnectServiceFactory};
|
||||
pub use self::tcp::{
|
||||
default_connector, default_connector_factory, new_connector, new_connector_factory,
|
||||
};
|
||||
pub use self::host::Host;
|
||||
pub use self::info::ConnectInfo;
|
||||
pub use self::resolve::Resolve;
|
||||
pub use self::resolver::{Resolver, ResolverService};
|
||||
|
90
actix-tls/src/connect/native_tls.rs
Normal file
90
actix-tls/src/connect/native_tls.rs
Normal file
@ -0,0 +1,90 @@
|
||||
//! Native-TLS based connector service.
|
||||
//!
|
||||
//! See [`TlsConnector`] for main connector service factory docs.
|
||||
|
||||
use std::io;
|
||||
|
||||
use actix_rt::net::ActixStream;
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use actix_utils::future::{ok, Ready};
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use log::trace;
|
||||
use tokio_native_tls::{
|
||||
native_tls::TlsConnector as NativeTlsConnector, TlsConnector as TokioNativeTlsConnector,
|
||||
TlsStream,
|
||||
};
|
||||
|
||||
use crate::connect::{Connection, Host};
|
||||
|
||||
pub mod reexports {
|
||||
//! Re-exports from `native-tls` that are useful for connectors.
|
||||
|
||||
pub use tokio_native_tls::native_tls::TlsConnector;
|
||||
}
|
||||
|
||||
/// Connector service and factory using `native-tls`.
|
||||
#[derive(Clone)]
|
||||
pub struct TlsConnector {
|
||||
connector: TokioNativeTlsConnector,
|
||||
}
|
||||
|
||||
impl TlsConnector {
|
||||
/// Constructs new connector service from a `native-tls` connector.
|
||||
///
|
||||
/// This type is it's own service factory, so it can be used in that setting, too.
|
||||
pub fn new(connector: NativeTlsConnector) -> Self {
|
||||
Self {
|
||||
connector: TokioNativeTlsConnector::from(connector),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Host, IO> ServiceFactory<Connection<R, IO>> for TlsConnector
|
||||
where
|
||||
IO: ActixStream + 'static,
|
||||
{
|
||||
type Response = Connection<R, TlsStream<IO>>;
|
||||
type Error = io::Error;
|
||||
type Config = ();
|
||||
type Service = Self;
|
||||
type InitError = ();
|
||||
type Future = Ready<Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
ok(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// The `native-tls` connector is both it's ServiceFactory and Service impl type.
|
||||
/// As the factory and service share the same type and state.
|
||||
impl<R, IO> Service<Connection<R, IO>> for TlsConnector
|
||||
where
|
||||
R: Host,
|
||||
IO: ActixStream + 'static,
|
||||
{
|
||||
type Response = Connection<R, TlsStream<IO>>;
|
||||
type Error = io::Error;
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||
|
||||
actix_service::always_ready!();
|
||||
|
||||
fn call(&self, stream: Connection<R, IO>) -> Self::Future {
|
||||
let (io, stream) = stream.replace_io(());
|
||||
let connector = self.connector.clone();
|
||||
|
||||
Box::pin(async move {
|
||||
trace!("SSL Handshake start for: {:?}", stream.hostname());
|
||||
connector
|
||||
.connect(stream.hostname(), io)
|
||||
.await
|
||||
.map(|res| {
|
||||
trace!("SSL Handshake success: {:?}", stream.hostname());
|
||||
stream.replace_io(res).1
|
||||
})
|
||||
.map_err(|e| {
|
||||
trace!("SSL Handshake error: {:?}", e);
|
||||
io::Error::new(io::ErrorKind::Other, format!("{}", e))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
146
actix-tls/src/connect/openssl.rs
Normal file
146
actix-tls/src/connect/openssl.rs
Normal file
@ -0,0 +1,146 @@
|
||||
//! OpenSSL based connector service.
|
||||
//!
|
||||
//! See [`TlsConnector`] for main connector service factory docs.
|
||||
|
||||
use std::{
|
||||
future::Future,
|
||||
io,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use actix_rt::net::ActixStream;
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use actix_utils::future::{ok, Ready};
|
||||
use futures_core::ready;
|
||||
use log::trace;
|
||||
use openssl::ssl::SslConnector;
|
||||
use tokio_openssl::SslStream;
|
||||
|
||||
use crate::connect::{Connection, Host};
|
||||
|
||||
pub mod reexports {
|
||||
//! Re-exports from `openssl` that are useful for connectors.
|
||||
|
||||
pub use openssl::ssl::{Error as SslError, HandshakeError, SslConnector, SslMethod};
|
||||
}
|
||||
|
||||
/// Connector service factory using `openssl`.
|
||||
pub struct TlsConnector {
|
||||
connector: SslConnector,
|
||||
}
|
||||
|
||||
impl TlsConnector {
|
||||
/// Constructs new connector service factory from an `openssl` connector.
|
||||
pub fn new(connector: SslConnector) -> Self {
|
||||
TlsConnector { connector }
|
||||
}
|
||||
|
||||
/// Constructs new connector service from an `openssl` connector.
|
||||
pub fn service(connector: SslConnector) -> TlsConnectorService {
|
||||
TlsConnectorService { connector }
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for TlsConnector {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
connector: self.connector.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R, IO> ServiceFactory<Connection<R, IO>> for TlsConnector
|
||||
where
|
||||
R: Host,
|
||||
IO: ActixStream + 'static,
|
||||
{
|
||||
type Response = Connection<R, SslStream<IO>>;
|
||||
type Error = io::Error;
|
||||
type Config = ();
|
||||
type Service = TlsConnectorService;
|
||||
type InitError = ();
|
||||
type Future = Ready<Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
ok(TlsConnectorService {
|
||||
connector: self.connector.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Connector service using `openssl`.
|
||||
pub struct TlsConnectorService {
|
||||
connector: SslConnector,
|
||||
}
|
||||
|
||||
impl Clone for TlsConnectorService {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
connector: self.connector.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R, IO> Service<Connection<R, IO>> for TlsConnectorService
|
||||
where
|
||||
R: Host,
|
||||
IO: ActixStream,
|
||||
{
|
||||
type Response = Connection<R, SslStream<IO>>;
|
||||
type Error = io::Error;
|
||||
type Future = ConnectFut<R, IO>;
|
||||
|
||||
actix_service::always_ready!();
|
||||
|
||||
fn call(&self, stream: Connection<R, IO>) -> Self::Future {
|
||||
trace!("SSL Handshake start for: {:?}", stream.hostname());
|
||||
let (io, stream) = stream.replace_io(());
|
||||
let host = stream.hostname();
|
||||
|
||||
let config = self
|
||||
.connector
|
||||
.configure()
|
||||
.expect("SSL connect configuration was invalid.");
|
||||
|
||||
let ssl = config
|
||||
.into_ssl(host)
|
||||
.expect("SSL connect configuration was invalid.");
|
||||
|
||||
ConnectFut {
|
||||
io: Some(SslStream::new(ssl, io).unwrap()),
|
||||
stream: Some(stream),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Connect future for OpenSSL service.
|
||||
#[doc(hidden)]
|
||||
pub struct ConnectFut<R, IO> {
|
||||
io: Option<SslStream<IO>>,
|
||||
stream: Option<Connection<R, ()>>,
|
||||
}
|
||||
|
||||
impl<R: Host, IO> Future for ConnectFut<R, IO>
|
||||
where
|
||||
R: Host,
|
||||
IO: ActixStream,
|
||||
{
|
||||
type Output = Result<Connection<R, SslStream<IO>>, io::Error>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.get_mut();
|
||||
|
||||
match ready!(Pin::new(this.io.as_mut().unwrap()).poll_connect(cx)) {
|
||||
Ok(_) => {
|
||||
let stream = this.stream.take().unwrap();
|
||||
trace!("SSL Handshake success: {:?}", stream.hostname());
|
||||
Poll::Ready(Ok(stream.replace_io(this.io.take().unwrap()).1))
|
||||
}
|
||||
Err(e) => {
|
||||
trace!("SSL Handshake error: {:?}", e);
|
||||
Poll::Ready(Err(io::Error::new(io::ErrorKind::Other, format!("{}", e))))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
207
actix-tls/src/connect/resolve.rs
Executable file → Normal file
207
actix-tls/src/connect/resolve.rs
Executable file → Normal file
@ -1,61 +1,12 @@
|
||||
use std::{
|
||||
future::Future,
|
||||
io,
|
||||
net::SocketAddr,
|
||||
pin::Pin,
|
||||
rc::Rc,
|
||||
task::{Context, Poll},
|
||||
vec::IntoIter,
|
||||
};
|
||||
//! The [`Resolve`] trait.
|
||||
|
||||
use actix_rt::task::{spawn_blocking, JoinHandle};
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use futures_core::{future::LocalBoxFuture, ready};
|
||||
use log::trace;
|
||||
use std::{error::Error as StdError, net::SocketAddr};
|
||||
|
||||
use super::connect::{Address, Connect};
|
||||
use super::error::ConnectError;
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
|
||||
/// DNS Resolver Service Factory
|
||||
#[derive(Clone)]
|
||||
pub struct ResolverFactory {
|
||||
resolver: Resolver,
|
||||
}
|
||||
|
||||
impl ResolverFactory {
|
||||
pub fn new(resolver: Resolver) -> Self {
|
||||
Self { resolver }
|
||||
}
|
||||
|
||||
pub fn service(&self) -> Resolver {
|
||||
self.resolver.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Address> ServiceFactory<Connect<T>> for ResolverFactory {
|
||||
type Response = Connect<T>;
|
||||
type Error = ConnectError;
|
||||
type Config = ();
|
||||
type Service = Resolver;
|
||||
type InitError = ();
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
let service = self.resolver.clone();
|
||||
Box::pin(async { Ok(service) })
|
||||
}
|
||||
}
|
||||
|
||||
/// DNS Resolver Service
|
||||
#[derive(Clone)]
|
||||
pub enum Resolver {
|
||||
Default,
|
||||
Custom(Rc<dyn Resolve>),
|
||||
}
|
||||
|
||||
/// An interface for custom async DNS resolvers.
|
||||
/// Custom async DNS resolvers.
|
||||
///
|
||||
/// # Usage
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use std::net::SocketAddr;
|
||||
///
|
||||
@ -89,155 +40,23 @@ pub enum Resolver {
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let resolver = MyResolver {
|
||||
/// let my_resolver = MyResolver {
|
||||
/// trust_dns: TokioAsyncResolver::tokio_from_system_conf().unwrap(),
|
||||
/// };
|
||||
///
|
||||
/// // construct custom resolver
|
||||
/// let resolver = Resolver::new_custom(resolver);
|
||||
///
|
||||
/// // pass custom resolver to connector builder.
|
||||
/// // connector would then be usable as a service or awc's connector.
|
||||
/// let connector = actix_tls::connect::new_connector::<&str>(resolver.clone());
|
||||
/// // wrap custom resolver
|
||||
/// let resolver = Resolver::custom(my_resolver);
|
||||
///
|
||||
/// // resolver can be passed to connector factory where returned service factory
|
||||
/// // can be used to construct new connector services.
|
||||
/// let factory = actix_tls::connect::new_connector_factory::<&str>(resolver);
|
||||
/// // can be used to construct new connector services for use in clients
|
||||
/// let factory = actix_tls::connect::Connector::new(resolver);
|
||||
/// let connector = factory.service();
|
||||
/// ```
|
||||
pub trait Resolve {
|
||||
/// Given DNS lookup information, returns a future that completes with socket information.
|
||||
fn lookup<'a>(
|
||||
&'a self,
|
||||
host: &'a str,
|
||||
port: u16,
|
||||
) -> LocalBoxFuture<'a, Result<Vec<SocketAddr>, Box<dyn std::error::Error>>>;
|
||||
}
|
||||
|
||||
impl Resolver {
|
||||
/// Constructor for custom Resolve trait object and use it as resolver.
|
||||
pub fn new_custom(resolver: impl Resolve + 'static) -> Self {
|
||||
Self::Custom(Rc::new(resolver))
|
||||
}
|
||||
|
||||
// look up with default resolver variant.
|
||||
fn look_up<T: Address>(req: &Connect<T>) -> JoinHandle<io::Result<IntoIter<SocketAddr>>> {
|
||||
let host = req.hostname();
|
||||
// TODO: Connect should always return host with port if possible.
|
||||
let host = if req
|
||||
.hostname()
|
||||
.splitn(2, ':')
|
||||
.last()
|
||||
.and_then(|p| p.parse::<u16>().ok())
|
||||
.map(|p| p == req.port())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
host.to_string()
|
||||
} else {
|
||||
format!("{}:{}", host, req.port())
|
||||
};
|
||||
|
||||
// run blocking DNS lookup in thread pool
|
||||
spawn_blocking(move || std::net::ToSocketAddrs::to_socket_addrs(&host))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Address> Service<Connect<T>> for Resolver {
|
||||
type Response = Connect<T>;
|
||||
type Error = ConnectError;
|
||||
type Future = ResolverFuture<T>;
|
||||
|
||||
actix_service::always_ready!();
|
||||
|
||||
fn call(&self, req: Connect<T>) -> Self::Future {
|
||||
if req.addr.is_some() {
|
||||
ResolverFuture::Connected(Some(req))
|
||||
} else if let Ok(ip) = req.hostname().parse() {
|
||||
let addr = SocketAddr::new(ip, req.port());
|
||||
let req = req.set_addr(Some(addr));
|
||||
ResolverFuture::Connected(Some(req))
|
||||
} else {
|
||||
trace!("DNS resolver: resolving host {:?}", req.hostname());
|
||||
|
||||
match self {
|
||||
Self::Default => {
|
||||
let fut = Self::look_up(&req);
|
||||
ResolverFuture::LookUp(fut, Some(req))
|
||||
}
|
||||
|
||||
Self::Custom(resolver) => {
|
||||
let resolver = Rc::clone(resolver);
|
||||
ResolverFuture::LookupCustom(Box::pin(async move {
|
||||
let addrs = resolver
|
||||
.lookup(req.hostname(), req.port())
|
||||
.await
|
||||
.map_err(ConnectError::Resolver)?;
|
||||
|
||||
let req = req.set_addrs(addrs);
|
||||
|
||||
if req.addr.is_none() {
|
||||
Err(ConnectError::NoRecords)
|
||||
} else {
|
||||
Ok(req)
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ResolverFuture<T: Address> {
|
||||
Connected(Option<Connect<T>>),
|
||||
LookUp(
|
||||
JoinHandle<io::Result<IntoIter<SocketAddr>>>,
|
||||
Option<Connect<T>>,
|
||||
),
|
||||
LookupCustom(LocalBoxFuture<'static, Result<Connect<T>, ConnectError>>),
|
||||
}
|
||||
|
||||
impl<T: Address> Future for ResolverFuture<T> {
|
||||
type Output = Result<Connect<T>, ConnectError>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
match self.get_mut() {
|
||||
Self::Connected(conn) => Poll::Ready(Ok(conn
|
||||
.take()
|
||||
.expect("ResolverFuture polled after finished"))),
|
||||
|
||||
Self::LookUp(fut, req) => {
|
||||
let res = match ready!(Pin::new(fut).poll(cx)) {
|
||||
Ok(Ok(res)) => Ok(res),
|
||||
Ok(Err(e)) => Err(ConnectError::Resolver(Box::new(e))),
|
||||
Err(e) => Err(ConnectError::Io(e.into())),
|
||||
};
|
||||
|
||||
let req = req.take().unwrap();
|
||||
|
||||
let addrs = res.map_err(|err| {
|
||||
trace!(
|
||||
"DNS resolver: failed to resolve host {:?} err: {:?}",
|
||||
req.hostname(),
|
||||
err
|
||||
);
|
||||
|
||||
err
|
||||
})?;
|
||||
|
||||
let req = req.set_addrs(addrs);
|
||||
|
||||
trace!(
|
||||
"DNS resolver: host {:?} resolved to {:?}",
|
||||
req.hostname(),
|
||||
req.addrs()
|
||||
);
|
||||
|
||||
if req.addr.is_none() {
|
||||
Poll::Ready(Err(ConnectError::NoRecords))
|
||||
} else {
|
||||
Poll::Ready(Ok(req))
|
||||
}
|
||||
}
|
||||
|
||||
Self::LookupCustom(fut) => fut.as_mut().poll(cx),
|
||||
}
|
||||
}
|
||||
) -> LocalBoxFuture<'a, Result<Vec<SocketAddr>, Box<dyn StdError>>>;
|
||||
}
|
||||
|
201
actix-tls/src/connect/resolver.rs
Normal file
201
actix-tls/src/connect/resolver.rs
Normal file
@ -0,0 +1,201 @@
|
||||
use std::{
|
||||
future::Future,
|
||||
io,
|
||||
net::SocketAddr,
|
||||
pin::Pin,
|
||||
rc::Rc,
|
||||
task::{Context, Poll},
|
||||
vec::IntoIter,
|
||||
};
|
||||
|
||||
use actix_rt::task::{spawn_blocking, JoinHandle};
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use actix_utils::future::{ok, Ready};
|
||||
use futures_core::{future::LocalBoxFuture, ready};
|
||||
use log::trace;
|
||||
|
||||
use super::{ConnectError, ConnectInfo, Host, Resolve};
|
||||
|
||||
/// DNS resolver service factory.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Resolver {
|
||||
resolver: ResolverService,
|
||||
}
|
||||
|
||||
impl Resolver {
|
||||
/// Constructs a new resolver factory with a custom resolver.
|
||||
pub fn custom(resolver: impl Resolve + 'static) -> Self {
|
||||
Self {
|
||||
resolver: ResolverService::custom(resolver),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new resolver service.
|
||||
pub fn service(&self) -> ResolverService {
|
||||
self.resolver.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Host> ServiceFactory<ConnectInfo<R>> for Resolver {
|
||||
type Response = ConnectInfo<R>;
|
||||
type Error = ConnectError;
|
||||
type Config = ();
|
||||
type Service = ResolverService;
|
||||
type InitError = ();
|
||||
type Future = Ready<Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
ok(self.resolver.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum ResolverKind {
|
||||
/// Built-in DNS resolver.
|
||||
///
|
||||
/// See [`std::net::ToSocketAddrs`] trait.
|
||||
Default,
|
||||
|
||||
/// Custom, user-provided DNS resolver.
|
||||
Custom(Rc<dyn Resolve>),
|
||||
}
|
||||
|
||||
impl Default for ResolverKind {
|
||||
fn default() -> Self {
|
||||
Self::Default
|
||||
}
|
||||
}
|
||||
|
||||
/// DNS resolver service.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct ResolverService {
|
||||
kind: ResolverKind,
|
||||
}
|
||||
|
||||
impl ResolverService {
|
||||
/// Constructor for custom Resolve trait object and use it as resolver.
|
||||
pub fn custom(resolver: impl Resolve + 'static) -> Self {
|
||||
Self {
|
||||
kind: ResolverKind::Custom(Rc::new(resolver)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve DNS with default resolver.
|
||||
fn default_lookup<R: Host>(
|
||||
req: &ConnectInfo<R>,
|
||||
) -> JoinHandle<io::Result<IntoIter<SocketAddr>>> {
|
||||
// reconstruct host; concatenate hostname and port together
|
||||
let host = format!("{}:{}", req.hostname(), req.port());
|
||||
|
||||
// run blocking DNS lookup in thread pool since DNS lookups can take upwards of seconds on
|
||||
// some platforms if conditions are poor and OS-level cache is not populated
|
||||
spawn_blocking(move || std::net::ToSocketAddrs::to_socket_addrs(&host))
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Host> Service<ConnectInfo<R>> for ResolverService {
|
||||
type Response = ConnectInfo<R>;
|
||||
type Error = ConnectError;
|
||||
type Future = ResolverFut<R>;
|
||||
|
||||
actix_service::always_ready!();
|
||||
|
||||
fn call(&self, req: ConnectInfo<R>) -> Self::Future {
|
||||
if req.addr.is_resolved() {
|
||||
// socket address(es) already resolved; return existing connection request
|
||||
ResolverFut::Resolved(Some(req))
|
||||
} else if let Ok(ip) = req.hostname().parse() {
|
||||
// request hostname is valid ip address; add address to request and return
|
||||
let addr = SocketAddr::new(ip, req.port());
|
||||
let req = req.set_addr(Some(addr));
|
||||
ResolverFut::Resolved(Some(req))
|
||||
} else {
|
||||
trace!("DNS resolver: resolving host {:?}", req.hostname());
|
||||
|
||||
match &self.kind {
|
||||
ResolverKind::Default => {
|
||||
let fut = Self::default_lookup(&req);
|
||||
ResolverFut::LookUp(fut, Some(req))
|
||||
}
|
||||
|
||||
ResolverKind::Custom(resolver) => {
|
||||
let resolver = Rc::clone(resolver);
|
||||
|
||||
ResolverFut::LookupCustom(Box::pin(async move {
|
||||
let addrs = resolver
|
||||
.lookup(req.hostname(), req.port())
|
||||
.await
|
||||
.map_err(ConnectError::Resolver)?;
|
||||
|
||||
let req = req.set_addrs(addrs);
|
||||
|
||||
if req.addr.is_unresolved() {
|
||||
Err(ConnectError::NoRecords)
|
||||
} else {
|
||||
Ok(req)
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Future for resolver service.
|
||||
#[doc(hidden)]
|
||||
pub enum ResolverFut<R: Host> {
|
||||
Resolved(Option<ConnectInfo<R>>),
|
||||
LookUp(
|
||||
JoinHandle<io::Result<IntoIter<SocketAddr>>>,
|
||||
Option<ConnectInfo<R>>,
|
||||
),
|
||||
LookupCustom(LocalBoxFuture<'static, Result<ConnectInfo<R>, ConnectError>>),
|
||||
}
|
||||
|
||||
impl<R: Host> Future for ResolverFut<R> {
|
||||
type Output = Result<ConnectInfo<R>, ConnectError>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
match self.get_mut() {
|
||||
Self::Resolved(conn) => Poll::Ready(Ok(conn
|
||||
.take()
|
||||
.expect("ResolverFuture polled after finished"))),
|
||||
|
||||
Self::LookUp(fut, req) => {
|
||||
let res = match ready!(Pin::new(fut).poll(cx)) {
|
||||
Ok(Ok(res)) => Ok(res),
|
||||
Ok(Err(e)) => Err(ConnectError::Resolver(Box::new(e))),
|
||||
Err(e) => Err(ConnectError::Io(e.into())),
|
||||
};
|
||||
|
||||
let req = req.take().unwrap();
|
||||
|
||||
let addrs = res.map_err(|err| {
|
||||
trace!(
|
||||
"DNS resolver: failed to resolve host {:?} err: {:?}",
|
||||
req.hostname(),
|
||||
err
|
||||
);
|
||||
|
||||
err
|
||||
})?;
|
||||
|
||||
let req = req.set_addrs(addrs);
|
||||
|
||||
trace!(
|
||||
"DNS resolver: host {:?} resolved to {:?}",
|
||||
req.hostname(),
|
||||
req.addrs()
|
||||
);
|
||||
|
||||
if req.addr.is_unresolved() {
|
||||
Poll::Ready(Err(ConnectError::NoRecords))
|
||||
} else {
|
||||
Poll::Ready(Ok(req))
|
||||
}
|
||||
}
|
||||
|
||||
Self::LookupCustom(fut) => fut.as_mut().poll(cx),
|
||||
}
|
||||
}
|
||||
}
|
148
actix-tls/src/connect/rustls.rs
Normal file
148
actix-tls/src/connect/rustls.rs
Normal file
@ -0,0 +1,148 @@
|
||||
//! Rustls based connector service.
|
||||
//!
|
||||
//! See [`TlsConnector`] for main connector service factory docs.
|
||||
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
future::Future,
|
||||
io,
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use actix_rt::net::ActixStream;
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use actix_utils::future::{ok, Ready};
|
||||
use futures_core::ready;
|
||||
use log::trace;
|
||||
use tokio_rustls::rustls::{client::ServerName, OwnedTrustAnchor, RootCertStore};
|
||||
use tokio_rustls::{client::TlsStream, rustls::ClientConfig};
|
||||
use tokio_rustls::{Connect as RustlsConnect, TlsConnector as RustlsTlsConnector};
|
||||
use webpki_roots::TLS_SERVER_ROOTS;
|
||||
|
||||
use crate::connect::{Connection, Host};
|
||||
|
||||
pub mod reexports {
|
||||
//! Re-exports from `rustls` and `webpki_roots` that are useful for connectors.
|
||||
|
||||
pub use tokio_rustls::{client::TlsStream, rustls::ClientConfig};
|
||||
|
||||
pub use webpki_roots::TLS_SERVER_ROOTS;
|
||||
}
|
||||
|
||||
/// Returns standard root certificates from `webpki-roots` crate as a rustls certificate store.
|
||||
pub fn webpki_roots_cert_store() -> RootCertStore {
|
||||
let mut root_certs = RootCertStore::empty();
|
||||
for cert in TLS_SERVER_ROOTS.0 {
|
||||
let cert = OwnedTrustAnchor::from_subject_spki_name_constraints(
|
||||
cert.subject,
|
||||
cert.spki,
|
||||
cert.name_constraints,
|
||||
);
|
||||
let certs = vec![cert].into_iter();
|
||||
root_certs.add_server_trust_anchors(certs);
|
||||
}
|
||||
root_certs
|
||||
}
|
||||
|
||||
/// Connector service factory using `rustls`.
|
||||
#[derive(Clone)]
|
||||
pub struct TlsConnector {
|
||||
connector: Arc<ClientConfig>,
|
||||
}
|
||||
|
||||
impl TlsConnector {
|
||||
/// Constructs new connector service factory from a `rustls` client configuration.
|
||||
pub fn new(connector: Arc<ClientConfig>) -> Self {
|
||||
TlsConnector { connector }
|
||||
}
|
||||
|
||||
/// Constructs new connector service from a `rustls` client configuration.
|
||||
pub fn service(connector: Arc<ClientConfig>) -> TlsConnectorService {
|
||||
TlsConnectorService { connector }
|
||||
}
|
||||
}
|
||||
|
||||
impl<R, IO> ServiceFactory<Connection<R, IO>> for TlsConnector
|
||||
where
|
||||
R: Host,
|
||||
IO: ActixStream + 'static,
|
||||
{
|
||||
type Response = Connection<R, TlsStream<IO>>;
|
||||
type Error = io::Error;
|
||||
type Config = ();
|
||||
type Service = TlsConnectorService;
|
||||
type InitError = ();
|
||||
type Future = Ready<Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
ok(TlsConnectorService {
|
||||
connector: self.connector.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Connector service using `rustls`.
|
||||
#[derive(Clone)]
|
||||
pub struct TlsConnectorService {
|
||||
connector: Arc<ClientConfig>,
|
||||
}
|
||||
|
||||
impl<R, IO> Service<Connection<R, IO>> for TlsConnectorService
|
||||
where
|
||||
R: Host,
|
||||
IO: ActixStream,
|
||||
{
|
||||
type Response = Connection<R, TlsStream<IO>>;
|
||||
type Error = io::Error;
|
||||
type Future = ConnectFut<R, IO>;
|
||||
|
||||
actix_service::always_ready!();
|
||||
|
||||
fn call(&self, connection: Connection<R, IO>) -> Self::Future {
|
||||
trace!("SSL Handshake start for: {:?}", connection.hostname());
|
||||
let (stream, connection) = connection.replace_io(());
|
||||
|
||||
match ServerName::try_from(connection.hostname()) {
|
||||
Ok(host) => ConnectFut::Future {
|
||||
connect: RustlsTlsConnector::from(self.connector.clone()).connect(host, stream),
|
||||
connection: Some(connection),
|
||||
},
|
||||
Err(_) => ConnectFut::InvalidDns,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Connect future for Rustls service.
|
||||
#[doc(hidden)]
|
||||
pub enum ConnectFut<R, IO> {
|
||||
/// See issue <https://github.com/briansmith/webpki/issues/54>
|
||||
InvalidDns,
|
||||
Future {
|
||||
connect: RustlsConnect<IO>,
|
||||
connection: Option<Connection<R, ()>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<R, IO> Future for ConnectFut<R, IO>
|
||||
where
|
||||
R: Host,
|
||||
IO: ActixStream,
|
||||
{
|
||||
type Output = Result<Connection<R, TlsStream<IO>>, io::Error>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
match self.get_mut() {
|
||||
Self::InvalidDns => Poll::Ready(Err(
|
||||
io::Error::new(io::ErrorKind::Other, "rustls currently only handles hostname-based connections. See https://github.com/briansmith/webpki/issues/54")
|
||||
)),
|
||||
Self::Future { connect, connection } => {
|
||||
let stream = ready!(Pin::new(connect).poll(cx))?;
|
||||
let connection = connection.take().unwrap();
|
||||
trace!("SSL Handshake success: {:?}", connection.hostname());
|
||||
Poll::Ready(Ok(connection.replace_io(stream).1))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,129 +0,0 @@
|
||||
use std::{
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use actix_rt::net::TcpStream;
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use futures_core::{future::LocalBoxFuture, ready};
|
||||
|
||||
use super::connect::{Address, Connect, Connection};
|
||||
use super::connector::{TcpConnector, TcpConnectorFactory};
|
||||
use super::error::ConnectError;
|
||||
use super::resolve::{Resolver, ResolverFactory};
|
||||
|
||||
pub struct ConnectServiceFactory {
|
||||
tcp: TcpConnectorFactory,
|
||||
resolver: ResolverFactory,
|
||||
}
|
||||
|
||||
impl ConnectServiceFactory {
|
||||
/// Construct new ConnectService factory
|
||||
pub fn new(resolver: Resolver) -> Self {
|
||||
ConnectServiceFactory {
|
||||
tcp: TcpConnectorFactory,
|
||||
resolver: ResolverFactory::new(resolver),
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct new service
|
||||
pub fn service(&self) -> ConnectService {
|
||||
ConnectService {
|
||||
tcp: self.tcp.service(),
|
||||
resolver: self.resolver.service(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for ConnectServiceFactory {
|
||||
fn clone(&self) -> Self {
|
||||
ConnectServiceFactory {
|
||||
tcp: self.tcp,
|
||||
resolver: self.resolver.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Address> ServiceFactory<Connect<T>> for ConnectServiceFactory {
|
||||
type Response = Connection<T, TcpStream>;
|
||||
type Error = ConnectError;
|
||||
type Config = ();
|
||||
type Service = ConnectService;
|
||||
type InitError = ();
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
let service = self.service();
|
||||
Box::pin(async { Ok(service) })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ConnectService {
|
||||
tcp: TcpConnector,
|
||||
resolver: Resolver,
|
||||
}
|
||||
|
||||
impl<T: Address> Service<Connect<T>> for ConnectService {
|
||||
type Response = Connection<T, TcpStream>;
|
||||
type Error = ConnectError;
|
||||
type Future = ConnectServiceResponse<T>;
|
||||
|
||||
actix_service::always_ready!();
|
||||
|
||||
fn call(&self, req: Connect<T>) -> Self::Future {
|
||||
ConnectServiceResponse {
|
||||
fut: ConnectFuture::Resolve(self.resolver.call(req)),
|
||||
tcp: self.tcp,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// helper enum to generic over futures of resolve and connect phase.
|
||||
pub(crate) enum ConnectFuture<T: Address> {
|
||||
Resolve(<Resolver as Service<Connect<T>>>::Future),
|
||||
Connect(<TcpConnector as Service<Connect<T>>>::Future),
|
||||
}
|
||||
|
||||
// helper enum to contain the future output of ConnectFuture
|
||||
pub(crate) enum ConnectOutput<T: Address> {
|
||||
Resolved(Connect<T>),
|
||||
Connected(Connection<T, TcpStream>),
|
||||
}
|
||||
|
||||
impl<T: Address> ConnectFuture<T> {
|
||||
fn poll_connect(
|
||||
&mut self,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Result<ConnectOutput<T>, ConnectError>> {
|
||||
match self {
|
||||
ConnectFuture::Resolve(ref mut fut) => {
|
||||
Pin::new(fut).poll(cx).map_ok(ConnectOutput::Resolved)
|
||||
}
|
||||
ConnectFuture::Connect(ref mut fut) => {
|
||||
Pin::new(fut).poll(cx).map_ok(ConnectOutput::Connected)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConnectServiceResponse<T: Address> {
|
||||
fut: ConnectFuture<T>,
|
||||
tcp: TcpConnector,
|
||||
}
|
||||
|
||||
impl<T: Address> Future for ConnectServiceResponse<T> {
|
||||
type Output = Result<Connection<T, TcpStream>, ConnectError>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
loop {
|
||||
match ready!(self.fut.poll_connect(cx))? {
|
||||
ConnectOutput::Resolved(res) => {
|
||||
self.fut = ConnectFuture::Connect(self.tcp.call(res));
|
||||
}
|
||||
ConnectOutput::Connected(res) => return Poll::Ready(Ok(res)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,43 +1,202 @@
|
||||
use actix_rt::net::TcpStream;
|
||||
//! TCP connector service.
|
||||
//!
|
||||
//! See [`TcpConnector`] for main connector service factory docs.
|
||||
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
future::Future,
|
||||
io,
|
||||
net::{IpAddr, SocketAddr, SocketAddrV4, SocketAddrV6},
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use actix_rt::net::{TcpSocket, TcpStream};
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use actix_utils::future::{ok, Ready};
|
||||
use futures_core::ready;
|
||||
use log::{error, trace};
|
||||
use tokio_util::sync::ReusableBoxFuture;
|
||||
|
||||
use super::{Address, Connect, ConnectError, ConnectServiceFactory, Connection, Resolver};
|
||||
use super::{connect_addrs::ConnectAddrs, error::ConnectError, ConnectInfo, Connection, Host};
|
||||
|
||||
/// Create TCP connector service.
|
||||
pub fn new_connector<T: Address + 'static>(
|
||||
resolver: Resolver,
|
||||
) -> impl Service<Connect<T>, Response = Connection<T, TcpStream>, Error = ConnectError> + Clone
|
||||
{
|
||||
ConnectServiceFactory::new(resolver).service()
|
||||
/// TCP connector service factory.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct TcpConnector;
|
||||
|
||||
impl TcpConnector {
|
||||
/// Returns a new TCP connector service.
|
||||
pub fn service(&self) -> TcpConnectorService {
|
||||
TcpConnectorService
|
||||
}
|
||||
}
|
||||
|
||||
/// Create TCP connector service factory.
|
||||
pub fn new_connector_factory<T: Address + 'static>(
|
||||
resolver: Resolver,
|
||||
) -> impl ServiceFactory<
|
||||
Connect<T>,
|
||||
Config = (),
|
||||
Response = Connection<T, TcpStream>,
|
||||
Error = ConnectError,
|
||||
InitError = (),
|
||||
> + Clone {
|
||||
ConnectServiceFactory::new(resolver)
|
||||
impl<R: Host> ServiceFactory<ConnectInfo<R>> for TcpConnector {
|
||||
type Response = Connection<R, TcpStream>;
|
||||
type Error = ConnectError;
|
||||
type Config = ();
|
||||
type Service = TcpConnectorService;
|
||||
type InitError = ();
|
||||
type Future = Ready<Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
ok(self.service())
|
||||
}
|
||||
}
|
||||
|
||||
/// Create TCP connector service with default parameters.
|
||||
pub fn default_connector<T: Address + 'static>(
|
||||
) -> impl Service<Connect<T>, Response = Connection<T, TcpStream>, Error = ConnectError> + Clone
|
||||
{
|
||||
new_connector(Resolver::Default)
|
||||
/// TCP connector service.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct TcpConnectorService;
|
||||
|
||||
impl<R: Host> Service<ConnectInfo<R>> for TcpConnectorService {
|
||||
type Response = Connection<R, TcpStream>;
|
||||
type Error = ConnectError;
|
||||
type Future = TcpConnectorFut<R>;
|
||||
|
||||
actix_service::always_ready!();
|
||||
|
||||
fn call(&self, req: ConnectInfo<R>) -> Self::Future {
|
||||
let port = req.port();
|
||||
|
||||
let ConnectInfo {
|
||||
request: req,
|
||||
addr,
|
||||
local_addr,
|
||||
..
|
||||
} = req;
|
||||
|
||||
TcpConnectorFut::new(req, port, local_addr, addr)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create TCP connector service factory with default parameters.
|
||||
pub fn default_connector_factory<T: Address + 'static>() -> impl ServiceFactory<
|
||||
Connect<T>,
|
||||
Config = (),
|
||||
Response = Connection<T, TcpStream>,
|
||||
Error = ConnectError,
|
||||
InitError = (),
|
||||
> + Clone {
|
||||
new_connector_factory(Resolver::Default)
|
||||
/// Connect future for TCP service.
|
||||
#[doc(hidden)]
|
||||
pub enum TcpConnectorFut<R> {
|
||||
Response {
|
||||
req: Option<R>,
|
||||
port: u16,
|
||||
local_addr: Option<IpAddr>,
|
||||
addrs: Option<VecDeque<SocketAddr>>,
|
||||
stream: ReusableBoxFuture<Result<TcpStream, io::Error>>,
|
||||
},
|
||||
|
||||
Error(Option<ConnectError>),
|
||||
}
|
||||
|
||||
impl<R: Host> TcpConnectorFut<R> {
|
||||
pub(crate) fn new(
|
||||
req: R,
|
||||
port: u16,
|
||||
local_addr: Option<IpAddr>,
|
||||
addr: ConnectAddrs,
|
||||
) -> TcpConnectorFut<R> {
|
||||
if addr.is_unresolved() {
|
||||
error!("TCP connector: unresolved connection address");
|
||||
return TcpConnectorFut::Error(Some(ConnectError::Unresolved));
|
||||
}
|
||||
|
||||
trace!(
|
||||
"TCP connector: connecting to {} on port {}",
|
||||
req.hostname(),
|
||||
port
|
||||
);
|
||||
|
||||
match addr {
|
||||
ConnectAddrs::None => unreachable!("none variant already checked"),
|
||||
|
||||
ConnectAddrs::One(addr) => TcpConnectorFut::Response {
|
||||
req: Some(req),
|
||||
port,
|
||||
local_addr,
|
||||
addrs: None,
|
||||
stream: ReusableBoxFuture::new(connect(addr, local_addr)),
|
||||
},
|
||||
|
||||
// when resolver returns multiple socket addr for request they would be popped from
|
||||
// front end of queue and returns with the first successful tcp connection.
|
||||
ConnectAddrs::Multi(mut addrs) => {
|
||||
let addr = addrs.pop_front().unwrap();
|
||||
|
||||
TcpConnectorFut::Response {
|
||||
req: Some(req),
|
||||
port,
|
||||
local_addr,
|
||||
addrs: Some(addrs),
|
||||
stream: ReusableBoxFuture::new(connect(addr, local_addr)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Host> Future for TcpConnectorFut<R> {
|
||||
type Output = Result<Connection<R, TcpStream>, ConnectError>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
match self.get_mut() {
|
||||
TcpConnectorFut::Error(err) => Poll::Ready(Err(err.take().unwrap())),
|
||||
|
||||
TcpConnectorFut::Response {
|
||||
req,
|
||||
port,
|
||||
local_addr,
|
||||
addrs,
|
||||
stream,
|
||||
} => loop {
|
||||
match ready!(stream.poll(cx)) {
|
||||
Ok(sock) => {
|
||||
let req = req.take().unwrap();
|
||||
|
||||
trace!(
|
||||
"TCP connector: successfully connected to {:?} - {:?}",
|
||||
req.hostname(),
|
||||
sock.peer_addr()
|
||||
);
|
||||
|
||||
return Poll::Ready(Ok(Connection::new(req, sock)));
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
trace!(
|
||||
"TCP connector: failed to connect to {:?} port: {}",
|
||||
req.as_ref().unwrap().hostname(),
|
||||
port,
|
||||
);
|
||||
|
||||
if let Some(addr) = addrs.as_mut().and_then(|addrs| addrs.pop_front()) {
|
||||
stream.set(connect(addr, *local_addr));
|
||||
} else {
|
||||
return Poll::Ready(Err(ConnectError::Io(err)));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn connect(addr: SocketAddr, local_addr: Option<IpAddr>) -> io::Result<TcpStream> {
|
||||
// use local addr if connect asks for it
|
||||
match local_addr {
|
||||
Some(ip_addr) => {
|
||||
let socket = match ip_addr {
|
||||
IpAddr::V4(ip_addr) => {
|
||||
let socket = TcpSocket::new_v4()?;
|
||||
let addr = SocketAddr::V4(SocketAddrV4::new(ip_addr, 0));
|
||||
socket.bind(addr)?;
|
||||
socket
|
||||
}
|
||||
IpAddr::V6(ip_addr) => {
|
||||
let socket = TcpSocket::new_v6()?;
|
||||
let addr = SocketAddr::V6(SocketAddrV6::new(ip_addr, 0, 0, 0));
|
||||
socket.bind(addr)?;
|
||||
socket
|
||||
}
|
||||
};
|
||||
|
||||
socket.connect(addr).await
|
||||
}
|
||||
|
||||
None => TcpStream::connect(addr).await,
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +0,0 @@
|
||||
//! TLS Services
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
pub mod openssl;
|
||||
|
||||
#[cfg(feature = "rustls")]
|
||||
pub mod rustls;
|
||||
|
||||
#[cfg(feature = "native-tls")]
|
||||
pub mod native_tls;
|
@ -1,88 +0,0 @@
|
||||
use std::io;
|
||||
|
||||
use actix_rt::net::ActixStream;
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use log::trace;
|
||||
use tokio_native_tls::{TlsConnector as TokioNativetlsConnector, TlsStream};
|
||||
|
||||
pub use tokio_native_tls::native_tls::TlsConnector;
|
||||
|
||||
use crate::connect::{Address, Connection};
|
||||
|
||||
/// Native-tls connector factory and service
|
||||
pub struct NativetlsConnector {
|
||||
connector: TokioNativetlsConnector,
|
||||
}
|
||||
|
||||
impl NativetlsConnector {
|
||||
pub fn new(connector: TlsConnector) -> Self {
|
||||
Self {
|
||||
connector: TokioNativetlsConnector::from(connector),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NativetlsConnector {
|
||||
pub fn service(connector: TlsConnector) -> Self {
|
||||
Self::new(connector)
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for NativetlsConnector {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
connector: self.connector.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Address, U> ServiceFactory<Connection<T, U>> for NativetlsConnector
|
||||
where
|
||||
U: ActixStream + 'static,
|
||||
{
|
||||
type Response = Connection<T, TlsStream<U>>;
|
||||
type Error = io::Error;
|
||||
type Config = ();
|
||||
type Service = Self;
|
||||
type InitError = ();
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
let connector = self.clone();
|
||||
Box::pin(async { Ok(connector) })
|
||||
}
|
||||
}
|
||||
|
||||
// NativetlsConnector is both it's ServiceFactory and Service impl type.
|
||||
// As the factory and service share the same type and state.
|
||||
impl<T, U> Service<Connection<T, U>> for NativetlsConnector
|
||||
where
|
||||
T: Address,
|
||||
U: ActixStream + 'static,
|
||||
{
|
||||
type Response = Connection<T, TlsStream<U>>;
|
||||
type Error = io::Error;
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||
|
||||
actix_service::always_ready!();
|
||||
|
||||
fn call(&self, stream: Connection<T, U>) -> Self::Future {
|
||||
let (io, stream) = stream.replace_io(());
|
||||
let connector = self.connector.clone();
|
||||
Box::pin(async move {
|
||||
trace!("SSL Handshake start for: {:?}", stream.host());
|
||||
connector
|
||||
.connect(stream.host(), io)
|
||||
.await
|
||||
.map(|res| {
|
||||
trace!("SSL Handshake success: {:?}", stream.host());
|
||||
stream.replace_io(res).1
|
||||
})
|
||||
.map_err(|e| {
|
||||
trace!("SSL Handshake error: {:?}", e);
|
||||
io::Error::new(io::ErrorKind::Other, format!("{}", e))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
@ -1,130 +0,0 @@
|
||||
use std::{
|
||||
future::Future,
|
||||
io,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use actix_rt::net::ActixStream;
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use futures_core::{future::LocalBoxFuture, ready};
|
||||
use log::trace;
|
||||
|
||||
pub use openssl::ssl::{Error as SslError, HandshakeError, SslConnector, SslMethod};
|
||||
pub use tokio_openssl::SslStream;
|
||||
|
||||
use crate::connect::{Address, Connection};
|
||||
|
||||
/// OpenSSL connector factory
|
||||
pub struct OpensslConnector {
|
||||
connector: SslConnector,
|
||||
}
|
||||
|
||||
impl OpensslConnector {
|
||||
pub fn new(connector: SslConnector) -> Self {
|
||||
OpensslConnector { connector }
|
||||
}
|
||||
|
||||
pub fn service(connector: SslConnector) -> OpensslConnectorService {
|
||||
OpensslConnectorService { connector }
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for OpensslConnector {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
connector: self.connector.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> ServiceFactory<Connection<T, U>> for OpensslConnector
|
||||
where
|
||||
T: Address,
|
||||
U: ActixStream + 'static,
|
||||
{
|
||||
type Response = Connection<T, SslStream<U>>;
|
||||
type Error = io::Error;
|
||||
type Config = ();
|
||||
type Service = OpensslConnectorService;
|
||||
type InitError = ();
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
let connector = self.connector.clone();
|
||||
Box::pin(async { Ok(OpensslConnectorService { connector }) })
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OpensslConnectorService {
|
||||
connector: SslConnector,
|
||||
}
|
||||
|
||||
impl Clone for OpensslConnectorService {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
connector: self.connector.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Service<Connection<T, U>> for OpensslConnectorService
|
||||
where
|
||||
T: Address,
|
||||
U: ActixStream,
|
||||
{
|
||||
type Response = Connection<T, SslStream<U>>;
|
||||
type Error = io::Error;
|
||||
type Future = ConnectAsyncExt<T, U>;
|
||||
|
||||
actix_service::always_ready!();
|
||||
|
||||
fn call(&self, stream: Connection<T, U>) -> Self::Future {
|
||||
trace!("SSL Handshake start for: {:?}", stream.host());
|
||||
let (io, stream) = stream.replace_io(());
|
||||
let host = stream.host();
|
||||
|
||||
let config = self
|
||||
.connector
|
||||
.configure()
|
||||
.expect("SSL connect configuration was invalid.");
|
||||
|
||||
let ssl = config
|
||||
.into_ssl(host)
|
||||
.expect("SSL connect configuration was invalid.");
|
||||
|
||||
ConnectAsyncExt {
|
||||
io: Some(SslStream::new(ssl, io).unwrap()),
|
||||
stream: Some(stream),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConnectAsyncExt<T, U> {
|
||||
io: Option<SslStream<U>>,
|
||||
stream: Option<Connection<T, ()>>,
|
||||
}
|
||||
|
||||
impl<T: Address, U> Future for ConnectAsyncExt<T, U>
|
||||
where
|
||||
T: Address,
|
||||
U: ActixStream,
|
||||
{
|
||||
type Output = Result<Connection<T, SslStream<U>>, io::Error>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.get_mut();
|
||||
|
||||
match ready!(Pin::new(this.io.as_mut().unwrap()).poll_connect(cx)) {
|
||||
Ok(_) => {
|
||||
let stream = this.stream.take().unwrap();
|
||||
trace!("SSL Handshake success: {:?}", stream.host());
|
||||
Poll::Ready(Ok(stream.replace_io(this.io.take().unwrap()).1))
|
||||
}
|
||||
Err(e) => {
|
||||
trace!("SSL Handshake error: {:?}", e);
|
||||
Poll::Ready(Err(io::Error::new(io::ErrorKind::Other, format!("{}", e))))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,146 +0,0 @@
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
future::Future,
|
||||
io,
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
pub use tokio_rustls::{client::TlsStream, rustls::ClientConfig};
|
||||
pub use webpki_roots::TLS_SERVER_ROOTS;
|
||||
|
||||
use actix_rt::net::ActixStream;
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use futures_core::{future::LocalBoxFuture, ready};
|
||||
use log::trace;
|
||||
use tokio_rustls::rustls::{client::ServerName, OwnedTrustAnchor, RootCertStore};
|
||||
use tokio_rustls::{Connect, TlsConnector};
|
||||
|
||||
use crate::connect::{Address, Connection};
|
||||
|
||||
/// Returns standard root certificates from `webpki-roots` crate as a rustls certificate store.
|
||||
pub fn webpki_roots_cert_store() -> RootCertStore {
|
||||
let mut root_certs = RootCertStore::empty();
|
||||
for cert in TLS_SERVER_ROOTS.0 {
|
||||
let cert = OwnedTrustAnchor::from_subject_spki_name_constraints(
|
||||
cert.subject,
|
||||
cert.spki,
|
||||
cert.name_constraints,
|
||||
);
|
||||
let certs = vec![cert].into_iter();
|
||||
root_certs.add_server_trust_anchors(certs);
|
||||
}
|
||||
root_certs
|
||||
}
|
||||
|
||||
/// Rustls connector factory
|
||||
pub struct RustlsConnector {
|
||||
connector: Arc<ClientConfig>,
|
||||
}
|
||||
|
||||
impl RustlsConnector {
|
||||
pub fn new(connector: Arc<ClientConfig>) -> Self {
|
||||
RustlsConnector { connector }
|
||||
}
|
||||
}
|
||||
|
||||
impl RustlsConnector {
|
||||
pub fn service(connector: Arc<ClientConfig>) -> RustlsConnectorService {
|
||||
RustlsConnectorService { connector }
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for RustlsConnector {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
connector: self.connector.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> ServiceFactory<Connection<T, U>> for RustlsConnector
|
||||
where
|
||||
T: Address,
|
||||
U: ActixStream + 'static,
|
||||
{
|
||||
type Response = Connection<T, TlsStream<U>>;
|
||||
type Error = io::Error;
|
||||
type Config = ();
|
||||
type Service = RustlsConnectorService;
|
||||
type InitError = ();
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
let connector = self.connector.clone();
|
||||
Box::pin(async { Ok(RustlsConnectorService { connector }) })
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RustlsConnectorService {
|
||||
connector: Arc<ClientConfig>,
|
||||
}
|
||||
|
||||
impl Clone for RustlsConnectorService {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
connector: self.connector.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Service<Connection<T, U>> for RustlsConnectorService
|
||||
where
|
||||
T: Address,
|
||||
U: ActixStream,
|
||||
{
|
||||
type Response = Connection<T, TlsStream<U>>;
|
||||
type Error = io::Error;
|
||||
type Future = RustlsConnectorServiceFuture<T, U>;
|
||||
|
||||
actix_service::always_ready!();
|
||||
|
||||
fn call(&self, connection: Connection<T, U>) -> Self::Future {
|
||||
trace!("SSL Handshake start for: {:?}", connection.host());
|
||||
let (stream, connection) = connection.replace_io(());
|
||||
|
||||
match ServerName::try_from(connection.host()) {
|
||||
Ok(host) => RustlsConnectorServiceFuture::Future {
|
||||
connect: TlsConnector::from(self.connector.clone()).connect(host, stream),
|
||||
connection: Some(connection),
|
||||
},
|
||||
Err(_) => RustlsConnectorServiceFuture::InvalidDns,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum RustlsConnectorServiceFuture<T, U> {
|
||||
/// See issue <https://github.com/briansmith/webpki/issues/54>
|
||||
InvalidDns,
|
||||
Future {
|
||||
connect: Connect<U>,
|
||||
connection: Option<Connection<T, ()>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<T, U> Future for RustlsConnectorServiceFuture<T, U>
|
||||
where
|
||||
T: Address,
|
||||
U: ActixStream,
|
||||
{
|
||||
type Output = Result<Connection<T, TlsStream<U>>, io::Error>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
match self.get_mut() {
|
||||
Self::InvalidDns => Poll::Ready(Err(
|
||||
io::Error::new(io::ErrorKind::Other, "rustls currently only handles hostname-based connections. See https://github.com/briansmith/webpki/issues/54")
|
||||
)),
|
||||
Self::Future { connect, connection } => {
|
||||
let stream = ready!(Pin::new(connect).poll(cx))?;
|
||||
let connection = connection.take().unwrap();
|
||||
trace!("SSL Handshake success: {:?}", connection.host());
|
||||
Poll::Ready(Ok(connection.replace_io(stream).1))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
use http::Uri;
|
||||
|
||||
use super::Address;
|
||||
use super::Host;
|
||||
|
||||
impl Address for Uri {
|
||||
impl Host for Uri {
|
||||
fn hostname(&self) -> &str {
|
||||
self.host().unwrap_or("")
|
||||
}
|
||||
@ -35,9 +35,18 @@ fn scheme_to_port(scheme: Option<&str>) -> Option<u16> {
|
||||
Some("mqtts") => Some(8883),
|
||||
|
||||
// File Transfer Protocol (FTP)
|
||||
Some("ftp") => Some(1883),
|
||||
Some("ftp") => Some(21),
|
||||
Some("ftps") => Some(990),
|
||||
|
||||
// Redis
|
||||
Some("redis") => Some(6379),
|
||||
|
||||
// MySQL
|
||||
Some("mysql") => Some(3306),
|
||||
|
||||
// PostgreSQL
|
||||
Some("postgres") => Some(5432),
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,19 @@
|
||||
//! TLS acceptor and connector services for Actix ecosystem
|
||||
//! TLS acceptor and connector services for the Actix ecosystem.
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(missing_docs)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
#[allow(unused_extern_crates)]
|
||||
extern crate tls_openssl as openssl;
|
||||
|
||||
#[cfg(feature = "accept")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "accept")))]
|
||||
pub mod accept;
|
||||
|
||||
#[cfg(feature = "connect")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "connect")))]
|
||||
pub mod connect;
|
||||
|
@ -7,13 +7,15 @@
|
||||
feature = "openssl"
|
||||
))]
|
||||
|
||||
extern crate tls_openssl as openssl;
|
||||
|
||||
use std::io::{BufReader, Write};
|
||||
|
||||
use actix_rt::net::TcpStream;
|
||||
use actix_server::TestServer;
|
||||
use actix_service::ServiceFactoryExt as _;
|
||||
use actix_tls::accept::rustls::{Acceptor, TlsStream};
|
||||
use actix_tls::connect::tls::openssl::SslConnector;
|
||||
use actix_tls::connect::openssl::reexports::SslConnector;
|
||||
use actix_utils::future::ok;
|
||||
use rustls_pemfile::{certs, pkcs8_private_keys};
|
||||
use tls_openssl::ssl::SslVerifyMode;
|
||||
@ -53,13 +55,13 @@ fn rustls_server_config(cert: String, key: String) -> rustls::ServerConfig {
|
||||
}
|
||||
|
||||
fn openssl_connector(cert: String, key: String) -> SslConnector {
|
||||
use actix_tls::connect::tls::openssl::{SslConnector as OpensslConnector, SslMethod};
|
||||
use tls_openssl::{pkey::PKey, x509::X509};
|
||||
use actix_tls::connect::openssl::reexports::SslMethod;
|
||||
use openssl::{pkey::PKey, x509::X509};
|
||||
|
||||
let cert = X509::from_pem(cert.as_bytes()).unwrap();
|
||||
let key = PKey::private_key_from_pem(key.as_bytes()).unwrap();
|
||||
|
||||
let mut ssl = OpensslConnector::builder(SslMethod::tls()).unwrap();
|
||||
let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap();
|
||||
ssl.set_verify(SslVerifyMode::NONE);
|
||||
ssl.set_certificate(&cert).unwrap();
|
||||
ssl.set_private_key(&key).unwrap();
|
||||
|
63
actix-tls/tests/test_connect.rs
Executable file → Normal file
63
actix-tls/tests/test_connect.rs
Executable file → Normal file
@ -12,7 +12,7 @@ use actix_service::{fn_service, Service, ServiceFactory};
|
||||
use bytes::Bytes;
|
||||
use futures_util::sink::SinkExt;
|
||||
|
||||
use actix_tls::connect::{self as actix_connect, Connect};
|
||||
use actix_tls::connect::{ConnectError, ConnectInfo, Connection, Connector, Host};
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
#[actix_rt::test]
|
||||
@ -25,9 +25,9 @@ async fn test_string() {
|
||||
})
|
||||
});
|
||||
|
||||
let conn = actix_connect::default_connector();
|
||||
let connector = Connector::default().service();
|
||||
let addr = format!("localhost:{}", srv.port());
|
||||
let con = conn.call(addr.into()).await.unwrap();
|
||||
let con = connector.call(addr.into()).await.unwrap();
|
||||
assert_eq!(con.peer_addr().unwrap(), srv.addr());
|
||||
}
|
||||
|
||||
@ -42,7 +42,7 @@ async fn test_rustls_string() {
|
||||
})
|
||||
});
|
||||
|
||||
let conn = actix_connect::default_connector();
|
||||
let conn = Connector::default().service();
|
||||
let addr = format!("localhost:{}", srv.port());
|
||||
let con = conn.call(addr.into()).await.unwrap();
|
||||
assert_eq!(con.peer_addr().unwrap(), srv.addr());
|
||||
@ -58,23 +58,29 @@ async fn test_static_str() {
|
||||
})
|
||||
});
|
||||
|
||||
let conn = actix_connect::default_connector();
|
||||
let info = ConnectInfo::with_addr("10", srv.addr());
|
||||
let connector = Connector::default().service();
|
||||
let conn = connector.call(info).await.unwrap();
|
||||
assert_eq!(conn.peer_addr().unwrap(), srv.addr());
|
||||
|
||||
let con = conn
|
||||
.call(Connect::with_addr("10", srv.addr()))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(con.peer_addr().unwrap(), srv.addr());
|
||||
|
||||
let connect = Connect::new(srv.host().to_owned());
|
||||
|
||||
let conn = actix_connect::default_connector();
|
||||
let con = conn.call(connect).await;
|
||||
assert!(con.is_err());
|
||||
let info = ConnectInfo::new(srv.host().to_owned());
|
||||
let connector = Connector::default().service();
|
||||
let conn = connector.call(info).await;
|
||||
assert!(conn.is_err());
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_new_service() {
|
||||
async fn service_factory() {
|
||||
pub fn default_connector_factory<T: Host + 'static>() -> impl ServiceFactory<
|
||||
ConnectInfo<T>,
|
||||
Config = (),
|
||||
Response = Connection<T, TcpStream>,
|
||||
Error = ConnectError,
|
||||
InitError = (),
|
||||
> {
|
||||
Connector::default()
|
||||
}
|
||||
|
||||
let srv = TestServer::with(|| {
|
||||
fn_service(|io: TcpStream| async {
|
||||
let mut framed = Framed::new(io, BytesCodec);
|
||||
@ -83,14 +89,11 @@ async fn test_new_service() {
|
||||
})
|
||||
});
|
||||
|
||||
let factory = actix_connect::default_connector_factory();
|
||||
|
||||
let conn = factory.new_service(()).await.unwrap();
|
||||
let con = conn
|
||||
.call(Connect::with_addr("10", srv.addr()))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(con.peer_addr().unwrap(), srv.addr());
|
||||
let info = ConnectInfo::with_addr("10", srv.addr());
|
||||
let factory = default_connector_factory();
|
||||
let connector = factory.new_service(()).await.unwrap();
|
||||
let con = connector.call(info).await;
|
||||
assert_eq!(con.unwrap().peer_addr().unwrap(), srv.addr());
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "openssl", feature = "uri"))]
|
||||
@ -106,9 +109,9 @@ async fn test_openssl_uri() {
|
||||
})
|
||||
});
|
||||
|
||||
let conn = actix_connect::default_connector();
|
||||
let connector = Connector::default().service();
|
||||
let addr = http::Uri::try_from(format!("https://localhost:{}", srv.port())).unwrap();
|
||||
let con = conn.call(addr.into()).await.unwrap();
|
||||
let con = connector.call(addr.into()).await.unwrap();
|
||||
assert_eq!(con.peer_addr().unwrap(), srv.addr());
|
||||
}
|
||||
|
||||
@ -125,7 +128,7 @@ async fn test_rustls_uri() {
|
||||
})
|
||||
});
|
||||
|
||||
let conn = actix_connect::default_connector();
|
||||
let conn = Connector::default().service();
|
||||
let addr = http::Uri::try_from(format!("https://localhost:{}", srv.port())).unwrap();
|
||||
let con = conn.call(addr.into()).await.unwrap();
|
||||
assert_eq!(con.peer_addr().unwrap(), srv.addr());
|
||||
@ -141,11 +144,11 @@ async fn test_local_addr() {
|
||||
})
|
||||
});
|
||||
|
||||
let conn = actix_connect::default_connector();
|
||||
let conn = Connector::default().service();
|
||||
let local = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 3));
|
||||
|
||||
let (con, _) = conn
|
||||
.call(Connect::with_addr("10", srv.addr()).set_local_addr(local))
|
||||
.call(ConnectInfo::with_addr("10", srv.addr()).set_local_addr(local))
|
||||
.await
|
||||
.unwrap()
|
||||
.into_parts();
|
||||
|
@ -10,7 +10,9 @@ use actix_server::TestServer;
|
||||
use actix_service::{fn_service, Service, ServiceFactory};
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
|
||||
use actix_tls::connect::{new_connector_factory, Connect, Resolve, Resolver};
|
||||
use actix_tls::connect::{
|
||||
ConnectError, ConnectInfo, Connection, Connector, Host, Resolve, Resolver,
|
||||
};
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn custom_resolver() {
|
||||
@ -36,6 +38,18 @@ async fn custom_resolver() {
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn custom_resolver_connect() {
|
||||
pub fn connector_factory<T: Host + 'static>(
|
||||
resolver: Resolver,
|
||||
) -> impl ServiceFactory<
|
||||
ConnectInfo<T>,
|
||||
Config = (),
|
||||
Response = Connection<T, TcpStream>,
|
||||
Error = ConnectError,
|
||||
InitError = (),
|
||||
> {
|
||||
Connector::new(resolver)
|
||||
}
|
||||
|
||||
use trust_dns_resolver::TokioAsyncResolver;
|
||||
|
||||
let srv =
|
||||
@ -68,12 +82,11 @@ async fn custom_resolver_connect() {
|
||||
trust_dns: TokioAsyncResolver::tokio_from_system_conf().unwrap(),
|
||||
};
|
||||
|
||||
let resolver = Resolver::new_custom(resolver);
|
||||
let factory = new_connector_factory(resolver);
|
||||
let factory = connector_factory(Resolver::custom(resolver));
|
||||
|
||||
let conn = factory.new_service(()).await.unwrap();
|
||||
let con = conn
|
||||
.call(Connect::with_addr("example.com", srv.addr()))
|
||||
.call(ConnectInfo::with_addr("example.com", srv.addr()))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(con.peer_addr().unwrap(), srv.addr());
|
||||
|
Loading…
Reference in New Issue
Block a user