mirror of
https://github.com/fafhrd91/actix-net
synced 2025-08-16 12:39:00 +02:00
Compare commits
21 Commits
circleci-p
...
tls-v3.0.0
Author | SHA1 | Date | |
---|---|---|---|
|
183bcf6ae3 | ||
|
5dc2bfcb01 | ||
|
5556afd524 | ||
|
de5908bfe7 | ||
|
c63880a292 | ||
|
44e4381879 | ||
|
18eced7305 | ||
|
a2437eed29 | ||
|
67b357a175 | ||
|
3597af5c45 | ||
|
8891c2681e | ||
|
233c61ba08 | ||
|
161f239f12 | ||
|
7e7df2f931 | ||
|
ce8ec15eaa | ||
|
ae28ce5377 | ||
|
54d1d9e520 | ||
|
0b0cbd5388 | ||
|
443a328fb4 | ||
|
58a67ade32 | ||
|
38caa8f088 |
@@ -14,7 +14,7 @@ ci-check = "hack --workspace --feature-powerset --exclude-features=io-uring chec
|
|||||||
ci-check-linux = "hack --workspace --feature-powerset check --tests --examples"
|
ci-check-linux = "hack --workspace --feature-powerset check --tests --examples"
|
||||||
|
|
||||||
# tests avoiding io-uring feature
|
# tests avoiding io-uring feature
|
||||||
ci-test = "hack test --workspace --exclude=actix-rt --exclude=actix-server --all-features --lib --tests --no-fail-fast -- --nocapture"
|
ci-test = " hack --feature-powerset --exclude=actix-rt --exclude=actix-server --exclude-features=io-uring test --workspace --lib --tests --no-fail-fast -- --nocapture"
|
||||||
ci-test-rt = " hack --feature-powerset --exclude-features=io-uring test --package=actix-rt --lib --tests --no-fail-fast -- --nocapture"
|
ci-test-rt = " hack --feature-powerset --exclude-features=io-uring test --package=actix-rt --lib --tests --no-fail-fast -- --nocapture"
|
||||||
ci-test-server = "hack --feature-powerset --exclude-features=io-uring test --package=actix-server --lib --tests --no-fail-fast -- --nocapture"
|
ci-test-server = "hack --feature-powerset --exclude-features=io-uring test --package=actix-server --lib --tests --no-fail-fast -- --nocapture"
|
||||||
|
|
||||||
|
@@ -1,22 +0,0 @@
|
|||||||
version: 2.1
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test-io-uring-features:
|
|
||||||
machine:
|
|
||||||
image: ubuntu-2004:202107-02
|
|
||||||
resource_class: large
|
|
||||||
environment:
|
|
||||||
CI: '1'
|
|
||||||
CARGO_INCREMENTAL: '0'
|
|
||||||
RUST_BACKTRACE: '1'
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
- run: uname -a
|
|
||||||
- run: apt-get update; apt-get install -y liburing-dev
|
|
||||||
- run: cargo test -p=actix-rt --features=io-uring
|
|
||||||
- run: cargo test -p=actix-server --features=io-uring
|
|
||||||
|
|
||||||
workflows:
|
|
||||||
test:
|
|
||||||
jobs:
|
|
||||||
- test-io-uring-features
|
|
@@ -3,6 +3,12 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 2.5.0 - 2021-11-22
|
||||||
|
* Add `System::run_with_code` to allow retrieving the exit code on stop. [#411]
|
||||||
|
|
||||||
|
[#411]: https://github.com/actix/actix-net/pull/411
|
||||||
|
|
||||||
|
|
||||||
## 2.4.0 - 2021-11-05
|
## 2.4.0 - 2021-11-05
|
||||||
* Add `Arbiter::try_current` for situations where thread may or may not have Arbiter context. [#408]
|
* Add `Arbiter::try_current` for situations where thread may or may not have Arbiter context. [#408]
|
||||||
* Start io-uring with `System::new` when feature is enabled. [#395]
|
* Start io-uring with `System::new` when feature is enabled. [#395]
|
||||||
@@ -108,7 +114,7 @@
|
|||||||
[#129]: https://github.com/actix/actix-net/issues/129
|
[#129]: https://github.com/actix/actix-net/issues/129
|
||||||
|
|
||||||
|
|
||||||
## 1.1.0 - 2020-04-08 (YANKED)
|
## 1.1.0 - 2020-04-08 _(YANKED)_
|
||||||
* Expose `System::is_set` to check if current system has ben started [#99]
|
* Expose `System::is_set` to check if current system has ben started [#99]
|
||||||
* Add `Arbiter::is_running` to check if event loop is running [#124]
|
* Add `Arbiter::is_running` to check if event loop is running [#124]
|
||||||
* Add `Arbiter::local_join` associated function
|
* Add `Arbiter::local_join` associated function
|
||||||
|
@@ -1,9 +1,10 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-rt"
|
name = "actix-rt"
|
||||||
version = "2.4.0"
|
version = "2.5.0"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"Rob Ede <robjtede@icloud.com>",
|
"Rob Ede <robjtede@icloud.com>",
|
||||||
|
"fakeshadow <24548779@qq.com>",
|
||||||
]
|
]
|
||||||
description = "Tokio-based single-threaded async runtime for the Actix ecosystem"
|
description = "Tokio-based single-threaded async runtime for the Actix ecosystem"
|
||||||
keywords = ["async", "futures", "io", "runtime"]
|
keywords = ["async", "futures", "io", "runtime"]
|
||||||
|
@@ -3,11 +3,11 @@
|
|||||||
> Tokio-based single-threaded async runtime for the Actix ecosystem.
|
> Tokio-based single-threaded async runtime for the Actix ecosystem.
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-rt)
|
[](https://crates.io/crates/actix-rt)
|
||||||
[](https://docs.rs/actix-rt/2.4.0)
|
[](https://docs.rs/actix-rt/2.5.0)
|
||||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-rt/2.4.0)
|
[](https://deps.rs/crate/actix-rt/2.5.0)
|
||||||

|

|
||||||
[](https://discord.gg/WghFtEH6Hb)
|
[](https://discord.gg/WghFtEH6Hb)
|
||||||
|
|
||||||
|
@@ -36,10 +36,13 @@
|
|||||||
//! # `io-uring` Support
|
//! # `io-uring` Support
|
||||||
//! There is experimental support for using io-uring with this crate by enabling the
|
//! There is experimental support for using io-uring with this crate by enabling the
|
||||||
//! `io-uring` feature. For now, it is semver exempt.
|
//! `io-uring` feature. For now, it is semver exempt.
|
||||||
|
//!
|
||||||
|
//! Note that there are currently some unimplemented parts of using `actix-rt` with `io-uring`.
|
||||||
|
//! In particular, when running a `System`, only `System::block_on` is supported.
|
||||||
|
|
||||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||||
#![allow(clippy::type_complexity)]
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
|
#![allow(clippy::type_complexity)]
|
||||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||||
|
|
||||||
|
@@ -67,11 +67,7 @@ impl System {
|
|||||||
let sys_ctrl = SystemController::new(sys_rx, stop_tx);
|
let sys_ctrl = SystemController::new(sys_rx, stop_tx);
|
||||||
rt.spawn(sys_ctrl);
|
rt.spawn(sys_ctrl);
|
||||||
|
|
||||||
SystemRunner {
|
SystemRunner { rt, stop_rx }
|
||||||
rt,
|
|
||||||
stop_rx,
|
|
||||||
system,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +90,7 @@ impl System {
|
|||||||
where
|
where
|
||||||
F: Fn() -> tokio::runtime::Runtime,
|
F: Fn() -> tokio::runtime::Runtime,
|
||||||
{
|
{
|
||||||
unimplemented!("System::with_tokio_rt is not implemented yet")
|
unimplemented!("System::with_tokio_rt is not implemented for io-uring feature yet")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,36 +173,35 @@ impl System {
|
|||||||
|
|
||||||
/// Runner that keeps a [System]'s event loop alive until stop message is received.
|
/// Runner that keeps a [System]'s event loop alive until stop message is received.
|
||||||
#[cfg(not(feature = "io-uring"))]
|
#[cfg(not(feature = "io-uring"))]
|
||||||
#[must_use = "A SystemRunner does nothing unless `run` or `block_on` is called."]
|
#[must_use = "A SystemRunner does nothing unless `run` is called."]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SystemRunner {
|
pub struct SystemRunner {
|
||||||
rt: crate::runtime::Runtime,
|
rt: crate::runtime::Runtime,
|
||||||
stop_rx: oneshot::Receiver<i32>,
|
stop_rx: oneshot::Receiver<i32>,
|
||||||
#[allow(dead_code)]
|
|
||||||
system: System,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "io-uring"))]
|
#[cfg(not(feature = "io-uring"))]
|
||||||
impl SystemRunner {
|
impl SystemRunner {
|
||||||
/// Starts event loop and will return once [System] is [stopped](System::stop).
|
/// Starts event loop and will return once [System] is [stopped](System::stop).
|
||||||
pub fn run(self) -> io::Result<()> {
|
pub fn run(self) -> io::Result<()> {
|
||||||
|
let exit_code = self.run_with_code()?;
|
||||||
|
|
||||||
|
match exit_code {
|
||||||
|
0 => Ok(()),
|
||||||
|
nonzero => Err(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
format!("Non-zero exit code: {}", nonzero),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs the event loop until [stopped](System::stop_with_code), returning the exit code.
|
||||||
|
pub fn run_with_code(self) -> io::Result<i32> {
|
||||||
let SystemRunner { rt, stop_rx, .. } = self;
|
let SystemRunner { rt, stop_rx, .. } = self;
|
||||||
|
|
||||||
// run loop
|
// run loop
|
||||||
match rt.block_on(stop_rx) {
|
rt.block_on(stop_rx)
|
||||||
Ok(code) => {
|
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
|
||||||
if code != 0 {
|
|
||||||
Err(io::Error::new(
|
|
||||||
io::ErrorKind::Other,
|
|
||||||
format!("Non-zero exit code: {}", code),
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(e) => Err(io::Error::new(io::ErrorKind::Other, e)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs the provided future, blocking the current thread until the future completes.
|
/// Runs the provided future, blocking the current thread until the future completes.
|
||||||
@@ -218,7 +213,7 @@ impl SystemRunner {
|
|||||||
|
|
||||||
/// Runner that keeps a [System]'s event loop alive until stop message is received.
|
/// Runner that keeps a [System]'s event loop alive until stop message is received.
|
||||||
#[cfg(feature = "io-uring")]
|
#[cfg(feature = "io-uring")]
|
||||||
#[must_use = "A SystemRunner does nothing unless `run` or `block_on` is called."]
|
#[must_use = "A SystemRunner does nothing unless `run` is called."]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SystemRunner;
|
pub struct SystemRunner;
|
||||||
|
|
||||||
@@ -226,7 +221,14 @@ pub struct SystemRunner;
|
|||||||
impl SystemRunner {
|
impl SystemRunner {
|
||||||
/// Starts event loop and will return once [System] is [stopped](System::stop).
|
/// Starts event loop and will return once [System] is [stopped](System::stop).
|
||||||
pub fn run(self) -> io::Result<()> {
|
pub fn run(self) -> io::Result<()> {
|
||||||
unimplemented!("SystemRunner::run is not implemented yet")
|
unimplemented!("SystemRunner::run is not implemented for io-uring feature yet");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs the event loop until [stopped](System::stop_with_code), returning the exit code.
|
||||||
|
pub fn run_with_code(self) -> io::Result<i32> {
|
||||||
|
unimplemented!(
|
||||||
|
"SystemRunner::run_with_code is not implemented for io-uring feature yet"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs the provided future, blocking the current thread until the future completes.
|
/// Runs the provided future, blocking the current thread until the future completes.
|
||||||
|
@@ -24,6 +24,15 @@ fn await_for_timer() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "io-uring"))]
|
||||||
|
#[test]
|
||||||
|
fn run_with_code() {
|
||||||
|
let sys = System::new();
|
||||||
|
System::current().stop_with_code(42);
|
||||||
|
let exit_code = sys.run_with_code().expect("system stop should not error");
|
||||||
|
assert_eq!(exit_code, 42);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn join_another_arbiter() {
|
fn join_another_arbiter() {
|
||||||
let time = Duration::from_secs(1);
|
let time = Duration::from_secs(1);
|
||||||
@@ -99,8 +108,8 @@ fn wait_for_spawns() {
|
|||||||
|
|
||||||
let handle = rt.spawn(async {
|
let handle = rt.spawn(async {
|
||||||
println!("running on the runtime");
|
println!("running on the runtime");
|
||||||
// assertion panic is caught at task boundary
|
// panic is caught at task boundary
|
||||||
assert_eq!(1, 2);
|
panic!("intentional test panic");
|
||||||
});
|
});
|
||||||
|
|
||||||
assert!(rt.block_on(handle).is_err());
|
assert!(rt.block_on(handle).is_err());
|
||||||
|
@@ -3,13 +3,19 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
## 2.0.0-beta.8 - 2021-11-05
|
## 2.0.0-beta.9 - 2021-11-15
|
||||||
|
* Restore `Arbiter` support lost in `beta.8`. [#417]
|
||||||
|
|
||||||
|
[#417]: https://github.com/actix/actix-net/pull/417
|
||||||
|
|
||||||
|
|
||||||
|
## 2.0.0-beta.8 - 2021-11-05 _(YANKED)_
|
||||||
* Fix non-unix signal handler. [#410]
|
* Fix non-unix signal handler. [#410]
|
||||||
|
|
||||||
[#410]: https://github.com/actix/actix-net/pull/410
|
[#410]: https://github.com/actix/actix-net/pull/410
|
||||||
|
|
||||||
|
|
||||||
## 2.0.0-beta.7 - 2021-11-05
|
## 2.0.0-beta.7 - 2021-11-05 _(YANKED)_
|
||||||
* Server can be started in regular Tokio runtime. [#408]
|
* Server can be started in regular Tokio runtime. [#408]
|
||||||
* Expose new `Server` type whose `Future` impl resolves when server stops. [#408]
|
* Expose new `Server` type whose `Future` impl resolves when server stops. [#408]
|
||||||
* Rename `Server` to `ServerHandle`. [#407]
|
* Rename `Server` to `ServerHandle`. [#407]
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-server"
|
name = "actix-server"
|
||||||
version = "2.0.0-beta.8"
|
version = "2.0.0-beta.9"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"fakeshadow <24548779@qq.com>",
|
"fakeshadow <24548779@qq.com>",
|
||||||
@@ -18,7 +18,7 @@ path = "src/lib.rs"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
io-uring = ["tokio-uring"]
|
io-uring = ["tokio-uring", "actix-rt/io-uring"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-rt = { version = "2.4.0", default-features = false }
|
actix-rt = { version = "2.4.0", default-features = false }
|
||||||
@@ -26,8 +26,9 @@ actix-service = "2.0.0"
|
|||||||
actix-utils = "3.0.0"
|
actix-utils = "3.0.0"
|
||||||
|
|
||||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||||
|
futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
mio = { version = "0.7.6", features = ["os-poll", "net"] }
|
mio = { version = "0.8", features = ["os-poll", "net"] }
|
||||||
num_cpus = "1.13"
|
num_cpus = "1.13"
|
||||||
socket2 = "0.4.2"
|
socket2 = "0.4.2"
|
||||||
tokio = { version = "1.5.1", features = ["sync"] }
|
tokio = { version = "1.5.1", features = ["sync"] }
|
||||||
@@ -37,7 +38,7 @@ tokio-uring = { version = "0.1", optional = true }
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-codec = "0.4.0"
|
actix-codec = "0.4.0"
|
||||||
actix-rt = "2.0.0"
|
actix-rt = "2.4.0"
|
||||||
|
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
env_logger = "0.9"
|
env_logger = "0.9"
|
||||||
|
@@ -8,8 +8,7 @@ use crate::{
|
|||||||
server::ServerCommand,
|
server::ServerCommand,
|
||||||
service::{InternalServiceFactory, ServiceFactory, StreamNewService},
|
service::{InternalServiceFactory, ServiceFactory, StreamNewService},
|
||||||
socket::{
|
socket::{
|
||||||
create_mio_tcp_listener, MioListener, MioTcpListener, StdSocketAddr, StdTcpListener,
|
create_mio_tcp_listener, MioListener, MioTcpListener, StdTcpListener, ToSocketAddrs,
|
||||||
ToSocketAddrs,
|
|
||||||
},
|
},
|
||||||
worker::ServerWorkerConfig,
|
worker::ServerWorkerConfig,
|
||||||
Server,
|
Server,
|
||||||
@@ -113,7 +112,7 @@ impl ServerBuilder {
|
|||||||
self.max_concurrent_connections(num)
|
self.max_concurrent_connections(num)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stop Actix system.
|
/// Stop Actix `System` after server shutdown.
|
||||||
pub fn system_exit(mut self) -> Self {
|
pub fn system_exit(mut self) -> Self {
|
||||||
self.exit = true;
|
self.exit = true;
|
||||||
self
|
self
|
||||||
@@ -243,7 +242,8 @@ impl ServerBuilder {
|
|||||||
use std::net::{IpAddr, Ipv4Addr};
|
use std::net::{IpAddr, Ipv4Addr};
|
||||||
lst.set_nonblocking(true)?;
|
lst.set_nonblocking(true)?;
|
||||||
let token = self.next_token();
|
let token = self.next_token();
|
||||||
let addr = StdSocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
|
let addr =
|
||||||
|
crate::socket::StdSocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
|
||||||
self.factories.push(StreamNewService::create(
|
self.factories.push(StreamNewService::create(
|
||||||
name.as_ref().to_string(),
|
name.as_ref().to_string(),
|
||||||
token,
|
token,
|
||||||
|
@@ -42,10 +42,12 @@ impl ServerHandle {
|
|||||||
/// Stop incoming connection processing, stop all workers and exit.
|
/// Stop incoming connection processing, stop all workers and exit.
|
||||||
pub fn stop(&self, graceful: bool) -> impl Future<Output = ()> {
|
pub fn stop(&self, graceful: bool) -> impl Future<Output = ()> {
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
|
|
||||||
let _ = self.cmd_tx.send(ServerCommand::Stop {
|
let _ = self.cmd_tx.send(ServerCommand::Stop {
|
||||||
graceful,
|
graceful,
|
||||||
completion: Some(tx),
|
completion: Some(tx),
|
||||||
});
|
});
|
||||||
|
|
||||||
async {
|
async {
|
||||||
let _ = rx.await;
|
let _ = rx.await;
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,7 @@ use std::{
|
|||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
|
|
||||||
use futures_core::future::{BoxFuture, LocalBoxFuture};
|
use futures_core::future::BoxFuture;
|
||||||
|
|
||||||
// a poor man's join future. joined future is only used when starting/stopping the server.
|
// a poor man's join future. joined future is only used when starting/stopping the server.
|
||||||
// pin_project and pinned futures are overkill for this task.
|
// pin_project and pinned futures are overkill for this task.
|
||||||
@@ -61,63 +61,6 @@ impl<T> Future for JoinAll<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn join_all_local<T>(
|
|
||||||
fut: Vec<impl Future<Output = T> + 'static>,
|
|
||||||
) -> JoinAllLocal<T> {
|
|
||||||
let fut = fut
|
|
||||||
.into_iter()
|
|
||||||
.map(|f| JoinLocalFuture::LocalFuture(Box::pin(f)))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
JoinAllLocal { fut }
|
|
||||||
}
|
|
||||||
|
|
||||||
// a poor man's join future. joined future is only used when starting/stopping the server.
|
|
||||||
// pin_project and pinned futures are overkill for this task.
|
|
||||||
pub(crate) struct JoinAllLocal<T> {
|
|
||||||
fut: Vec<JoinLocalFuture<T>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum JoinLocalFuture<T> {
|
|
||||||
LocalFuture(LocalBoxFuture<'static, T>),
|
|
||||||
Result(Option<T>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Unpin for JoinAllLocal<T> {}
|
|
||||||
|
|
||||||
impl<T> Future for JoinAllLocal<T> {
|
|
||||||
type Output = Vec<T>;
|
|
||||||
|
|
||||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
|
||||||
let mut ready = true;
|
|
||||||
|
|
||||||
let this = self.get_mut();
|
|
||||||
for fut in this.fut.iter_mut() {
|
|
||||||
if let JoinLocalFuture::LocalFuture(f) = fut {
|
|
||||||
match f.as_mut().poll(cx) {
|
|
||||||
Poll::Ready(t) => {
|
|
||||||
*fut = JoinLocalFuture::Result(Some(t));
|
|
||||||
}
|
|
||||||
Poll::Pending => ready = false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ready {
|
|
||||||
let mut res = Vec::new();
|
|
||||||
for fut in this.fut.iter_mut() {
|
|
||||||
if let JoinLocalFuture::Result(f) = fut {
|
|
||||||
res.push(f.take().unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Poll::Ready(res)
|
|
||||||
} else {
|
|
||||||
Poll::Pending
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -132,13 +75,4 @@ mod test {
|
|||||||
assert_eq!(Err(3), res.next().unwrap());
|
assert_eq!(Err(3), res.next().unwrap());
|
||||||
assert_eq!(Ok(9), res.next().unwrap());
|
assert_eq!(Ok(9), res.next().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
|
||||||
async fn test_join_all_local() {
|
|
||||||
let futs = vec![ready(Ok(1)), ready(Err(3)), ready(Ok(9))];
|
|
||||||
let mut res = join_all_local(futs).await.into_iter();
|
|
||||||
assert_eq!(Ok(1), res.next().unwrap());
|
|
||||||
assert_eq!(Err(3), res.next().unwrap());
|
|
||||||
assert_eq!(Ok(9), res.next().unwrap());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -19,7 +19,7 @@ use crate::{
|
|||||||
builder::ServerBuilder,
|
builder::ServerBuilder,
|
||||||
join_all::join_all,
|
join_all::join_all,
|
||||||
service::InternalServiceFactory,
|
service::InternalServiceFactory,
|
||||||
signals::{Signal, Signals},
|
signals::{SignalKind, Signals},
|
||||||
waker_queue::{WakerInterest, WakerQueue},
|
waker_queue::{WakerInterest, WakerQueue},
|
||||||
worker::{ServerWorker, ServerWorkerConfig, WorkerHandleServer},
|
worker::{ServerWorker, ServerWorkerConfig, WorkerHandleServer},
|
||||||
ServerHandle,
|
ServerHandle,
|
||||||
@@ -27,16 +27,22 @@ use crate::{
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) enum ServerCommand {
|
pub(crate) enum ServerCommand {
|
||||||
/// TODO
|
/// Worker failed to accept connection, indicating a probable panic.
|
||||||
|
///
|
||||||
|
/// Contains index of faulted worker.
|
||||||
WorkerFaulted(usize),
|
WorkerFaulted(usize),
|
||||||
|
|
||||||
|
/// Pause accepting connections.
|
||||||
|
///
|
||||||
/// Contains return channel to notify caller of successful state change.
|
/// Contains return channel to notify caller of successful state change.
|
||||||
Pause(oneshot::Sender<()>),
|
Pause(oneshot::Sender<()>),
|
||||||
|
|
||||||
|
/// Resume accepting connections.
|
||||||
|
///
|
||||||
/// Contains return channel to notify caller of successful state change.
|
/// Contains return channel to notify caller of successful state change.
|
||||||
Resume(oneshot::Sender<()>),
|
Resume(oneshot::Sender<()>),
|
||||||
|
|
||||||
/// TODO
|
/// Stop accepting connections and begin shutdown procedure.
|
||||||
Stop {
|
Stop {
|
||||||
/// True if shut down should be graceful.
|
/// True if shut down should be graceful.
|
||||||
graceful: bool,
|
graceful: bool,
|
||||||
@@ -132,15 +138,13 @@ impl Server {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Give log information on what runtime will be used.
|
// Give log information on what runtime will be used.
|
||||||
let is_tokio = tokio::runtime::Handle::try_current().is_ok();
|
|
||||||
let is_actix = actix_rt::System::try_current().is_some();
|
let is_actix = actix_rt::System::try_current().is_some();
|
||||||
|
let is_tokio = tokio::runtime::Handle::try_current().is_ok();
|
||||||
|
|
||||||
match (is_tokio, is_actix) {
|
match (is_actix, is_tokio) {
|
||||||
(true, false) => info!("Tokio runtime found. Starting in existing Tokio runtime"),
|
(true, _) => info!("Actix runtime found; starting in Actix runtime"),
|
||||||
(_, true) => info!("Actix runtime found. Starting in Actix runtime"),
|
(_, true) => info!("Tokio runtime found; starting in existing Tokio runtime"),
|
||||||
(_, _) => info!(
|
(_, false) => panic!("Actix or Tokio runtime not found; halting"),
|
||||||
"Actix/Tokio runtime not found. Starting in newt Tokio current-thread runtime"
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (_, name, lst) in &builder.sockets {
|
for (_, name, lst) in &builder.sockets {
|
||||||
@@ -196,11 +200,11 @@ impl Future for Server {
|
|||||||
|
|
||||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
match self.as_mut().get_mut() {
|
match self.as_mut().get_mut() {
|
||||||
Server::Error(err) => Poll::Ready(Err(err
|
Self::Error(err) => Poll::Ready(Err(err
|
||||||
.take()
|
.take()
|
||||||
.expect("Server future cannot be polled after error"))),
|
.expect("Server future cannot be polled after error"))),
|
||||||
|
|
||||||
Server::Server(inner) => {
|
Self::Server(inner) => {
|
||||||
// poll Signals
|
// poll Signals
|
||||||
if let Some(ref mut signals) = inner.signals {
|
if let Some(ref mut signals) = inner.signals {
|
||||||
if let Poll::Ready(signal) = Pin::new(signals).poll(cx) {
|
if let Poll::Ready(signal) = Pin::new(signals).poll(cx) {
|
||||||
@@ -326,9 +330,9 @@ impl ServerInner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_signal(&mut self, signal: Signal) -> Option<BoxFuture<'static, ()>> {
|
fn handle_signal(&mut self, signal: SignalKind) -> Option<BoxFuture<'static, ()>> {
|
||||||
match signal {
|
match signal {
|
||||||
Signal::Int => {
|
SignalKind::Int => {
|
||||||
info!("SIGINT received; starting forced shutdown");
|
info!("SIGINT received; starting forced shutdown");
|
||||||
self.exit = true;
|
self.exit = true;
|
||||||
self.handle_cmd(ServerCommand::Stop {
|
self.handle_cmd(ServerCommand::Stop {
|
||||||
@@ -337,7 +341,7 @@ impl ServerInner {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
Signal::Term => {
|
SignalKind::Term => {
|
||||||
info!("SIGTERM received; starting graceful shutdown");
|
info!("SIGTERM received; starting graceful shutdown");
|
||||||
self.exit = true;
|
self.exit = true;
|
||||||
self.handle_cmd(ServerCommand::Stop {
|
self.handle_cmd(ServerCommand::Stop {
|
||||||
@@ -346,7 +350,7 @@ impl ServerInner {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
Signal::Quit => {
|
SignalKind::Quit => {
|
||||||
info!("SIGQUIT received; starting forced shutdown");
|
info!("SIGQUIT received; starting forced shutdown");
|
||||||
self.exit = true;
|
self.exit = true;
|
||||||
self.handle_cmd(ServerCommand::Stop {
|
self.handle_cmd(ServerCommand::Stop {
|
||||||
|
@@ -11,7 +11,7 @@ use log::trace;
|
|||||||
// #[allow(dead_code)]
|
// #[allow(dead_code)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
#[allow(dead_code)] // variants are never constructed on non-unix
|
#[allow(dead_code)] // variants are never constructed on non-unix
|
||||||
pub(crate) enum Signal {
|
pub(crate) enum SignalKind {
|
||||||
/// `SIGINT`
|
/// `SIGINT`
|
||||||
Int,
|
Int,
|
||||||
|
|
||||||
@@ -22,12 +22,12 @@ pub(crate) enum Signal {
|
|||||||
Quit,
|
Quit,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Signal {
|
impl fmt::Display for SignalKind {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.write_str(match self {
|
f.write_str(match self {
|
||||||
Signal::Int => "SIGINT",
|
SignalKind::Int => "SIGINT",
|
||||||
Signal::Term => "SIGTERM",
|
SignalKind::Term => "SIGTERM",
|
||||||
Signal::Quit => "SIGQUIT",
|
SignalKind::Quit => "SIGQUIT",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,7 +38,7 @@ pub(crate) struct Signals {
|
|||||||
signals: futures_core::future::BoxFuture<'static, std::io::Result<()>>,
|
signals: futures_core::future::BoxFuture<'static, std::io::Result<()>>,
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
signals: Vec<(Signal, actix_rt::signal::unix::Signal)>,
|
signals: Vec<(SignalKind, actix_rt::signal::unix::Signal)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Signals {
|
impl Signals {
|
||||||
@@ -58,9 +58,9 @@ impl Signals {
|
|||||||
use actix_rt::signal::unix;
|
use actix_rt::signal::unix;
|
||||||
|
|
||||||
let sig_map = [
|
let sig_map = [
|
||||||
(unix::SignalKind::interrupt(), Signal::Int),
|
(unix::SignalKind::interrupt(), SignalKind::Int),
|
||||||
(unix::SignalKind::terminate(), Signal::Term),
|
(unix::SignalKind::terminate(), SignalKind::Term),
|
||||||
(unix::SignalKind::quit(), Signal::Quit),
|
(unix::SignalKind::quit(), SignalKind::Quit),
|
||||||
];
|
];
|
||||||
|
|
||||||
let signals = sig_map
|
let signals = sig_map
|
||||||
@@ -85,18 +85,17 @@ impl Signals {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Future for Signals {
|
impl Future for Signals {
|
||||||
type Output = Signal;
|
type Output = SignalKind;
|
||||||
|
|
||||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
#[cfg(not(unix))]
|
#[cfg(not(unix))]
|
||||||
{
|
{
|
||||||
self.signals.as_mut().poll(cx).map(|_| Signal::Int)
|
self.signals.as_mut().poll(cx).map(|_| SignalKind::Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
for (sig, fut) in self.signals.iter_mut() {
|
for (sig, fut) in self.signals.iter_mut() {
|
||||||
// TODO: match on if let Some ?
|
|
||||||
if Pin::new(fut).poll_recv(cx).is_ready() {
|
if Pin::new(fut).poll_recv(cx).is_ready() {
|
||||||
trace!("{} received", sig);
|
trace!("{} received", sig);
|
||||||
return Poll::Ready(*sig);
|
return Poll::Ready(*sig);
|
||||||
|
@@ -159,24 +159,24 @@ pub enum MioStream {
|
|||||||
Uds(mio::net::UnixStream),
|
Uds(mio::net::UnixStream),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// helper trait for converting mio stream to tokio stream.
|
/// Helper trait for converting a Mio stream into a Tokio stream.
|
||||||
pub trait FromStream: Sized {
|
pub trait FromStream: Sized {
|
||||||
fn from_mio(sock: MioStream) -> io::Result<Self>;
|
fn from_mio(sock: MioStream) -> io::Result<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
mod win_impl {
|
mod win_impl {
|
||||||
use super::*;
|
|
||||||
|
|
||||||
use std::os::windows::io::{FromRawSocket, IntoRawSocket};
|
use std::os::windows::io::{FromRawSocket, IntoRawSocket};
|
||||||
|
|
||||||
// FIXME: This is a workaround and we need an efficient way to convert between mio and tokio stream
|
use super::*;
|
||||||
|
|
||||||
|
// TODO: This is a workaround and we need an efficient way to convert between Mio and Tokio stream
|
||||||
impl FromStream for TcpStream {
|
impl FromStream for TcpStream {
|
||||||
fn from_mio(sock: MioStream) -> io::Result<Self> {
|
fn from_mio(sock: MioStream) -> io::Result<Self> {
|
||||||
match sock {
|
match sock {
|
||||||
MioStream::Tcp(mio) => {
|
MioStream::Tcp(mio) => {
|
||||||
let raw = IntoRawSocket::into_raw_socket(mio);
|
let raw = IntoRawSocket::into_raw_socket(mio);
|
||||||
// SAFETY: This is a in place conversion from mio stream to tokio stream.
|
// SAFETY: This is an in-place conversion from Mio stream to Tokio stream.
|
||||||
TcpStream::from_std(unsafe { FromRawSocket::from_raw_socket(raw) })
|
TcpStream::from_std(unsafe { FromRawSocket::from_raw_socket(raw) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -186,19 +186,19 @@ mod win_impl {
|
|||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
mod unix_impl {
|
mod unix_impl {
|
||||||
use super::*;
|
|
||||||
|
|
||||||
use std::os::unix::io::{FromRawFd, IntoRawFd};
|
use std::os::unix::io::{FromRawFd, IntoRawFd};
|
||||||
|
|
||||||
use actix_rt::net::UnixStream;
|
use actix_rt::net::UnixStream;
|
||||||
|
|
||||||
// FIXME: This is a workaround and we need an efficient way to convert between mio and tokio stream
|
use super::*;
|
||||||
|
|
||||||
|
// HACK: This is a workaround and we need an efficient way to convert between Mio and Tokio stream
|
||||||
impl FromStream for TcpStream {
|
impl FromStream for TcpStream {
|
||||||
fn from_mio(sock: MioStream) -> io::Result<Self> {
|
fn from_mio(sock: MioStream) -> io::Result<Self> {
|
||||||
match sock {
|
match sock {
|
||||||
MioStream::Tcp(mio) => {
|
MioStream::Tcp(mio) => {
|
||||||
let raw = IntoRawFd::into_raw_fd(mio);
|
let raw = IntoRawFd::into_raw_fd(mio);
|
||||||
// SAFETY: This is a in place conversion from mio stream to tokio stream.
|
// SAFETY: This is an in-place conversion from Mio stream to Tokio stream.
|
||||||
TcpStream::from_std(unsafe { FromRawFd::from_raw_fd(raw) })
|
TcpStream::from_std(unsafe { FromRawFd::from_raw_fd(raw) })
|
||||||
}
|
}
|
||||||
MioStream::Uds(_) => {
|
MioStream::Uds(_) => {
|
||||||
@@ -208,14 +208,14 @@ mod unix_impl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: This is a workaround and we need an efficient way to convert between mio and tokio stream
|
// HACK: This is a workaround and we need an efficient way to convert between Mio and Tokio stream
|
||||||
impl FromStream for UnixStream {
|
impl FromStream for UnixStream {
|
||||||
fn from_mio(sock: MioStream) -> io::Result<Self> {
|
fn from_mio(sock: MioStream) -> io::Result<Self> {
|
||||||
match sock {
|
match sock {
|
||||||
MioStream::Tcp(_) => panic!("Should not happen, bug in server impl"),
|
MioStream::Tcp(_) => panic!("Should not happen, bug in server impl"),
|
||||||
MioStream::Uds(mio) => {
|
MioStream::Uds(mio) => {
|
||||||
let raw = IntoRawFd::into_raw_fd(mio);
|
let raw = IntoRawFd::into_raw_fd(mio);
|
||||||
// SAFETY: This is a in place conversion from mio stream to tokio stream.
|
// SAFETY: This is an in-place conversion from Mio stream to Tokio stream.
|
||||||
UnixStream::from_std(unsafe { FromRawFd::from_raw_fd(raw) })
|
UnixStream::from_std(unsafe { FromRawFd::from_raw_fd(raw) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
use std::sync::mpsc;
|
use std::{io, net, sync::mpsc, thread};
|
||||||
use std::{io, net, thread};
|
|
||||||
|
|
||||||
use actix_rt::{net::TcpStream, System};
|
use actix_rt::{net::TcpStream, System};
|
||||||
|
|
||||||
@@ -105,12 +104,16 @@ impl TestServer {
|
|||||||
|
|
||||||
/// Get first available unused local address.
|
/// Get first available unused local address.
|
||||||
pub fn unused_addr() -> net::SocketAddr {
|
pub fn unused_addr() -> net::SocketAddr {
|
||||||
|
use socket2::{Domain, Protocol, Socket, Type};
|
||||||
|
|
||||||
let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
|
let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
|
||||||
let socket = mio::net::TcpSocket::new_v4().unwrap();
|
let socket =
|
||||||
socket.bind(addr).unwrap();
|
Socket::new(Domain::for_address(addr), Type::STREAM, Some(Protocol::TCP)).unwrap();
|
||||||
socket.set_reuseaddr(true).unwrap();
|
socket.set_reuse_address(true).unwrap();
|
||||||
let tcp = socket.listen(1024).unwrap();
|
socket.set_nonblocking(true).unwrap();
|
||||||
tcp.local_addr().unwrap()
|
socket.bind(&addr.into()).unwrap();
|
||||||
|
socket.listen(1024).unwrap();
|
||||||
|
net::TcpListener::from(socket).local_addr().unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,3 +150,16 @@ impl Drop for TestServerRuntime {
|
|||||||
self.stop()
|
self.stop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use actix_service::fn_service;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn plain_tokio_runtime() {
|
||||||
|
let srv = TestServer::with(|| fn_service(|_sock| async move { Ok::<_, ()>(()) }));
|
||||||
|
assert!(srv.connect().is_ok());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -24,7 +24,6 @@ use tokio::sync::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
join_all::join_all_local,
|
|
||||||
service::{BoxedServerService, InternalServiceFactory},
|
service::{BoxedServerService, InternalServiceFactory},
|
||||||
socket::MioStream,
|
socket::MioStream,
|
||||||
waker_queue::{WakerInterest, WakerQueue},
|
waker_queue::{WakerInterest, WakerQueue},
|
||||||
@@ -202,8 +201,8 @@ impl WorkerHandleServer {
|
|||||||
pub(crate) struct ServerWorker {
|
pub(crate) struct ServerWorker {
|
||||||
// UnboundedReceiver<Conn> should always be the first field.
|
// UnboundedReceiver<Conn> should always be the first field.
|
||||||
// It must be dropped as soon as ServerWorker dropping.
|
// It must be dropped as soon as ServerWorker dropping.
|
||||||
rx: UnboundedReceiver<Conn>,
|
conn_rx: UnboundedReceiver<Conn>,
|
||||||
rx2: UnboundedReceiver<Stop>,
|
stop_rx: UnboundedReceiver<Stop>,
|
||||||
counter: WorkerCounter,
|
counter: WorkerCounter,
|
||||||
services: Box<[WorkerService]>,
|
services: Box<[WorkerService]>,
|
||||||
factories: Box<[Box<dyn InternalServiceFactory>]>,
|
factories: Box<[Box<dyn InternalServiceFactory>]>,
|
||||||
@@ -212,7 +211,7 @@ pub(crate) struct ServerWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct WorkerService {
|
struct WorkerService {
|
||||||
factory: usize,
|
factory_idx: usize,
|
||||||
status: WorkerServiceStatus,
|
status: WorkerServiceStatus,
|
||||||
service: BoxedServerService,
|
service: BoxedServerService,
|
||||||
}
|
}
|
||||||
@@ -234,6 +233,12 @@ enum WorkerServiceStatus {
|
|||||||
Stopped,
|
Stopped,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for WorkerServiceStatus {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Unavailable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Config for worker behavior passed down from server builder.
|
/// Config for worker behavior passed down from server builder.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub(crate) struct ServerWorkerConfig {
|
pub(crate) struct ServerWorkerConfig {
|
||||||
@@ -277,111 +282,196 @@ impl ServerWorker {
|
|||||||
) -> io::Result<(WorkerHandleAccept, WorkerHandleServer)> {
|
) -> io::Result<(WorkerHandleAccept, WorkerHandleServer)> {
|
||||||
trace!("starting server worker {}", idx);
|
trace!("starting server worker {}", idx);
|
||||||
|
|
||||||
let (tx1, rx) = unbounded_channel();
|
let (tx1, conn_rx) = unbounded_channel();
|
||||||
let (tx2, rx2) = unbounded_channel();
|
let (tx2, stop_rx) = unbounded_channel();
|
||||||
|
|
||||||
let counter = Counter::new(config.max_concurrent_connections);
|
let counter = Counter::new(config.max_concurrent_connections);
|
||||||
|
let pair = handle_pair(idx, tx1, tx2, counter.clone());
|
||||||
let counter_clone = counter.clone();
|
|
||||||
|
|
||||||
// get actix system context if it is set
|
// get actix system context if it is set
|
||||||
let sys = System::try_current();
|
let actix_system = System::try_current();
|
||||||
|
|
||||||
|
// get tokio runtime handle if it is set
|
||||||
|
let tokio_handle = tokio::runtime::Handle::try_current().ok();
|
||||||
|
|
||||||
// service factories initialization channel
|
// service factories initialization channel
|
||||||
let (factory_tx, factory_rx) = std::sync::mpsc::sync_channel(1);
|
let (factory_tx, factory_rx) = std::sync::mpsc::sync_channel::<io::Result<()>>(1);
|
||||||
|
|
||||||
|
// outline of following code:
|
||||||
|
//
|
||||||
|
// if system exists
|
||||||
|
// if uring enabled
|
||||||
|
// start arbiter using uring method
|
||||||
|
// else
|
||||||
|
// start arbiter with regular tokio
|
||||||
|
// else
|
||||||
|
// if uring enabled
|
||||||
|
// start uring in spawned thread
|
||||||
|
// else
|
||||||
|
// start regular tokio in spawned thread
|
||||||
|
|
||||||
// every worker runs in it's own thread and tokio runtime.
|
// every worker runs in it's own thread and tokio runtime.
|
||||||
// use a custom tokio runtime builder to change the settings of runtime.
|
// use a custom tokio runtime builder to change the settings of runtime.
|
||||||
std::thread::Builder::new()
|
|
||||||
.name(format!("actix-server worker {}", idx))
|
|
||||||
.spawn(move || {
|
|
||||||
// forward existing actix system context
|
|
||||||
if let Some(sys) = sys {
|
|
||||||
System::set_current(sys);
|
|
||||||
}
|
|
||||||
|
|
||||||
let worker_fut = async move {
|
match (actix_system, tokio_handle) {
|
||||||
let fut = factories
|
(None, None) => {
|
||||||
.iter()
|
panic!("No runtime detected. Start a Tokio (or Actix) runtime.");
|
||||||
.enumerate()
|
}
|
||||||
.map(|(idx, factory)| {
|
|
||||||
let fut = factory.create();
|
|
||||||
async move { fut.await.map(|(t, s)| (idx, t, s)) }
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
// a second spawn to run !Send future tasks.
|
// no actix system
|
||||||
spawn(async move {
|
(None, Some(rt_handle)) => {
|
||||||
let res = join_all_local(fut)
|
std::thread::Builder::new()
|
||||||
.await
|
.name(format!("actix-server worker {}", idx))
|
||||||
.into_iter()
|
.spawn(move || {
|
||||||
.collect::<Result<Vec<_>, _>>();
|
let (worker_stopped_tx, worker_stopped_rx) = oneshot::channel();
|
||||||
|
|
||||||
let services = match res {
|
// local set for running service init futures and worker services
|
||||||
Ok(res) => res
|
let ls = tokio::task::LocalSet::new();
|
||||||
.into_iter()
|
|
||||||
.fold(Vec::new(), |mut services, (factory, token, service)| {
|
|
||||||
assert_eq!(token, services.len());
|
|
||||||
services.push(WorkerService {
|
|
||||||
factory,
|
|
||||||
service,
|
|
||||||
status: WorkerServiceStatus::Unavailable,
|
|
||||||
});
|
|
||||||
services
|
|
||||||
})
|
|
||||||
.into_boxed_slice(),
|
|
||||||
|
|
||||||
Err(e) => {
|
// init services using existing Tokio runtime (so probably on main thread)
|
||||||
error!("Can not start worker: {:?}", e);
|
let services = rt_handle.block_on(ls.run_until(async {
|
||||||
Arbiter::try_current().as_ref().map(ArbiterHandle::stop);
|
let mut services = Vec::new();
|
||||||
|
|
||||||
|
for (idx, factory) in factories.iter().enumerate() {
|
||||||
|
match factory.create().await {
|
||||||
|
Ok((token, svc)) => services.push((idx, token, svc)),
|
||||||
|
|
||||||
|
Err(err) => {
|
||||||
|
error!("Can not start worker: {:?}", err);
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
format!("can not start server service {}", idx),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(services)
|
||||||
|
}));
|
||||||
|
|
||||||
|
let services = match services {
|
||||||
|
Ok(services) => {
|
||||||
|
factory_tx.send(Ok(())).unwrap();
|
||||||
|
services
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
factory_tx.send(Err(err)).unwrap();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
factory_tx.send(()).unwrap();
|
let worker_services = wrap_worker_services(services);
|
||||||
|
|
||||||
// a third spawn to make sure ServerWorker runs as non boxed future.
|
let worker_fut = async move {
|
||||||
spawn(ServerWorker {
|
// spawn to make sure ServerWorker runs as non boxed future.
|
||||||
rx,
|
spawn(async move {
|
||||||
rx2,
|
ServerWorker {
|
||||||
services,
|
conn_rx,
|
||||||
counter: WorkerCounter::new(idx, waker_queue, counter_clone),
|
stop_rx,
|
||||||
factories: factories.into_boxed_slice(),
|
services: worker_services.into_boxed_slice(),
|
||||||
state: Default::default(),
|
counter: WorkerCounter::new(idx, waker_queue, counter),
|
||||||
shutdown_timeout: config.shutdown_timeout,
|
factories: factories.into_boxed_slice(),
|
||||||
})
|
state: WorkerState::default(),
|
||||||
.await
|
shutdown_timeout: config.shutdown_timeout,
|
||||||
.expect("task 3 panic");
|
}
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// wake up outermost task waiting for shutdown
|
||||||
|
worker_stopped_tx.send(()).unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
worker_stopped_rx.await.unwrap();
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(all(target_os = "linux", feature = "io-uring"))]
|
||||||
|
{
|
||||||
|
// TODO: pass max blocking thread config when tokio-uring enable configuration
|
||||||
|
// on building runtime.
|
||||||
|
let _ = config.max_blocking_threads;
|
||||||
|
tokio_uring::start(worker_fut);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(all(target_os = "linux", feature = "io-uring")))]
|
||||||
|
{
|
||||||
|
let rt = tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.max_blocking_threads(config.max_blocking_threads)
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
rt.block_on(ls.run_until(worker_fut));
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.await
|
.expect("cannot spawn server worker thread");
|
||||||
.expect("task 2 panic");
|
}
|
||||||
};
|
|
||||||
|
|
||||||
|
// with actix system
|
||||||
|
(Some(_sys), _) => {
|
||||||
#[cfg(all(target_os = "linux", feature = "io-uring"))]
|
#[cfg(all(target_os = "linux", feature = "io-uring"))]
|
||||||
{
|
let arbiter = {
|
||||||
// TODO: pass max blocking thread config when tokio-uring enable configuration
|
// TODO: pass max blocking thread config when tokio-uring enable configuration
|
||||||
// on building runtime.
|
// on building runtime.
|
||||||
let _ = config.max_blocking_threads;
|
let _ = config.max_blocking_threads;
|
||||||
tokio_uring::start(worker_fut)
|
Arbiter::new()
|
||||||
}
|
};
|
||||||
|
|
||||||
#[cfg(not(all(target_os = "linux", feature = "io-uring")))]
|
#[cfg(not(all(target_os = "linux", feature = "io-uring")))]
|
||||||
{
|
let arbiter = {
|
||||||
let rt = tokio::runtime::Builder::new_current_thread()
|
Arbiter::with_tokio_rt(move || {
|
||||||
.enable_all()
|
tokio::runtime::Builder::new_current_thread()
|
||||||
.max_blocking_threads(config.max_blocking_threads)
|
.enable_all()
|
||||||
.build()
|
.max_blocking_threads(config.max_blocking_threads)
|
||||||
.unwrap();
|
.build()
|
||||||
|
.unwrap()
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
rt.block_on(tokio::task::LocalSet::new().run_until(worker_fut))
|
arbiter.spawn(async move {
|
||||||
}
|
// spawn_local to run !Send future tasks.
|
||||||
})
|
spawn(async move {
|
||||||
.expect("worker thread error/panic");
|
let mut services = Vec::new();
|
||||||
|
|
||||||
|
for (idx, factory) in factories.iter().enumerate() {
|
||||||
|
match factory.create().await {
|
||||||
|
Ok((token, svc)) => services.push((idx, token, svc)),
|
||||||
|
|
||||||
|
Err(err) => {
|
||||||
|
error!("Can not start worker: {:?}", err);
|
||||||
|
Arbiter::current().stop();
|
||||||
|
factory_tx
|
||||||
|
.send(Err(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
format!("can not start server service {}", idx),
|
||||||
|
)))
|
||||||
|
.unwrap();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
factory_tx.send(Ok(())).unwrap();
|
||||||
|
|
||||||
|
let worker_services = wrap_worker_services(services);
|
||||||
|
|
||||||
|
// spawn to make sure ServerWorker runs as non boxed future.
|
||||||
|
spawn(ServerWorker {
|
||||||
|
conn_rx,
|
||||||
|
stop_rx,
|
||||||
|
services: worker_services.into_boxed_slice(),
|
||||||
|
counter: WorkerCounter::new(idx, waker_queue, counter),
|
||||||
|
factories: factories.into_boxed_slice(),
|
||||||
|
state: Default::default(),
|
||||||
|
shutdown_timeout: config.shutdown_timeout,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// wait for service factories initialization
|
// wait for service factories initialization
|
||||||
factory_rx.recv().unwrap();
|
factory_rx.recv().unwrap()?;
|
||||||
|
|
||||||
Ok(handle_pair(idx, tx1, tx2, counter))
|
Ok(pair)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn restart_service(&mut self, idx: usize, factory_id: usize) {
|
fn restart_service(&mut self, idx: usize, factory_id: usize) {
|
||||||
@@ -419,7 +509,7 @@ impl ServerWorker {
|
|||||||
if srv.status == WorkerServiceStatus::Unavailable {
|
if srv.status == WorkerServiceStatus::Unavailable {
|
||||||
trace!(
|
trace!(
|
||||||
"Service {:?} is available",
|
"Service {:?} is available",
|
||||||
self.factories[srv.factory].name(idx)
|
self.factories[srv.factory_idx].name(idx)
|
||||||
);
|
);
|
||||||
srv.status = WorkerServiceStatus::Available;
|
srv.status = WorkerServiceStatus::Available;
|
||||||
}
|
}
|
||||||
@@ -430,7 +520,7 @@ impl ServerWorker {
|
|||||||
if srv.status == WorkerServiceStatus::Available {
|
if srv.status == WorkerServiceStatus::Available {
|
||||||
trace!(
|
trace!(
|
||||||
"Service {:?} is unavailable",
|
"Service {:?} is unavailable",
|
||||||
self.factories[srv.factory].name(idx)
|
self.factories[srv.factory_idx].name(idx)
|
||||||
);
|
);
|
||||||
srv.status = WorkerServiceStatus::Unavailable;
|
srv.status = WorkerServiceStatus::Unavailable;
|
||||||
}
|
}
|
||||||
@@ -438,10 +528,10 @@ impl ServerWorker {
|
|||||||
Poll::Ready(Err(_)) => {
|
Poll::Ready(Err(_)) => {
|
||||||
error!(
|
error!(
|
||||||
"Service {:?} readiness check returned error, restarting",
|
"Service {:?} readiness check returned error, restarting",
|
||||||
self.factories[srv.factory].name(idx)
|
self.factories[srv.factory_idx].name(idx)
|
||||||
);
|
);
|
||||||
srv.status = WorkerServiceStatus::Failed;
|
srv.status = WorkerServiceStatus::Failed;
|
||||||
return Err((idx, srv.factory));
|
return Err((idx, srv.factory_idx));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -484,7 +574,6 @@ impl Default for WorkerState {
|
|||||||
|
|
||||||
impl Drop for ServerWorker {
|
impl Drop for ServerWorker {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
trace!("stopping ServerWorker Arbiter");
|
|
||||||
Arbiter::try_current().as_ref().map(ArbiterHandle::stop);
|
Arbiter::try_current().as_ref().map(ArbiterHandle::stop);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -496,7 +585,8 @@ impl Future for ServerWorker {
|
|||||||
let this = self.as_mut().get_mut();
|
let this = self.as_mut().get_mut();
|
||||||
|
|
||||||
// `StopWorker` message handler
|
// `StopWorker` message handler
|
||||||
if let Poll::Ready(Some(Stop { graceful, tx })) = Pin::new(&mut this.rx2).poll_recv(cx)
|
if let Poll::Ready(Some(Stop { graceful, tx })) =
|
||||||
|
Pin::new(&mut this.stop_rx).poll_recv(cx)
|
||||||
{
|
{
|
||||||
let num = this.counter.total();
|
let num = this.counter.total();
|
||||||
if num == 0 {
|
if num == 0 {
|
||||||
@@ -559,7 +649,7 @@ impl Future for ServerWorker {
|
|||||||
}
|
}
|
||||||
WorkerState::Shutdown(ref mut shutdown) => {
|
WorkerState::Shutdown(ref mut shutdown) => {
|
||||||
// drop all pending connections in rx channel.
|
// drop all pending connections in rx channel.
|
||||||
while let Poll::Ready(Some(conn)) = Pin::new(&mut this.rx).poll_recv(cx) {
|
while let Poll::Ready(Some(conn)) = Pin::new(&mut this.conn_rx).poll_recv(cx) {
|
||||||
// WorkerCounterGuard is needed as Accept thread has incremented counter.
|
// WorkerCounterGuard is needed as Accept thread has incremented counter.
|
||||||
// It's guard's job to decrement the counter together with drop of Conn.
|
// It's guard's job to decrement the counter together with drop of Conn.
|
||||||
let guard = this.counter.guard();
|
let guard = this.counter.guard();
|
||||||
@@ -606,7 +696,7 @@ impl Future for ServerWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handle incoming io stream
|
// handle incoming io stream
|
||||||
match ready!(Pin::new(&mut this.rx).poll_recv(cx)) {
|
match ready!(Pin::new(&mut this.conn_rx).poll_recv(cx)) {
|
||||||
Some(msg) => {
|
Some(msg) => {
|
||||||
let guard = this.counter.guard();
|
let guard = this.counter.guard();
|
||||||
let _ = this.services[msg.token].service.call((guard, msg.io));
|
let _ = this.services[msg.token].service.call((guard, msg.io));
|
||||||
@@ -617,3 +707,19 @@ impl Future for ServerWorker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn wrap_worker_services(
|
||||||
|
services: Vec<(usize, usize, BoxedServerService)>,
|
||||||
|
) -> Vec<WorkerService> {
|
||||||
|
services
|
||||||
|
.into_iter()
|
||||||
|
.fold(Vec::new(), |mut services, (idx, token, service)| {
|
||||||
|
assert_eq!(token, services.len());
|
||||||
|
services.push(WorkerService {
|
||||||
|
factory_idx: idx,
|
||||||
|
service,
|
||||||
|
status: WorkerServiceStatus::Unavailable,
|
||||||
|
});
|
||||||
|
services
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@@ -1,21 +1,19 @@
|
|||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::{
|
||||||
use std::sync::{mpsc, Arc};
|
net,
|
||||||
use std::{net, thread, time::Duration};
|
sync::{
|
||||||
|
atomic::{AtomicUsize, Ordering},
|
||||||
|
mpsc, Arc,
|
||||||
|
},
|
||||||
|
thread,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
use actix_rt::{net::TcpStream, time::sleep};
|
use actix_rt::{net::TcpStream, time::sleep};
|
||||||
use actix_server::Server;
|
use actix_server::{Server, TestServer};
|
||||||
use actix_service::fn_service;
|
use actix_service::fn_service;
|
||||||
use socket2::{Domain, Protocol, Socket, Type};
|
|
||||||
|
|
||||||
fn unused_addr() -> net::SocketAddr {
|
fn unused_addr() -> net::SocketAddr {
|
||||||
let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
|
TestServer::unused_addr()
|
||||||
let socket =
|
|
||||||
Socket::new(Domain::for_address(addr), Type::STREAM, Some(Protocol::TCP)).unwrap();
|
|
||||||
socket.set_reuse_address(true).unwrap();
|
|
||||||
socket.set_nonblocking(true).unwrap();
|
|
||||||
socket.bind(&addr.into()).unwrap();
|
|
||||||
socket.listen(32).unwrap();
|
|
||||||
net::TcpListener::from(socket).local_addr().unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -33,28 +31,63 @@ fn test_bind() {
|
|||||||
})?
|
})?
|
||||||
.run();
|
.run();
|
||||||
|
|
||||||
let _ = tx.send((srv.handle(), actix_rt::System::current()));
|
let _ = tx.send(srv.handle());
|
||||||
|
|
||||||
srv.await
|
srv.await
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
let (srv, sys) = rx.recv().unwrap();
|
let srv = rx.recv().unwrap();
|
||||||
|
|
||||||
|
thread::sleep(Duration::from_millis(500));
|
||||||
|
assert!(net::TcpStream::connect(addr).is_ok());
|
||||||
|
|
||||||
|
let _ = srv.stop(true);
|
||||||
|
h.join().unwrap().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn plain_tokio_runtime() {
|
||||||
|
let addr = unused_addr();
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
|
||||||
|
let h = thread::spawn(move || {
|
||||||
|
let rt = tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
rt.block_on(async {
|
||||||
|
let srv = Server::build()
|
||||||
|
.workers(1)
|
||||||
|
.disable_signals()
|
||||||
|
.bind("test", addr, move || {
|
||||||
|
fn_service(|_| async { Ok::<_, ()>(()) })
|
||||||
|
})?
|
||||||
|
.run();
|
||||||
|
|
||||||
|
tx.send(srv.handle()).unwrap();
|
||||||
|
|
||||||
|
srv.await
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let srv = rx.recv().unwrap();
|
||||||
|
|
||||||
thread::sleep(Duration::from_millis(500));
|
thread::sleep(Duration::from_millis(500));
|
||||||
assert!(net::TcpStream::connect(addr).is_ok());
|
assert!(net::TcpStream::connect(addr).is_ok());
|
||||||
|
|
||||||
let _ = srv.stop(true);
|
let _ = srv.stop(true);
|
||||||
sys.stop();
|
|
||||||
h.join().unwrap().unwrap();
|
h.join().unwrap().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_listen() {
|
fn test_listen() {
|
||||||
let addr = unused_addr();
|
let addr = unused_addr();
|
||||||
|
let lst = net::TcpListener::bind(addr).unwrap();
|
||||||
|
|
||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
|
|
||||||
let h = thread::spawn(move || {
|
let h = thread::spawn(move || {
|
||||||
let lst = net::TcpListener::bind(addr)?;
|
|
||||||
actix_rt::System::new().block_on(async {
|
actix_rt::System::new().block_on(async {
|
||||||
let srv = Server::build()
|
let srv = Server::build()
|
||||||
.disable_signals()
|
.disable_signals()
|
||||||
@@ -64,19 +97,18 @@ fn test_listen() {
|
|||||||
})?
|
})?
|
||||||
.run();
|
.run();
|
||||||
|
|
||||||
let _ = tx.send((srv.handle(), actix_rt::System::current()));
|
let _ = tx.send(srv.handle());
|
||||||
|
|
||||||
srv.await
|
srv.await
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
let (srv, sys) = rx.recv().unwrap();
|
let srv = rx.recv().unwrap();
|
||||||
|
|
||||||
thread::sleep(Duration::from_millis(500));
|
thread::sleep(Duration::from_millis(500));
|
||||||
assert!(net::TcpStream::connect(addr).is_ok());
|
assert!(net::TcpStream::connect(addr).is_ok());
|
||||||
|
|
||||||
let _ = srv.stop(true);
|
let _ = srv.stop(true);
|
||||||
sys.stop();
|
|
||||||
h.join().unwrap().unwrap();
|
h.join().unwrap().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,12 +315,12 @@ async fn test_service_restart() {
|
|||||||
.workers(1)
|
.workers(1)
|
||||||
.run();
|
.run();
|
||||||
|
|
||||||
let _ = tx.send((srv.handle(), actix_rt::System::current()));
|
let _ = tx.send(srv.handle());
|
||||||
srv.await
|
srv.await
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
let (srv, sys) = rx.recv().unwrap();
|
let srv = rx.recv().unwrap();
|
||||||
|
|
||||||
for _ in 0..5 {
|
for _ in 0..5 {
|
||||||
TcpStream::connect(addr1)
|
TcpStream::connect(addr1)
|
||||||
@@ -311,7 +343,6 @@ async fn test_service_restart() {
|
|||||||
assert!(num2_clone.load(Ordering::SeqCst) > 5);
|
assert!(num2_clone.load(Ordering::SeqCst) > 5);
|
||||||
|
|
||||||
let _ = srv.stop(false);
|
let _ = srv.stop(false);
|
||||||
sys.stop();
|
|
||||||
h.join().unwrap().unwrap();
|
h.join().unwrap().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,13 +419,13 @@ async fn worker_restart() {
|
|||||||
.workers(2)
|
.workers(2)
|
||||||
.run();
|
.run();
|
||||||
|
|
||||||
let _ = tx.send((srv.handle(), actix_rt::System::current()));
|
let _ = tx.send(srv.handle());
|
||||||
|
|
||||||
srv.await
|
srv.await
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
let (srv, sys) = rx.recv().unwrap();
|
let srv = rx.recv().unwrap();
|
||||||
|
|
||||||
sleep(Duration::from_secs(3)).await;
|
sleep(Duration::from_secs(3)).await;
|
||||||
|
|
||||||
@@ -452,6 +483,31 @@ async fn worker_restart() {
|
|||||||
stream.shutdown().await.unwrap();
|
stream.shutdown().await.unwrap();
|
||||||
|
|
||||||
let _ = srv.stop(false);
|
let _ = srv.stop(false);
|
||||||
sys.stop();
|
|
||||||
h.join().unwrap().unwrap();
|
h.join().unwrap().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn no_runtime() {
|
||||||
|
// test set up in a way that would prevent time out if support for runtime-less init was added
|
||||||
|
|
||||||
|
let addr = unused_addr();
|
||||||
|
|
||||||
|
let srv = Server::build()
|
||||||
|
.workers(1)
|
||||||
|
.disable_signals()
|
||||||
|
.bind("test", addr, move || {
|
||||||
|
fn_service(|_| async { Ok::<_, ()>(()) })
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
.run();
|
||||||
|
|
||||||
|
let rt = tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let _ = srv.handle().stop(true);
|
||||||
|
|
||||||
|
rt.block_on(async { srv.await }).unwrap();
|
||||||
|
}
|
||||||
|
@@ -56,221 +56,124 @@
|
|||||||
|
|
||||||
|
|
||||||
## 1.0.6 - 2020-08-09
|
## 1.0.6 - 2020-08-09
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
* Removed unsound custom Cell implementation that allowed obtaining several mutable references to
|
* Removed unsound custom Cell implementation that allowed obtaining several mutable references to
|
||||||
the same data, which is undefined behavior in Rust and could lead to violations of memory safety. External code could obtain several mutable references to the same data through
|
the same data, which is undefined behavior in Rust and could lead to violations of memory safety. External code could obtain several mutable references to the same data through
|
||||||
service combinators. Attempts to acquire several mutable references to the same data will instead
|
service combinators. Attempts to acquire several mutable references to the same data will instead
|
||||||
result in a panic.
|
result in a panic.
|
||||||
|
|
||||||
## [1.0.5] - 2020-01-16
|
|
||||||
|
|
||||||
### Fixed
|
## 1.0.5 - 2020-01-16
|
||||||
|
* Fixed unsoundness in .and_then()/.then() service combinators.
|
||||||
|
|
||||||
* Fixed unsoundness in .and_then()/.then() service combinators
|
|
||||||
|
|
||||||
## [1.0.4] - 2020-01-15
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
|
## 1.0.4 - 2020-01-15
|
||||||
* Revert 1.0.3 change
|
* Revert 1.0.3 change
|
||||||
|
|
||||||
## [1.0.3] - 2020-01-15
|
|
||||||
|
|
||||||
### Fixed
|
## 1.0.3 - 2020-01-15
|
||||||
|
* Fixed unsoundness in `AndThenService` impl.
|
||||||
* Fixed unsoundness in `AndThenService` impl
|
|
||||||
|
|
||||||
## [1.0.2] - 2020-01-08
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
* Add `into_service` helper function
|
|
||||||
|
|
||||||
|
|
||||||
## [1.0.1] - 2019-12-22
|
## 1.0.2 - 2020-01-08
|
||||||
|
* Add `into_service` helper function.
|
||||||
### Changed
|
|
||||||
|
|
||||||
* `map_config()` and `unit_config()` accepts `IntoServiceFactory` type
|
|
||||||
|
|
||||||
|
|
||||||
## [1.0.0] - 2019-12-11
|
## 1.0.1 - 2019-12-22
|
||||||
|
* `map_config()` and `unit_config()` now accept `IntoServiceFactory` type.
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
|
## 1.0.0 - 2019-12-11
|
||||||
* Add Clone impl for Apply service
|
* Add Clone impl for Apply service
|
||||||
|
|
||||||
|
|
||||||
## [1.0.0-alpha.4] - 2019-12-08
|
## 1.0.0-alpha.4 - 2019-12-08
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
* Renamed `service_fn` to `fn_service`
|
* Renamed `service_fn` to `fn_service`
|
||||||
|
|
||||||
* Renamed `factory_fn` to `fn_factory`
|
* Renamed `factory_fn` to `fn_factory`
|
||||||
|
|
||||||
* Renamed `factory_fn_cfg` to `fn_factory_with_config`
|
* Renamed `factory_fn_cfg` to `fn_factory_with_config`
|
||||||
|
|
||||||
|
|
||||||
## [1.0.0-alpha.3] - 2019-12-06
|
## 1.0.0-alpha.3 - 2019-12-06
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
* Add missing Clone impls
|
* Add missing Clone impls
|
||||||
|
|
||||||
* Restore `Transform::map_init_err()` combinator
|
* Restore `Transform::map_init_err()` combinator
|
||||||
|
|
||||||
* Restore `Service/Factory::apply_fn()` in form of `Pipeline/Factory::and_then_apply_fn()`
|
* Restore `Service/Factory::apply_fn()` in form of `Pipeline/Factory::and_then_apply_fn()`
|
||||||
|
|
||||||
* Optimize service combinators and futures memory layout
|
* Optimize service combinators and futures memory layout
|
||||||
|
|
||||||
|
|
||||||
## [1.0.0-alpha.2] - 2019-12-02
|
## 1.0.0-alpha.2 - 2019-12-02
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
* Use owned config value for service factory
|
* Use owned config value for service factory
|
||||||
|
|
||||||
* Renamed BoxedNewService/BoxedService to BoxServiceFactory/BoxService
|
* Renamed BoxedNewService/BoxedService to BoxServiceFactory/BoxService
|
||||||
|
|
||||||
|
|
||||||
## [1.0.0-alpha.1] - 2019-11-25
|
## 1.0.0-alpha.1 - 2019-11-25
|
||||||
|
* Migrated to `std::future`
|
||||||
### Changed
|
|
||||||
|
|
||||||
* Migraded to `std::future`
|
|
||||||
|
|
||||||
* `NewService` renamed to `ServiceFactory`
|
* `NewService` renamed to `ServiceFactory`
|
||||||
|
|
||||||
* Added `pipeline` and `pipeline_factory` function
|
* Added `pipeline` and `pipeline_factory` function
|
||||||
|
|
||||||
|
|
||||||
## [0.4.2] - 2019-08-27
|
## 0.4.2 - 2019-08-27
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
* Check service readiness for `new_apply_cfg` combinator
|
* Check service readiness for `new_apply_cfg` combinator
|
||||||
|
|
||||||
|
|
||||||
## [0.4.1] - 2019-06-06
|
## 0.4.1 - 2019-06-06
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
* Add `new_apply_cfg` function
|
* Add `new_apply_cfg` function
|
||||||
|
|
||||||
## [0.4.0] - 2019-05-12
|
|
||||||
|
|
||||||
### Changed
|
## 0.4.0 - 2019-05-12
|
||||||
|
* Add `NewService::map_config` and `NewService::unit_config` combinators.
|
||||||
* Use associated type for `NewService` config
|
* Use associated type for `NewService` config.
|
||||||
|
* Change `apply_cfg` function.
|
||||||
* Change `apply_cfg` function
|
* Renamed helper functions.
|
||||||
|
|
||||||
* Renamed helper functions
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
* Add `NewService::map_config` and `NewService::unit_config` combinators
|
|
||||||
|
|
||||||
|
|
||||||
## [0.3.6] - 2019-04-07
|
## 0.3.6 - 2019-04-07
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
* Poll boxed service call result immediately
|
* Poll boxed service call result immediately
|
||||||
|
|
||||||
|
|
||||||
## [0.3.5] - 2019-03-29
|
## 0.3.5 - 2019-03-29
|
||||||
|
* Add `impl<S: Service> Service for Rc<RefCell<S>>`.
|
||||||
### Added
|
|
||||||
|
|
||||||
* Add `impl<S: Service> Service for Rc<RefCell<S>>`
|
|
||||||
|
|
||||||
|
|
||||||
## [0.3.4] - 2019-03-12
|
## 0.3.4 - 2019-03-12
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
* Add `Transform::from_err()` combinator
|
* Add `Transform::from_err()` combinator
|
||||||
|
|
||||||
* Add `apply_fn` helper
|
* Add `apply_fn` helper
|
||||||
|
|
||||||
* Add `apply_fn_factory` helper
|
* Add `apply_fn_factory` helper
|
||||||
|
|
||||||
* Add `apply_transform` helper
|
* Add `apply_transform` helper
|
||||||
|
|
||||||
* Add `apply_cfg` helper
|
* Add `apply_cfg` helper
|
||||||
|
|
||||||
|
|
||||||
## [0.3.3] - 2019-03-09
|
## 0.3.3 - 2019-03-09
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
* Add `ApplyTransform` new service for transform and new service.
|
* Add `ApplyTransform` new service for transform and new service.
|
||||||
|
* Add `NewService::apply_cfg()` combinator, allows to use nested `NewService` with different config parameter.
|
||||||
* Add `NewService::apply_cfg()` combinator, allows to use
|
|
||||||
nested `NewService` with different config parameter.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
* Revert IntoFuture change
|
* Revert IntoFuture change
|
||||||
|
|
||||||
|
|
||||||
## [0.3.2] - 2019-03-04
|
## 0.3.2 - 2019-03-04
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
* Change `NewService::Future` and `Transform::Future` to the `IntoFuture` trait.
|
* Change `NewService::Future` and `Transform::Future` to the `IntoFuture` trait.
|
||||||
|
|
||||||
* Export `AndThenTransform` type
|
* Export `AndThenTransform` type
|
||||||
|
|
||||||
|
|
||||||
## [0.3.1] - 2019-03-04
|
## 0.3.1 - 2019-03-04
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
* Simplify Transform trait
|
* Simplify Transform trait
|
||||||
|
|
||||||
|
|
||||||
## [0.3.0] - 2019-03-02
|
## 0.3.0 - 2019-03-02
|
||||||
|
|
||||||
## Added
|
|
||||||
|
|
||||||
* Added boxed NewService and Service.
|
* Added boxed NewService and Service.
|
||||||
|
|
||||||
## Changed
|
|
||||||
|
|
||||||
* Added `Config` parameter to `NewService` trait.
|
* Added `Config` parameter to `NewService` trait.
|
||||||
|
|
||||||
* Added `Config` parameter to `NewTransform` trait.
|
* Added `Config` parameter to `NewTransform` trait.
|
||||||
|
|
||||||
|
|
||||||
## [0.2.2] - 2019-02-19
|
## 0.2.2 - 2019-02-19
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
* Added `NewService` impl for `Rc<S> where S: NewService`
|
* Added `NewService` impl for `Rc<S> where S: NewService`
|
||||||
|
|
||||||
* Added `NewService` impl for `Arc<S> where S: NewService`
|
* Added `NewService` impl for `Arc<S> where S: NewService`
|
||||||
|
|
||||||
|
|
||||||
## [0.2.1] - 2019-02-03
|
## 0.2.1 - 2019-02-03
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
* Generalize `.apply` combinator with Transform trait
|
* Generalize `.apply` combinator with Transform trait
|
||||||
|
|
||||||
|
|
||||||
## [0.2.0] - 2019-02-01
|
## 0.2.0 - 2019-02-01
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
* Use associated type instead of generic for Service definition.
|
* Use associated type instead of generic for Service definition.
|
||||||
|
|
||||||
* Before:
|
* Before:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
impl Service<Request> for Client {
|
impl Service<Request> for Client {
|
||||||
type Response = Response;
|
type Response = Response;
|
||||||
@@ -278,7 +181,6 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
* After:
|
* After:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
impl Service for Client {
|
impl Service for Client {
|
||||||
type Request = Request;
|
type Request = Request;
|
||||||
@@ -288,50 +190,30 @@
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## [0.1.6] - 2019-01-24
|
## 0.1.6 - 2019-01-24
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
* Use `FnMut` instead of `Fn` for .apply() and .map() combinators and `FnService` type
|
* Use `FnMut` instead of `Fn` for .apply() and .map() combinators and `FnService` type
|
||||||
|
|
||||||
* Change `.apply()` error semantic, new service's error is `From<Self::Error>`
|
* Change `.apply()` error semantic, new service's error is `From<Self::Error>`
|
||||||
|
|
||||||
|
|
||||||
## [0.1.5] - 2019-01-13
|
## 0.1.5 - 2019-01-13
|
||||||
|
* Make `Out::Error` convertible from `T::Error` for apply combinator
|
||||||
### Changed
|
|
||||||
|
|
||||||
* Make `Out::Error` convertable from `T::Error` for apply combinator
|
|
||||||
|
|
||||||
|
|
||||||
## [0.1.4] - 2019-01-11
|
## 0.1.4 - 2019-01-11
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
* Use `FnMut` instead of `Fn` for `FnService`
|
* Use `FnMut` instead of `Fn` for `FnService`
|
||||||
|
|
||||||
|
|
||||||
## [0.1.3] - 2018-12-12
|
## 0.1.3 - 2018-12-12
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
* Split service combinators to separate trait
|
* Split service combinators to separate trait
|
||||||
|
|
||||||
|
|
||||||
## [0.1.2] - 2018-12-12
|
## 0.1.2 - 2018-12-12
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
* Release future early for `.and_then()` and `.then()` combinators
|
* Release future early for `.and_then()` and `.then()` combinators
|
||||||
|
|
||||||
|
|
||||||
## [0.1.1] - 2018-12-09
|
## 0.1.1 - 2018-12-09
|
||||||
|
* Added Service impl for `Box<S: Service>`
|
||||||
### Added
|
|
||||||
|
|
||||||
* Added Service impl for Box<S: Service>
|
|
||||||
|
|
||||||
|
|
||||||
## [0.1.0] - 2018-12-09
|
## 0.1.0 - 2018-12-09
|
||||||
|
|
||||||
* Initial import
|
* Initial import
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
/// An implementation of [`poll_ready`]() that always signals readiness.
|
/// An implementation of [`poll_ready`]() that always signals readiness.
|
||||||
///
|
///
|
||||||
/// This should only be used for basic leaf services that have no concept of un-readiness.
|
/// This should only be used for basic leaf services that have no concept of un-readiness.
|
||||||
/// For wrapper or other serivice types, use [`forward_ready!`] for simple cases or write a bespoke
|
/// For wrapper or other service types, use [`forward_ready!`] for simple cases or write a bespoke
|
||||||
/// `poll_ready` implementation.
|
/// `poll_ready` implementation.
|
||||||
///
|
///
|
||||||
/// [`poll_ready`]: crate::Service::poll_ready
|
/// [`poll_ready`]: crate::Service::poll_ready
|
||||||
|
@@ -3,6 +3,87 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 3.0.0-rc.1 - 2021-11-29
|
||||||
|
### 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]
|
||||||
|
* Implement `Default` for `connect::tcp::{TcpConnector, TcpConnectorService}`. [#423]
|
||||||
|
* Implement `Default` for `connect::ConnectorService`. [#423]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* The crate's default features flags no longer include `uri`. [#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]
|
||||||
|
* Mark `tcp::{TcpConnector, TcpConnectorService}` structs `#[non_exhaustive]`. [#423]
|
||||||
|
* 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]
|
||||||
|
* Inline modules in `connect::tls` to `connect` module. [#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
|
||||||
|
[#423]: https://github.com/actix/actix-net/pull/423
|
||||||
|
|
||||||
|
|
||||||
|
### 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
|
||||||
|
* The crate's default features flags no longer include `uri`. [#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]
|
||||||
|
* Inline modules in `connect::tls` to `connect` module. [#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
|
||||||
|
* Add configurable timeout for accepting TLS connection. [#393]
|
||||||
|
* Added `TlsError::Timeout` variant. [#393]
|
||||||
|
* All TLS acceptor services now use `TlsError` for their error types. [#393]
|
||||||
|
* Added `TlsError::into_service_error`. [#420]
|
||||||
|
|
||||||
|
[#393]: https://github.com/actix/actix-net/pull/393
|
||||||
|
[#420]: https://github.com/actix/actix-net/pull/420
|
||||||
|
|
||||||
|
|
||||||
|
## 3.0.0-beta.8 - 2021-11-15
|
||||||
|
* Add `Connect::request` for getting a reference to the connection request. [#415]
|
||||||
|
|
||||||
|
[#415]: https://github.com/actix/actix-net/pull/415
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.7 - 2021-10-20
|
## 3.0.0-beta.7 - 2021-10-20
|
||||||
* Add `webpki_roots_cert_store()` to get rustls compatible webpki roots cert store. [#401]
|
* Add `webpki_roots_cert_store()` to get rustls compatible webpki roots cert store. [#401]
|
||||||
* Alias `connect::ssl` to `connect::tls`. [#401]
|
* Alias `connect::ssl` to `connect::tls`. [#401]
|
||||||
@@ -25,8 +106,7 @@
|
|||||||
* Remove `connect::ssl::openssl::OpensslConnectService`. [#297]
|
* Remove `connect::ssl::openssl::OpensslConnectService`. [#297]
|
||||||
* Add `connect::ssl::native_tls` module for native tls support. [#295]
|
* Add `connect::ssl::native_tls` module for native tls support. [#295]
|
||||||
* Rename `accept::{nativetls => native_tls}`. [#295]
|
* Rename `accept::{nativetls => native_tls}`. [#295]
|
||||||
* Remove `connect::TcpConnectService` type. service caller expect a `TcpStream` should use
|
* Remove `connect::TcpConnectService` type. Service caller expecting a `TcpStream` should use `connect::ConnectService` instead and call `Connection<T, TcpStream>::into_parts`. [#299]
|
||||||
`connect::ConnectService` instead and call `Connection<T, TcpStream>::into_parts`. [#299]
|
|
||||||
|
|
||||||
[#295]: https://github.com/actix/actix-net/pull/295
|
[#295]: https://github.com/actix/actix-net/pull/295
|
||||||
[#296]: https://github.com/actix/actix-net/pull/296
|
[#296]: https://github.com/actix/actix-net/pull/296
|
||||||
|
@@ -1,7 +1,10 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-tls"
|
name = "actix-tls"
|
||||||
version = "3.0.0-beta.7"
|
version = "3.0.0-rc.1"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = [
|
||||||
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
|
"Rob Ede <robjtede@icloud.com>",
|
||||||
|
]
|
||||||
description = "TLS acceptor and connector services for Actix ecosystem"
|
description = "TLS acceptor and connector services for Actix ecosystem"
|
||||||
keywords = ["network", "tls", "ssl", "async", "transport"]
|
keywords = ["network", "tls", "ssl", "async", "transport"]
|
||||||
repository = "https://github.com/actix/actix-net.git"
|
repository = "https://github.com/actix/actix-net.git"
|
||||||
@@ -10,14 +13,15 @@ license = "MIT OR Apache-2.0"
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = ["openssl", "rustls", "native-tls", "accept", "connect", "uri"]
|
all-features = true
|
||||||
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "actix_tls"
|
name = "actix_tls"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["accept", "connect", "uri"]
|
default = ["accept", "connect"]
|
||||||
|
|
||||||
# enable acceptor services
|
# enable acceptor services
|
||||||
accept = []
|
accept = []
|
||||||
@@ -45,10 +49,13 @@ actix-utils = "3.0.0"
|
|||||||
|
|
||||||
derive_more = "0.99.5"
|
derive_more = "0.99.5"
|
||||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||||
http = { version = "0.2.3", optional = true }
|
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
pin-project-lite = "0.2.7"
|
||||||
tokio-util = { version = "0.6.3", default-features = false }
|
tokio-util = { version = "0.6.3", default-features = false }
|
||||||
|
|
||||||
|
# uri
|
||||||
|
http = { version = "0.2.3", optional = true }
|
||||||
|
|
||||||
# openssl
|
# openssl
|
||||||
tls-openssl = { package = "openssl", version = "0.10.9", optional = true }
|
tls-openssl = { package = "openssl", version = "0.10.9", optional = true }
|
||||||
tokio-openssl = { version = "0.6", optional = true }
|
tokio-openssl = { version = "0.6", optional = true }
|
||||||
@@ -62,14 +69,16 @@ tokio-native-tls = { version = "0.3", optional = true }
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.2.0"
|
actix-rt = "2.2.0"
|
||||||
actix-server = "2.0.0-beta.8"
|
actix-server = "2.0.0-beta.9"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
env_logger = "0.9"
|
env_logger = "0.9"
|
||||||
futures-util = { version = "0.3.7", default-features = false, features = ["sink"] }
|
futures-util = { version = "0.3.7", default-features = false, features = ["sink"] }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
rcgen = "0.8"
|
||||||
rustls-pemfile = "0.2.1"
|
rustls-pemfile = "0.2.1"
|
||||||
|
tokio-rustls = { version = "0.23", features = ["dangerous_configuration"] }
|
||||||
trust-dns-resolver = "0.20.0"
|
trust-dns-resolver = "0.20.0"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "tcp-rustls"
|
name = "accept-rustls"
|
||||||
required-features = ["accept", "rustls"]
|
required-features = ["accept", "rustls"]
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
//! TLS Acceptor Server
|
//! No-Op TLS Acceptor Server
|
||||||
//!
|
//!
|
||||||
//! Using either HTTPie (`http`) or cURL:
|
//! Using either HTTPie (`http`) or cURL:
|
||||||
//!
|
//!
|
@@ -1,25 +1,31 @@
|
|||||||
//! TLS acceptor services for Actix ecosystem.
|
//! TLS connection acceptor services.
|
||||||
//!
|
|
||||||
//! ## Crate Features
|
|
||||||
//! * `openssl` - TLS acceptor using the `openssl` crate.
|
|
||||||
//! * `rustls` - TLS acceptor using the `rustls` crate.
|
|
||||||
//! * `native-tls` - TLS acceptor using the `native-tls` crate.
|
|
||||||
|
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::{
|
||||||
|
convert::Infallible,
|
||||||
|
sync::atomic::{AtomicUsize, Ordering},
|
||||||
|
};
|
||||||
|
|
||||||
use actix_utils::counter::Counter;
|
use actix_utils::counter::Counter;
|
||||||
|
use derive_more::{Display, Error};
|
||||||
|
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "openssl")))]
|
||||||
pub mod openssl;
|
pub mod openssl;
|
||||||
|
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
|
||||||
pub mod rustls;
|
pub mod rustls;
|
||||||
|
|
||||||
#[cfg(feature = "native-tls")]
|
#[cfg(feature = "native-tls")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
|
||||||
pub mod native_tls;
|
pub mod native_tls;
|
||||||
|
|
||||||
pub(crate) static MAX_CONN: AtomicUsize = AtomicUsize::new(256);
|
pub(crate) static MAX_CONN: AtomicUsize = AtomicUsize::new(256);
|
||||||
|
|
||||||
|
#[cfg(any(feature = "openssl", feature = "rustls", feature = "native-tls"))]
|
||||||
|
pub(crate) const DEFAULT_TLS_HANDSHAKE_TIMEOUT: std::time::Duration =
|
||||||
|
std::time::Duration::from_secs(3);
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static MAX_CONN_COUNTER: Counter = Counter::new(MAX_CONN.load(Ordering::Relaxed));
|
static MAX_CONN_COUNTER: Counter = Counter::new(MAX_CONN.load(Ordering::Relaxed));
|
||||||
}
|
}
|
||||||
@@ -34,9 +40,52 @@ pub fn max_concurrent_tls_connect(num: usize) {
|
|||||||
MAX_CONN.store(num, Ordering::Relaxed);
|
MAX_CONN.store(num, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TLS error combined with service error.
|
/// TLS handshake error, TLS timeout, or inner service error.
|
||||||
#[derive(Debug)]
|
///
|
||||||
pub enum TlsError<E1, E2> {
|
/// All TLS acceptors from this crate will return the `SvcErr` type parameter as [`Infallible`],
|
||||||
Tls(E1),
|
/// which can be cast to your own service type, inferred or otherwise,
|
||||||
Service(E2),
|
/// using [`into_service_error`](Self::into_service_error).
|
||||||
|
#[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 service errors.
|
||||||
|
#[display(fmt = "Service error")]
|
||||||
|
Service(SvcErr),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<TlsErr> TlsError<TlsErr, Infallible> {
|
||||||
|
/// Casts the infallible service error type returned from acceptors into caller's type.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// # use std::convert::Infallible;
|
||||||
|
/// # use actix_tls::accept::TlsError;
|
||||||
|
/// let a: TlsError<u32, Infallible> = TlsError::Tls(42);
|
||||||
|
/// let _b: TlsError<u32, u64> = a.into_service_error();
|
||||||
|
/// ```
|
||||||
|
pub fn into_service_error<SvcErr>(self) -> TlsError<TlsErr, SvcErr> {
|
||||||
|
match self {
|
||||||
|
Self::Timeout => TlsError::Timeout,
|
||||||
|
Self::Tls(err) => TlsError::Tls(err),
|
||||||
|
Self::Service(err) => match err {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tls_service_error_inference() {
|
||||||
|
let a: TlsError<u32, Infallible> = TlsError::Tls(42);
|
||||||
|
let _b: TlsError<u32, u64> = a.into_service_error();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,45 +1,42 @@
|
|||||||
|
//! `native-tls` based TLS connection acceptor service.
|
||||||
|
//!
|
||||||
|
//! See [`Acceptor`] for main service factory docs.
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
|
convert::Infallible,
|
||||||
io::{self, IoSlice},
|
io::{self, IoSlice},
|
||||||
ops::{Deref, DerefMut},
|
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use actix_codec::{AsyncRead, AsyncWrite, ReadBuf};
|
use actix_codec::{AsyncRead, AsyncWrite, ReadBuf};
|
||||||
use actix_rt::net::{ActixStream, Ready};
|
use actix_rt::{
|
||||||
|
net::{ActixStream, Ready},
|
||||||
|
time::timeout,
|
||||||
|
};
|
||||||
use actix_service::{Service, ServiceFactory};
|
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;
|
use futures_core::future::LocalBoxFuture;
|
||||||
|
use tokio_native_tls::{native_tls::Error, TlsAcceptor};
|
||||||
|
|
||||||
pub use tokio_native_tls::native_tls::Error;
|
use super::{TlsError, DEFAULT_TLS_HANDSHAKE_TIMEOUT, MAX_CONN_COUNTER};
|
||||||
pub use tokio_native_tls::TlsAcceptor;
|
|
||||||
|
|
||||||
use super::MAX_CONN_COUNTER;
|
pub mod reexports {
|
||||||
|
//! Re-exports from `native-tls` that are useful for acceptors.
|
||||||
|
|
||||||
/// Wrapper type for `tokio_native_tls::TlsStream` in order to impl `ActixStream` trait.
|
pub use tokio_native_tls::{native_tls::Error, TlsAcceptor};
|
||||||
pub struct TlsStream<T>(tokio_native_tls::TlsStream<T>);
|
|
||||||
|
|
||||||
impl<T> From<tokio_native_tls::TlsStream<T>> for TlsStream<T> {
|
|
||||||
fn from(stream: tokio_native_tls::TlsStream<T>) -> Self {
|
|
||||||
Self(stream)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ActixStream> Deref for TlsStream<T> {
|
/// Wraps a `native-tls` based async TLS stream in order to implement [`ActixStream`].
|
||||||
type Target = tokio_native_tls::TlsStream<T>;
|
#[derive(Deref, DerefMut, From)]
|
||||||
|
pub struct TlsStream<IO>(tokio_native_tls::TlsStream<IO>);
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
impl<IO: ActixStream> AsyncRead for TlsStream<IO> {
|
||||||
&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> {
|
|
||||||
fn poll_read(
|
fn poll_read(
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
@@ -49,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(
|
fn poll_write(
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
@@ -79,28 +76,37 @@ 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>> {
|
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>> {
|
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.
|
/// Accept TLS connections via the `native-tls` crate.
|
||||||
///
|
|
||||||
/// `native-tls` feature enables this `Acceptor` type.
|
|
||||||
pub struct Acceptor {
|
pub struct Acceptor {
|
||||||
acceptor: TlsAcceptor,
|
acceptor: TlsAcceptor,
|
||||||
|
handshake_timeout: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Acceptor {
|
impl Acceptor {
|
||||||
/// Create `native-tls` based `Acceptor` service factory.
|
/// Constructs `native-tls` based acceptor service factory.
|
||||||
#[inline]
|
|
||||||
pub fn new(acceptor: TlsAcceptor) -> Self {
|
pub fn new(acceptor: TlsAcceptor) -> Self {
|
||||||
Acceptor { acceptor }
|
Acceptor {
|
||||||
|
acceptor,
|
||||||
|
handshake_timeout: DEFAULT_TLS_HANDSHAKE_TIMEOUT,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Limit the amount of time that the acceptor will wait for a TLS handshake to complete.
|
||||||
|
///
|
||||||
|
/// Default timeout is 3 seconds.
|
||||||
|
pub fn set_handshake_timeout(&mut self, handshake_timeout: Duration) -> &mut Self {
|
||||||
|
self.handshake_timeout = handshake_timeout;
|
||||||
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,39 +115,43 @@ impl Clone for Acceptor {
|
|||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
acceptor: self.acceptor.clone(),
|
acceptor: self.acceptor.clone(),
|
||||||
|
handshake_timeout: self.handshake_timeout,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ActixStream + 'static> ServiceFactory<T> for Acceptor {
|
impl<IO: ActixStream + 'static> ServiceFactory<IO> for Acceptor {
|
||||||
type Response = TlsStream<T>;
|
type Response = TlsStream<IO>;
|
||||||
type Error = Error;
|
type Error = TlsError<Error, Infallible>;
|
||||||
type Config = ();
|
type Config = ();
|
||||||
|
type Service = AcceptorService;
|
||||||
type Service = NativeTlsAcceptorService;
|
|
||||||
type InitError = ();
|
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 {
|
fn new_service(&self, _: ()) -> Self::Future {
|
||||||
let res = MAX_CONN_COUNTER.with(|conns| {
|
let res = MAX_CONN_COUNTER.with(|conns| {
|
||||||
Ok(NativeTlsAcceptorService {
|
Ok(AcceptorService {
|
||||||
acceptor: self.acceptor.clone(),
|
acceptor: self.acceptor.clone(),
|
||||||
conns: conns.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,
|
acceptor: TlsAcceptor,
|
||||||
conns: Counter,
|
conns: Counter,
|
||||||
|
handshake_timeout: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ActixStream + 'static> Service<T> for NativeTlsAcceptorService {
|
impl<IO: ActixStream + 'static> Service<IO> for AcceptorService {
|
||||||
type Response = TlsStream<T>;
|
type Response = TlsStream<IO>;
|
||||||
type Error = Error;
|
type Error = TlsError<Error, Infallible>;
|
||||||
type Future = LocalBoxFuture<'static, Result<TlsStream<T>, Error>>;
|
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||||
|
|
||||||
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
if self.conns.available(cx) {
|
if self.conns.available(cx) {
|
||||||
@@ -151,13 +161,21 @@ 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 guard = self.conns.get();
|
||||||
let acceptor = self.acceptor.clone();
|
let acceptor = self.acceptor.clone();
|
||||||
|
|
||||||
|
let dur = self.handshake_timeout;
|
||||||
|
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let io = acceptor.accept(io).await;
|
match timeout(dur, acceptor.accept(io)).await {
|
||||||
drop(guard);
|
Ok(Ok(io)) => {
|
||||||
io.map(Into::into)
|
drop(guard);
|
||||||
|
Ok(TlsStream(io))
|
||||||
|
}
|
||||||
|
Ok(Err(err)) => Err(TlsError::Tls(err)),
|
||||||
|
Err(_timeout) => Err(TlsError::Timeout),
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,47 +1,45 @@
|
|||||||
|
//! `openssl` based TLS acceptor service.
|
||||||
|
//!
|
||||||
|
//! See [`Acceptor`] for main service factory docs.
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
|
convert::Infallible,
|
||||||
future::Future,
|
future::Future,
|
||||||
io::{self, IoSlice},
|
io::{self, IoSlice},
|
||||||
ops::{Deref, DerefMut},
|
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use actix_codec::{AsyncRead, AsyncWrite, ReadBuf};
|
use actix_codec::{AsyncRead, AsyncWrite, ReadBuf};
|
||||||
use actix_rt::net::{ActixStream, Ready};
|
use actix_rt::{
|
||||||
use actix_service::{Service, ServiceFactory};
|
net::{ActixStream, Ready},
|
||||||
use actix_utils::counter::{Counter, CounterGuard};
|
time::{sleep, Sleep},
|
||||||
use futures_core::{future::LocalBoxFuture, ready};
|
|
||||||
|
|
||||||
pub use openssl::ssl::{
|
|
||||||
AlpnError, Error as SslError, HandshakeError, Ssl, SslAcceptor, SslAcceptorBuilder,
|
|
||||||
};
|
};
|
||||||
|
use actix_service::{Service, ServiceFactory};
|
||||||
|
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::MAX_CONN_COUNTER;
|
use super::{TlsError, DEFAULT_TLS_HANDSHAKE_TIMEOUT, MAX_CONN_COUNTER};
|
||||||
|
|
||||||
/// Wrapper type for `tokio_openssl::SslStream` in order to impl `ActixStream` trait.
|
pub mod reexports {
|
||||||
pub struct TlsStream<T>(tokio_openssl::SslStream<T>);
|
//! Re-exports from `openssl` that are useful for acceptors.
|
||||||
|
|
||||||
impl<T> From<tokio_openssl::SslStream<T>> for TlsStream<T> {
|
pub use openssl::ssl::{
|
||||||
fn from(stream: tokio_openssl::SslStream<T>) -> Self {
|
AlpnError, Error, HandshakeError, Ssl, SslAcceptor, SslAcceptorBuilder,
|
||||||
Self(stream)
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Deref for TlsStream<T> {
|
/// Wraps an `openssl` based async TLS stream in order to implement [`ActixStream`].
|
||||||
type Target = tokio_openssl::SslStream<T>;
|
#[derive(Deref, DerefMut, From)]
|
||||||
|
pub struct TlsStream<IO>(tokio_openssl::SslStream<IO>);
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
impl<IO: ActixStream> AsyncRead for TlsStream<IO> {
|
||||||
&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> {
|
|
||||||
fn poll_read(
|
fn poll_read(
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
@@ -51,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(
|
fn poll_write(
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
@@ -81,28 +79,38 @@ 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>> {
|
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>> {
|
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.
|
/// Accept TLS connections via the `openssl` crate.
|
||||||
///
|
|
||||||
/// `openssl` feature enables this `Acceptor` type.
|
|
||||||
pub struct Acceptor {
|
pub struct Acceptor {
|
||||||
acceptor: SslAcceptor,
|
acceptor: SslAcceptor,
|
||||||
|
handshake_timeout: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Acceptor {
|
impl Acceptor {
|
||||||
/// Create OpenSSL based `Acceptor` service factory.
|
/// Create `openssl` based acceptor service factory.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(acceptor: SslAcceptor) -> Self {
|
pub fn new(acceptor: SslAcceptor) -> Self {
|
||||||
Acceptor { acceptor }
|
Acceptor {
|
||||||
|
acceptor,
|
||||||
|
handshake_timeout: DEFAULT_TLS_HANDSHAKE_TIMEOUT,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Limit the amount of time that the acceptor will wait for a TLS handshake to complete.
|
||||||
|
///
|
||||||
|
/// Default timeout is 3 seconds.
|
||||||
|
pub fn set_handshake_timeout(&mut self, handshake_timeout: Duration) -> &mut Self {
|
||||||
|
self.handshake_timeout = handshake_timeout;
|
||||||
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,38 +119,43 @@ impl Clone for Acceptor {
|
|||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
acceptor: self.acceptor.clone(),
|
acceptor: self.acceptor.clone(),
|
||||||
|
handshake_timeout: self.handshake_timeout,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ActixStream> ServiceFactory<T> for Acceptor {
|
impl<IO: ActixStream> ServiceFactory<IO> for Acceptor {
|
||||||
type Response = TlsStream<T>;
|
type Response = TlsStream<IO>;
|
||||||
type Error = SslError;
|
type Error = TlsError<Error, Infallible>;
|
||||||
type Config = ();
|
type Config = ();
|
||||||
type Service = AcceptorService;
|
type Service = AcceptorService;
|
||||||
type InitError = ();
|
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 {
|
fn new_service(&self, _: ()) -> Self::Future {
|
||||||
let res = MAX_CONN_COUNTER.with(|conns| {
|
let res = MAX_CONN_COUNTER.with(|conns| {
|
||||||
Ok(AcceptorService {
|
Ok(AcceptorService {
|
||||||
acceptor: self.acceptor.clone(),
|
acceptor: self.acceptor.clone(),
|
||||||
conns: conns.clone(),
|
conns: conns.clone(),
|
||||||
|
handshake_timeout: self.handshake_timeout,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
Box::pin(async { res })
|
|
||||||
|
ready(res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// OpenSSL based acceptor service.
|
||||||
pub struct AcceptorService {
|
pub struct AcceptorService {
|
||||||
acceptor: SslAcceptor,
|
acceptor: SslAcceptor,
|
||||||
conns: Counter,
|
conns: Counter,
|
||||||
|
handshake_timeout: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ActixStream> Service<T> for AcceptorService {
|
impl<IO: ActixStream> Service<IO> for AcceptorService {
|
||||||
type Response = TlsStream<T>;
|
type Response = TlsStream<IO>;
|
||||||
type Error = SslError;
|
type Error = TlsError<Error, Infallible>;
|
||||||
type Future = AcceptorServiceResponse<T>;
|
type Future = AcceptFut<IO>;
|
||||||
|
|
||||||
fn poll_ready(&self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
fn poll_ready(&self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
if self.conns.available(ctx) {
|
if self.conns.available(ctx) {
|
||||||
@@ -152,30 +165,43 @@ 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_ctx = self.acceptor.context();
|
||||||
let ssl = Ssl::new(ssl_ctx).expect("Provided SSL acceptor was invalid.");
|
let ssl = Ssl::new(ssl_ctx).expect("Provided SSL acceptor was invalid.");
|
||||||
AcceptorServiceResponse {
|
|
||||||
|
AcceptFut {
|
||||||
_guard: self.conns.get(),
|
_guard: self.conns.get(),
|
||||||
|
timeout: sleep(self.handshake_timeout),
|
||||||
stream: Some(tokio_openssl::SslStream::new(ssl, io).unwrap()),
|
stream: Some(tokio_openssl::SslStream::new(ssl, io).unwrap()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AcceptorServiceResponse<T: ActixStream> {
|
pin_project! {
|
||||||
stream: Option<tokio_openssl::SslStream<T>>,
|
/// Accept future for OpenSSL service.
|
||||||
_guard: CounterGuard,
|
#[doc(hidden)]
|
||||||
}
|
pub struct AcceptFut<IO: ActixStream> {
|
||||||
|
stream: Option<tokio_openssl::SslStream<IO>>,
|
||||||
impl<T: ActixStream> Future for AcceptorServiceResponse<T> {
|
#[pin]
|
||||||
type Output = Result<TlsStream<T>, SslError>;
|
timeout: Sleep,
|
||||||
|
_guard: CounterGuard,
|
||||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
}
|
||||||
ready!(Pin::new(self.stream.as_mut().unwrap()).poll_accept(cx))?;
|
}
|
||||||
Poll::Ready(Ok(self
|
|
||||||
.stream
|
impl<IO: ActixStream> Future for AcceptFut<IO> {
|
||||||
.take()
|
type Output = Result<TlsStream<IO>, TlsError<Error, Infallible>>;
|
||||||
.expect("SSL connect has resolved.")
|
|
||||||
.into()))
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
|
let this = self.project();
|
||||||
|
|
||||||
|
match Pin::new(this.stream.as_mut().unwrap()).poll_accept(cx) {
|
||||||
|
Poll::Ready(Ok(())) => Poll::Ready(Ok(this
|
||||||
|
.stream
|
||||||
|
.take()
|
||||||
|
.expect("Acceptor should not be polled after it has completed.")
|
||||||
|
.into())),
|
||||||
|
Poll::Ready(Err(err)) => Poll::Ready(Err(TlsError::Tls(err))),
|
||||||
|
Poll::Pending => this.timeout.poll(cx).map(|_| Err(TlsError::Timeout)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,47 +1,45 @@
|
|||||||
|
//! `rustls` based TLS connection acceptor service.
|
||||||
|
//!
|
||||||
|
//! See [`Acceptor`] for main service factory docs.
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
|
convert::Infallible,
|
||||||
future::Future,
|
future::Future,
|
||||||
io::{self, IoSlice},
|
io::{self, IoSlice},
|
||||||
ops::{Deref, DerefMut},
|
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use actix_codec::{AsyncRead, AsyncWrite, ReadBuf};
|
use actix_codec::{AsyncRead, AsyncWrite, ReadBuf};
|
||||||
use actix_rt::net::{ActixStream, Ready};
|
use actix_rt::{
|
||||||
|
net::{ActixStream, Ready},
|
||||||
|
time::{sleep, Sleep},
|
||||||
|
};
|
||||||
use actix_service::{Service, ServiceFactory};
|
use actix_service::{Service, ServiceFactory};
|
||||||
use actix_utils::counter::{Counter, CounterGuard};
|
use actix_utils::{
|
||||||
use futures_core::future::LocalBoxFuture;
|
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};
|
use tokio_rustls::{Accept, TlsAcceptor};
|
||||||
|
|
||||||
pub use tokio_rustls::rustls::ServerConfig;
|
use super::{TlsError, DEFAULT_TLS_HANDSHAKE_TIMEOUT, MAX_CONN_COUNTER};
|
||||||
|
|
||||||
use super::MAX_CONN_COUNTER;
|
pub mod reexports {
|
||||||
|
//! Re-exports from `rustls` that are useful for acceptors.
|
||||||
|
|
||||||
/// Wrapper type for `tokio_openssl::SslStream` in order to impl `ActixStream` trait.
|
pub use tokio_rustls::rustls::ServerConfig;
|
||||||
pub struct TlsStream<T>(tokio_rustls::server::TlsStream<T>);
|
|
||||||
|
|
||||||
impl<T> From<tokio_rustls::server::TlsStream<T>> for TlsStream<T> {
|
|
||||||
fn from(stream: tokio_rustls::server::TlsStream<T>) -> Self {
|
|
||||||
Self(stream)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Deref for TlsStream<T> {
|
/// Wraps a `rustls` based async TLS stream in order to implement [`ActixStream`].
|
||||||
type Target = tokio_rustls::server::TlsStream<T>;
|
#[derive(Deref, DerefMut, From)]
|
||||||
|
pub struct TlsStream<IO>(tokio_rustls::server::TlsStream<IO>);
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
impl<IO: ActixStream> AsyncRead for TlsStream<IO> {
|
||||||
&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> {
|
|
||||||
fn poll_read(
|
fn poll_read(
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
@@ -51,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(
|
fn poll_write(
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
@@ -81,72 +79,81 @@ 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>> {
|
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>> {
|
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.
|
/// Accept TLS connections via the `rustls` crate.
|
||||||
///
|
|
||||||
/// `rustls` feature enables this `Acceptor` type.
|
|
||||||
pub struct Acceptor {
|
pub struct Acceptor {
|
||||||
config: Arc<ServerConfig>,
|
config: Arc<ServerConfig>,
|
||||||
|
handshake_timeout: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Acceptor {
|
impl Acceptor {
|
||||||
/// Create Rustls based `Acceptor` service factory.
|
/// Constructs `rustls` based acceptor service factory.
|
||||||
#[inline]
|
|
||||||
pub fn new(config: ServerConfig) -> Self {
|
pub fn new(config: ServerConfig) -> Self {
|
||||||
Acceptor {
|
Acceptor {
|
||||||
config: Arc::new(config),
|
config: Arc::new(config),
|
||||||
|
handshake_timeout: DEFAULT_TLS_HANDSHAKE_TIMEOUT,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Limit the amount of time that the acceptor will wait for a TLS handshake to complete.
|
||||||
|
///
|
||||||
|
/// Default timeout is 3 seconds.
|
||||||
|
pub fn set_handshake_timeout(&mut self, handshake_timeout: Duration) -> &mut Self {
|
||||||
|
self.handshake_timeout = handshake_timeout;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for Acceptor {
|
impl Clone for Acceptor {
|
||||||
#[inline]
|
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
config: self.config.clone(),
|
config: self.config.clone(),
|
||||||
|
handshake_timeout: self.handshake_timeout,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ActixStream> ServiceFactory<T> for Acceptor {
|
impl<IO: ActixStream> ServiceFactory<IO> for Acceptor {
|
||||||
type Response = TlsStream<T>;
|
type Response = TlsStream<IO>;
|
||||||
type Error = io::Error;
|
type Error = TlsError<io::Error, Infallible>;
|
||||||
type Config = ();
|
type Config = ();
|
||||||
|
|
||||||
type Service = AcceptorService;
|
type Service = AcceptorService;
|
||||||
type InitError = ();
|
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 {
|
fn new_service(&self, _: ()) -> Self::Future {
|
||||||
let res = MAX_CONN_COUNTER.with(|conns| {
|
let res = MAX_CONN_COUNTER.with(|conns| {
|
||||||
Ok(AcceptorService {
|
Ok(AcceptorService {
|
||||||
acceptor: self.config.clone().into(),
|
acceptor: self.config.clone().into(),
|
||||||
conns: conns.clone(),
|
conns: conns.clone(),
|
||||||
|
handshake_timeout: self.handshake_timeout,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
Box::pin(async { res })
|
|
||||||
|
ready(res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rustls based `Acceptor` service
|
/// Rustls based acceptor service.
|
||||||
pub struct AcceptorService {
|
pub struct AcceptorService {
|
||||||
acceptor: TlsAcceptor,
|
acceptor: TlsAcceptor,
|
||||||
conns: Counter,
|
conns: Counter,
|
||||||
|
handshake_timeout: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ActixStream> Service<T> for AcceptorService {
|
impl<IO: ActixStream> Service<IO> for AcceptorService {
|
||||||
type Response = TlsStream<T>;
|
type Response = TlsStream<IO>;
|
||||||
type Error = io::Error;
|
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>> {
|
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
if self.conns.available(cx) {
|
if self.conns.available(cx) {
|
||||||
@@ -156,24 +163,35 @@ impl<T: ActixStream> Service<T> for AcceptorService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(&self, req: T) -> Self::Future {
|
fn call(&self, req: IO) -> Self::Future {
|
||||||
AcceptorServiceFut {
|
AcceptFut {
|
||||||
_guard: self.conns.get(),
|
|
||||||
fut: self.acceptor.accept(req),
|
fut: self.acceptor.accept(req),
|
||||||
|
timeout: sleep(self.handshake_timeout),
|
||||||
|
_guard: self.conns.get(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AcceptorServiceFut<T: ActixStream> {
|
pin_project! {
|
||||||
fut: Accept<T>,
|
/// Accept future for Rustls service.
|
||||||
_guard: CounterGuard,
|
#[doc(hidden)]
|
||||||
}
|
pub struct AcceptFut<IO: ActixStream> {
|
||||||
|
fut: Accept<IO>,
|
||||||
impl<T: ActixStream> Future for AcceptorServiceFut<T> {
|
#[pin]
|
||||||
type Output = Result<TlsStream<T>, io::Error>;
|
timeout: Sleep,
|
||||||
|
_guard: CounterGuard,
|
||||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
}
|
||||||
let this = self.get_mut();
|
}
|
||||||
Pin::new(&mut this.fut).poll(cx).map_ok(TlsStream)
|
|
||||||
|
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();
|
||||||
|
match Pin::new(&mut this.fut).poll(cx) {
|
||||||
|
Poll::Ready(Ok(stream)) => Poll::Ready(Ok(TlsStream(stream))),
|
||||||
|
Poll::Ready(Err(err)) => Poll::Ready(Err(TlsError::Tls(err))),
|
||||||
|
Poll::Pending => this.timeout.poll(cx).map(|_| Err(TlsError::Timeout)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,350 +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<T> {
|
|
||||||
pub(crate) req: T,
|
|
||||||
pub(crate) port: u16,
|
|
||||||
pub(crate) addr: ConnectAddrs,
|
|
||||||
pub(crate) local_addr: Option<IpAddr>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Address> Connect<T> {
|
|
||||||
/// Create `Connect` instance by splitting the string by ':' and convert the second part to u16
|
|
||||||
pub fn new(req: T) -> Connect<T> {
|
|
||||||
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: T, addr: SocketAddr) -> Connect<T> {
|
|
||||||
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()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Address> From<T> for Connect<T> {
|
|
||||||
fn from(addr: T) -> Self {
|
|
||||||
Connect::new(addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Address> fmt::Display for Connect<T> {
|
|
||||||
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))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
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()
|
||||||
|
}
|
||||||
|
}
|
234
actix-tls/src/connect/connector.rs
Executable file → Normal file
234
actix-tls/src/connect/connector.rs
Executable file → Normal file
@@ -1,194 +1,128 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::VecDeque,
|
|
||||||
future::Future,
|
future::Future,
|
||||||
io,
|
|
||||||
net::{IpAddr, SocketAddr, SocketAddrV4, SocketAddrV6},
|
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
|
|
||||||
use actix_rt::net::{TcpSocket, TcpStream};
|
use actix_rt::net::TcpStream;
|
||||||
use actix_service::{Service, ServiceFactory};
|
use actix_service::{Service, ServiceFactory};
|
||||||
use futures_core::{future::LocalBoxFuture, ready};
|
use actix_utils::future::{ok, Ready};
|
||||||
use log::{error, trace};
|
use futures_core::ready;
|
||||||
use tokio_util::sync::ReusableBoxFuture;
|
|
||||||
|
|
||||||
use super::connect::{Address, Connect, ConnectAddrs, Connection};
|
use super::{
|
||||||
use super::error::ConnectError;
|
error::ConnectError,
|
||||||
|
resolver::{Resolver, ResolverService},
|
||||||
|
tcp::{TcpConnector, TcpConnectorService},
|
||||||
|
ConnectInfo, Connection, Host,
|
||||||
|
};
|
||||||
|
|
||||||
/// TCP connector service factory
|
/// Combined resolver and TCP connector service factory.
|
||||||
#[derive(Debug, Copy, Clone)]
|
///
|
||||||
pub struct TcpConnectorFactory;
|
/// 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 {
|
impl Connector {
|
||||||
/// Create TCP connector service
|
/// Constructs new connector factory with the given resolver.
|
||||||
pub fn service(&self) -> TcpConnector {
|
pub fn new(resolver: Resolver) -> Self {
|
||||||
TcpConnector
|
Connector { resolver }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build connector service.
|
||||||
|
pub fn service(&self) -> ConnectorService {
|
||||||
|
ConnectorService {
|
||||||
|
tcp: TcpConnector::default().service(),
|
||||||
|
resolver: self.resolver.service(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Address> ServiceFactory<Connect<T>> for TcpConnectorFactory {
|
impl<R: Host> ServiceFactory<ConnectInfo<R>> for Connector {
|
||||||
type Response = Connection<T, TcpStream>;
|
type Response = Connection<R, TcpStream>;
|
||||||
type Error = ConnectError;
|
type Error = ConnectError;
|
||||||
type Config = ();
|
type Config = ();
|
||||||
type Service = TcpConnector;
|
type Service = ConnectorService;
|
||||||
type InitError = ();
|
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 {
|
fn new_service(&self, _: ()) -> Self::Future {
|
||||||
let service = self.service();
|
ok(self.service())
|
||||||
Box::pin(async move { Ok(service) })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TCP connector service
|
/// Combined resolver and TCP connector service.
|
||||||
#[derive(Debug, Copy, Clone)]
|
///
|
||||||
pub struct TcpConnector;
|
/// Service implementation receives connection information, resolves DNS if required, and returns
|
||||||
|
/// a TCP stream.
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct ConnectorService {
|
||||||
|
tcp: TcpConnectorService,
|
||||||
|
resolver: ResolverService,
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Address> Service<Connect<T>> for TcpConnector {
|
impl<R: Host> Service<ConnectInfo<R>> for ConnectorService {
|
||||||
type Response = Connection<T, TcpStream>;
|
type Response = Connection<R, TcpStream>;
|
||||||
type Error = ConnectError;
|
type Error = ConnectError;
|
||||||
type Future = TcpConnectorResponse<T>;
|
type Future = ConnectServiceResponse<R>;
|
||||||
|
|
||||||
actix_service::always_ready!();
|
actix_service::always_ready!();
|
||||||
|
|
||||||
fn call(&self, req: Connect<T>) -> Self::Future {
|
fn call(&self, req: ConnectInfo<R>) -> Self::Future {
|
||||||
let port = req.port();
|
ConnectServiceResponse {
|
||||||
let Connect {
|
fut: ConnectFut::Resolve(self.resolver.call(req)),
|
||||||
req,
|
tcp: self.tcp,
|
||||||
addr,
|
}
|
||||||
local_addr,
|
|
||||||
..
|
|
||||||
} = req;
|
|
||||||
|
|
||||||
TcpConnectorResponse::new(req, port, local_addr, addr)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TCP stream connector response future
|
/// Chains futures of resolve and connect steps.
|
||||||
pub enum TcpConnectorResponse<T> {
|
pub(crate) enum ConnectFut<R: Host> {
|
||||||
Response {
|
Resolve(<ResolverService as Service<ConnectInfo<R>>>::Future),
|
||||||
req: Option<T>,
|
Connect(<TcpConnectorService as Service<ConnectInfo<R>>>::Future),
|
||||||
port: u16,
|
|
||||||
local_addr: Option<IpAddr>,
|
|
||||||
addrs: Option<VecDeque<SocketAddr>>,
|
|
||||||
stream: ReusableBoxFuture<Result<TcpStream, io::Error>>,
|
|
||||||
},
|
|
||||||
Error(Option<ConnectError>),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Address> TcpConnectorResponse<T> {
|
/// Container for the intermediate states of [`ConnectFut`].
|
||||||
pub(crate) fn new(
|
pub(crate) enum ConnectFutState<R: Host> {
|
||||||
req: T,
|
Resolved(ConnectInfo<R>),
|
||||||
port: u16,
|
Connected(Connection<R, TcpStream>),
|
||||||
local_addr: Option<IpAddr>,
|
}
|
||||||
addr: ConnectAddrs,
|
|
||||||
) -> TcpConnectorResponse<T> {
|
|
||||||
if addr.is_none() {
|
|
||||||
error!("TCP connector: unresolved connection address");
|
|
||||||
return TcpConnectorResponse::Error(Some(ConnectError::Unresolved));
|
|
||||||
}
|
|
||||||
|
|
||||||
trace!(
|
impl<R: Host> ConnectFut<R> {
|
||||||
"TCP connector: connecting to {} on port {}",
|
fn poll_connect(
|
||||||
req.hostname(),
|
&mut self,
|
||||||
port
|
cx: &mut Context<'_>,
|
||||||
);
|
) -> Poll<Result<ConnectFutState<R>, ConnectError>> {
|
||||||
|
match self {
|
||||||
|
ConnectFut::Resolve(ref mut fut) => {
|
||||||
|
Pin::new(fut).poll(cx).map_ok(ConnectFutState::Resolved)
|
||||||
|
}
|
||||||
|
|
||||||
match addr {
|
ConnectFut::Connect(ref mut fut) => {
|
||||||
ConnectAddrs::None => unreachable!("none variant already checked"),
|
Pin::new(fut).poll(cx).map_ok(ConnectFutState::Connected)
|
||||||
|
|
||||||
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<T: Address> Future for TcpConnectorResponse<T> {
|
pub struct ConnectServiceResponse<R: Host> {
|
||||||
type Output = Result<Connection<T, TcpStream>, ConnectError>;
|
fut: ConnectFut<R>,
|
||||||
|
tcp: TcpConnectorService,
|
||||||
|
}
|
||||||
|
|
||||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
impl<R: Host> Future for ConnectServiceResponse<R> {
|
||||||
match self.get_mut() {
|
type Output = Result<Connection<R, TcpStream>, ConnectError>;
|
||||||
TcpConnectorResponse::Error(err) => Poll::Ready(Err(err.take().unwrap())),
|
|
||||||
|
|
||||||
TcpConnectorResponse::Response {
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
req,
|
loop {
|
||||||
port,
|
match ready!(self.fut.poll_connect(cx))? {
|
||||||
local_addr,
|
ConnectFutState::Resolved(res) => {
|
||||||
addrs,
|
self.fut = ConnectFut::Connect(self.tcp.call(res));
|
||||||
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)));
|
|
||||||
}
|
|
||||||
|
|
||||||
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)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
ConnectFutState::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;
|
use derive_more::Display;
|
||||||
|
|
||||||
|
/// Errors that can result from using a connector service.
|
||||||
#[derive(Debug, Display)]
|
#[derive(Debug, Display)]
|
||||||
pub enum ConnectError {
|
pub enum ConnectError {
|
||||||
/// Failed to resolve the hostname
|
/// Failed to resolve the hostname
|
||||||
#[display(fmt = "Failed resolving hostname: {}", _0)]
|
#[display(fmt = "Failed resolving hostname")]
|
||||||
Resolver(Box<dyn std::error::Error>),
|
Resolver(Box<dyn std::error::Error>),
|
||||||
|
|
||||||
/// No dns records
|
/// No DNS records
|
||||||
#[display(fmt = "No dns records found for the input")]
|
#[display(fmt = "No DNS records found for the input")]
|
||||||
NoRecords,
|
NoRecords,
|
||||||
|
|
||||||
/// Invalid input
|
/// Invalid input
|
||||||
@@ -23,3 +24,13 @@ pub enum ConnectError {
|
|||||||
#[display(fmt = "{}", _0)]
|
#[display(fmt = "{}", _0)]
|
||||||
Io(io::Error),
|
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,76 +1,46 @@
|
|||||||
//! TCP connector services for Actix ecosystem.
|
//! TCP and TLS connector services.
|
||||||
//!
|
//!
|
||||||
//! # Stages of the TCP connector service:
|
//! # Stages of the TCP connector service:
|
||||||
//! - Resolve [`Address`] with given [`Resolver`] and collect list of socket addresses.
|
//! 1. Resolve [`Host`] (if needed) with given [`Resolver`] and collect list of socket addresses.
|
||||||
//! - Establish TCP connection and return [`TcpStream`].
|
//! 1. Establish TCP connection and return [`TcpStream`].
|
||||||
//!
|
//!
|
||||||
//! # Stages of TLS connector services:
|
//! # Stages of TLS connector services:
|
||||||
//! - Establish [`TcpStream`] with connector service.
|
//! 1. Resolve DNS and establish a [`TcpStream`] with the TCP connector service.
|
||||||
//! - Wrap the stream and perform connect handshake with remote peer.
|
//! 1. Wrap the stream and perform connect handshake with remote peer.
|
||||||
//! - Return certain stream type that impls `AsyncRead` and `AsyncWrite`.
|
//! 1. Return wrapped stream type that implements `AsyncRead` and `AsyncWrite`.
|
||||||
//!
|
|
||||||
//! # Package feature
|
|
||||||
//! * `openssl` - enables TLS support via `openssl` crate
|
|
||||||
//! * `rustls` - enables TLS support via `rustls` crate
|
|
||||||
//!
|
//!
|
||||||
//! [`TcpStream`]: actix_rt::net::TcpStream
|
//! [`TcpStream`]: actix_rt::net::TcpStream
|
||||||
|
|
||||||
#[allow(clippy::module_inception)]
|
mod connect_addrs;
|
||||||
mod connect;
|
mod connection;
|
||||||
mod connector;
|
mod connector;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod host;
|
||||||
|
mod info;
|
||||||
mod resolve;
|
mod resolve;
|
||||||
mod service;
|
mod resolver;
|
||||||
pub mod tls;
|
pub mod tcp;
|
||||||
#[doc(hidden)]
|
|
||||||
pub use tls as ssl;
|
|
||||||
#[cfg(feature = "uri")]
|
#[cfg(feature = "uri")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "uri")))]
|
||||||
mod uri;
|
mod uri;
|
||||||
|
|
||||||
use actix_rt::net::TcpStream;
|
#[cfg(feature = "openssl")]
|
||||||
use actix_service::{Service, ServiceFactory};
|
#[cfg_attr(docsrs, doc(cfg(feature = "openssl")))]
|
||||||
|
pub mod openssl;
|
||||||
|
|
||||||
pub use self::connect::{Address, Connect, Connection};
|
#[cfg(feature = "rustls")]
|
||||||
pub use self::connector::{TcpConnector, TcpConnectorFactory};
|
#[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::error::ConnectError;
|
||||||
pub use self::resolve::{Resolve, Resolver, ResolverFactory};
|
pub use self::host::Host;
|
||||||
pub use self::service::{ConnectService, ConnectServiceFactory};
|
pub use self::info::ConnectInfo;
|
||||||
|
pub use self::resolve::Resolve;
|
||||||
/// Create TCP connector service.
|
pub use self::resolver::{Resolver, ResolverService};
|
||||||
pub fn new_connector<T: Address + 'static>(
|
|
||||||
resolver: Resolver,
|
|
||||||
) -> impl Service<Connect<T>, Response = Connection<T, TcpStream>, Error = ConnectError> + Clone
|
|
||||||
{
|
|
||||||
ConnectServiceFactory::new(resolver).service()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create 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)
|
|
||||||
}
|
|
||||||
|
92
actix-tls/src/connect/native_tls.rs
Normal file
92
actix-tls/src/connect/native_tls.rs
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
//! 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 AsyncNativeTlsConnector,
|
||||||
|
TlsStream as AsyncTlsStream,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::connect::{Connection, Host};
|
||||||
|
|
||||||
|
pub mod reexports {
|
||||||
|
//! Re-exports from `native-tls` and `tokio-native-tls` that are useful for connectors.
|
||||||
|
|
||||||
|
pub use tokio_native_tls::native_tls::TlsConnector;
|
||||||
|
|
||||||
|
pub use tokio_native_tls::TlsStream as AsyncTlsStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Connector service and factory using `native-tls`.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct TlsConnector {
|
||||||
|
connector: AsyncNativeTlsConnector,
|
||||||
|
}
|
||||||
|
|
||||||
|
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: AsyncNativeTlsConnector::from(connector),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Host, IO> ServiceFactory<Connection<R, IO>> for TlsConnector
|
||||||
|
where
|
||||||
|
IO: ActixStream + 'static,
|
||||||
|
{
|
||||||
|
type Response = Connection<R, AsyncTlsStream<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, AsyncTlsStream<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))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
148
actix-tls/src/connect/openssl.rs
Normal file
148
actix-tls/src/connect/openssl.rs
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
//! 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 as AsyncSslStream;
|
||||||
|
|
||||||
|
use crate::connect::{Connection, Host};
|
||||||
|
|
||||||
|
pub mod reexports {
|
||||||
|
//! Re-exports from `openssl` and `tokio-openssl` that are useful for connectors.
|
||||||
|
|
||||||
|
pub use openssl::ssl::{Error, HandshakeError, SslConnector, SslMethod};
|
||||||
|
|
||||||
|
pub use tokio_openssl::SslStream as AsyncSslStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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, AsyncSslStream<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, AsyncSslStream<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(AsyncSslStream::new(ssl, io).unwrap()),
|
||||||
|
stream: Some(stream),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Connect future for OpenSSL service.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub struct ConnectFut<R, IO> {
|
||||||
|
io: Option<AsyncSslStream<IO>>,
|
||||||
|
stream: Option<Connection<R, ()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Host, IO> Future for ConnectFut<R, IO>
|
||||||
|
where
|
||||||
|
R: Host,
|
||||||
|
IO: ActixStream,
|
||||||
|
{
|
||||||
|
type Output = Result<Connection<R, AsyncSslStream<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::{
|
//! The [`Resolve`] trait.
|
||||||
future::Future,
|
|
||||||
io,
|
|
||||||
net::SocketAddr,
|
|
||||||
pin::Pin,
|
|
||||||
rc::Rc,
|
|
||||||
task::{Context, Poll},
|
|
||||||
vec::IntoIter,
|
|
||||||
};
|
|
||||||
|
|
||||||
use actix_rt::task::{spawn_blocking, JoinHandle};
|
use std::{error::Error as StdError, net::SocketAddr};
|
||||||
use actix_service::{Service, ServiceFactory};
|
|
||||||
use futures_core::{future::LocalBoxFuture, ready};
|
|
||||||
use log::trace;
|
|
||||||
|
|
||||||
use super::connect::{Address, Connect};
|
use futures_core::future::LocalBoxFuture;
|
||||||
use super::error::ConnectError;
|
|
||||||
|
|
||||||
/// DNS Resolver Service Factory
|
/// Custom async DNS resolvers.
|
||||||
#[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.
|
|
||||||
///
|
///
|
||||||
/// # Usage
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
/// use std::net::SocketAddr;
|
/// 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(),
|
/// trust_dns: TokioAsyncResolver::tokio_from_system_conf().unwrap(),
|
||||||
/// };
|
/// };
|
||||||
///
|
///
|
||||||
/// // construct custom resolver
|
/// // wrap custom resolver
|
||||||
/// let resolver = Resolver::new_custom(resolver);
|
/// let resolver = Resolver::custom(my_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());
|
|
||||||
///
|
///
|
||||||
/// // resolver can be passed to connector factory where returned service factory
|
/// // resolver can be passed to connector factory where returned service factory
|
||||||
/// // can be used to construct new connector services.
|
/// // can be used to construct new connector services for use in clients
|
||||||
/// let factory = actix_tls::connect::new_connector_factory::<&str>(resolver);
|
/// let factory = actix_tls::connect::Connector::new(resolver);
|
||||||
|
/// let connector = factory.service();
|
||||||
/// ```
|
/// ```
|
||||||
pub trait Resolve {
|
pub trait Resolve {
|
||||||
|
/// Given DNS lookup information, returns a future that completes with socket information.
|
||||||
fn lookup<'a>(
|
fn lookup<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
host: &'a str,
|
host: &'a str,
|
||||||
port: u16,
|
port: u16,
|
||||||
) -> LocalBoxFuture<'a, Result<Vec<SocketAddr>, Box<dyn std::error::Error>>>;
|
) -> LocalBoxFuture<'a, Result<Vec<SocketAddr>, Box<dyn StdError>>>;
|
||||||
}
|
|
||||||
|
|
||||||
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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
150
actix-tls/src/connect/rustls.rs
Normal file
150
actix-tls/src/connect/rustls.rs
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
//! 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 as AsyncTlsStream, 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::rustls::ClientConfig;
|
||||||
|
|
||||||
|
pub use tokio_rustls::client::TlsStream as AsyncTlsStream;
|
||||||
|
|
||||||
|
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, AsyncTlsStream<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, AsyncTlsStream<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, AsyncTlsStream<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)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
204
actix-tls/src/connect/tcp.rs
Normal file
204
actix-tls/src/connect/tcp.rs
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
//! 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::{connect_addrs::ConnectAddrs, error::ConnectError, ConnectInfo, Connection, Host};
|
||||||
|
|
||||||
|
/// TCP connector service factory.
|
||||||
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct TcpConnector;
|
||||||
|
|
||||||
|
impl TcpConnector {
|
||||||
|
/// Returns a new TCP connector service.
|
||||||
|
pub fn service(&self) -> TcpConnectorService {
|
||||||
|
TcpConnectorService::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TCP connector service.
|
||||||
|
#[derive(Debug, Copy, Clone, Default)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 http::Uri;
|
||||||
|
|
||||||
use super::Address;
|
use super::Host;
|
||||||
|
|
||||||
impl Address for Uri {
|
impl Host for Uri {
|
||||||
fn hostname(&self) -> &str {
|
fn hostname(&self) -> &str {
|
||||||
self.host().unwrap_or("")
|
self.host().unwrap_or("")
|
||||||
}
|
}
|
||||||
@@ -35,9 +35,18 @@ fn scheme_to_port(scheme: Option<&str>) -> Option<u16> {
|
|||||||
Some("mqtts") => Some(8883),
|
Some("mqtts") => Some(8883),
|
||||||
|
|
||||||
// File Transfer Protocol (FTP)
|
// File Transfer Protocol (FTP)
|
||||||
Some("ftp") => Some(1883),
|
Some("ftp") => Some(21),
|
||||||
Some("ftps") => Some(990),
|
Some("ftps") => Some(990),
|
||||||
|
|
||||||
|
// Redis
|
||||||
|
Some("redis") => Some(6379),
|
||||||
|
|
||||||
|
// MySQL
|
||||||
|
Some("mysql") => Some(3306),
|
||||||
|
|
||||||
|
// PostgreSQL
|
||||||
|
Some("postgres") => Some(5432),
|
||||||
|
|
||||||
_ => None,
|
_ => 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)]
|
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||||
|
#![warn(missing_docs)]
|
||||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||||
|
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||||
|
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
#[allow(unused_extern_crates)]
|
#[allow(unused_extern_crates)]
|
||||||
extern crate tls_openssl as openssl;
|
extern crate tls_openssl as openssl;
|
||||||
|
|
||||||
#[cfg(feature = "accept")]
|
#[cfg(feature = "accept")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "accept")))]
|
||||||
pub mod accept;
|
pub mod accept;
|
||||||
|
|
||||||
#[cfg(feature = "connect")]
|
#[cfg(feature = "connect")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "connect")))]
|
||||||
pub mod connect;
|
pub mod connect;
|
||||||
|
130
actix-tls/tests/accept-openssl.rs
Normal file
130
actix-tls/tests/accept-openssl.rs
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
//! Use Rustls connector to test OpenSSL acceptor.
|
||||||
|
|
||||||
|
#![cfg(all(
|
||||||
|
feature = "accept",
|
||||||
|
feature = "connect",
|
||||||
|
feature = "rustls",
|
||||||
|
feature = "openssl"
|
||||||
|
))]
|
||||||
|
|
||||||
|
use std::{convert::TryFrom, io::Write, sync::Arc};
|
||||||
|
|
||||||
|
use actix_rt::net::TcpStream;
|
||||||
|
use actix_server::TestServer;
|
||||||
|
use actix_service::ServiceFactoryExt as _;
|
||||||
|
use actix_tls::accept::openssl::{Acceptor, TlsStream};
|
||||||
|
use actix_utils::future::ok;
|
||||||
|
use tokio_rustls::rustls::{Certificate, ClientConfig, RootCertStore, ServerName};
|
||||||
|
|
||||||
|
fn new_cert_and_key() -> (String, String) {
|
||||||
|
let cert = rcgen::generate_simple_self_signed(vec![
|
||||||
|
"127.0.0.1".to_owned(),
|
||||||
|
"localhost".to_owned(),
|
||||||
|
])
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let key = cert.serialize_private_key_pem();
|
||||||
|
let cert = cert.serialize_pem().unwrap();
|
||||||
|
|
||||||
|
(cert, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn openssl_acceptor(cert: String, key: String) -> tls_openssl::ssl::SslAcceptor {
|
||||||
|
use tls_openssl::{
|
||||||
|
pkey::PKey,
|
||||||
|
ssl::{SslAcceptor, SslMethod},
|
||||||
|
x509::X509,
|
||||||
|
};
|
||||||
|
|
||||||
|
let cert = X509::from_pem(cert.as_bytes()).unwrap();
|
||||||
|
let key = PKey::private_key_from_pem(key.as_bytes()).unwrap();
|
||||||
|
|
||||||
|
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
|
||||||
|
builder.set_certificate(&cert).unwrap();
|
||||||
|
builder.set_private_key(&key).unwrap();
|
||||||
|
builder.set_alpn_select_callback(|_, _protocols| Ok(b"http/1.1"));
|
||||||
|
builder.set_alpn_protos(b"\x08http/1.1").unwrap();
|
||||||
|
builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
mod danger {
|
||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use tokio_rustls::rustls::{
|
||||||
|
self,
|
||||||
|
client::{ServerCertVerified, ServerCertVerifier},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct NoCertificateVerification;
|
||||||
|
|
||||||
|
impl ServerCertVerifier for NoCertificateVerification {
|
||||||
|
fn verify_server_cert(
|
||||||
|
&self,
|
||||||
|
_end_entity: &Certificate,
|
||||||
|
_intermediates: &[Certificate],
|
||||||
|
_server_name: &ServerName,
|
||||||
|
_scts: &mut dyn Iterator<Item = &[u8]>,
|
||||||
|
_ocsp_response: &[u8],
|
||||||
|
_now: SystemTime,
|
||||||
|
) -> Result<ServerCertVerified, rustls::Error> {
|
||||||
|
Ok(ServerCertVerified::assertion())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn rustls_connector(_cert: String, _key: String) -> ClientConfig {
|
||||||
|
let mut config = ClientConfig::builder()
|
||||||
|
.with_safe_defaults()
|
||||||
|
.with_root_certificates(RootCertStore::empty())
|
||||||
|
.with_no_client_auth();
|
||||||
|
|
||||||
|
config
|
||||||
|
.dangerous()
|
||||||
|
.set_certificate_verifier(Arc::new(danger::NoCertificateVerification));
|
||||||
|
|
||||||
|
config.alpn_protocols = vec![b"http/1.1".to_vec()];
|
||||||
|
config
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn accepts_connections() {
|
||||||
|
let (cert, key) = new_cert_and_key();
|
||||||
|
|
||||||
|
let srv = TestServer::with({
|
||||||
|
let cert = cert.clone();
|
||||||
|
let key = key.clone();
|
||||||
|
|
||||||
|
move || {
|
||||||
|
let openssl_acceptor = openssl_acceptor(cert.clone(), key.clone());
|
||||||
|
let tls_acceptor = Acceptor::new(openssl_acceptor);
|
||||||
|
|
||||||
|
tls_acceptor
|
||||||
|
.map_err(|err| println!("OpenSSL error: {:?}", err))
|
||||||
|
.and_then(move |_stream: TlsStream<TcpStream>| ok(()))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut sock = srv
|
||||||
|
.connect()
|
||||||
|
.expect("cannot connect to test server")
|
||||||
|
.into_std()
|
||||||
|
.unwrap();
|
||||||
|
sock.set_nonblocking(false).unwrap();
|
||||||
|
|
||||||
|
let config = rustls_connector(cert, key);
|
||||||
|
let config = Arc::new(config);
|
||||||
|
|
||||||
|
let mut conn = tokio_rustls::rustls::ClientConnection::new(
|
||||||
|
config,
|
||||||
|
ServerName::try_from("localhost").unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut stream = tokio_rustls::rustls::Stream::new(&mut conn, &mut sock);
|
||||||
|
|
||||||
|
stream.flush().expect("TLS handshake failed");
|
||||||
|
}
|
106
actix-tls/tests/accept-rustls.rs
Normal file
106
actix-tls/tests/accept-rustls.rs
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
//! Use OpenSSL connector to test Rustls acceptor.
|
||||||
|
|
||||||
|
#![cfg(all(
|
||||||
|
feature = "accept",
|
||||||
|
feature = "connect",
|
||||||
|
feature = "rustls",
|
||||||
|
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::openssl::reexports::SslConnector;
|
||||||
|
use actix_utils::future::ok;
|
||||||
|
use rustls_pemfile::{certs, pkcs8_private_keys};
|
||||||
|
use tls_openssl::ssl::SslVerifyMode;
|
||||||
|
use tokio_rustls::rustls::{self, Certificate, PrivateKey, ServerConfig};
|
||||||
|
|
||||||
|
fn new_cert_and_key() -> (String, String) {
|
||||||
|
let cert = rcgen::generate_simple_self_signed(vec![
|
||||||
|
"127.0.0.1".to_owned(),
|
||||||
|
"localhost".to_owned(),
|
||||||
|
])
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let key = cert.serialize_private_key_pem();
|
||||||
|
let cert = cert.serialize_pem().unwrap();
|
||||||
|
|
||||||
|
(cert, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rustls_server_config(cert: String, key: String) -> rustls::ServerConfig {
|
||||||
|
// Load TLS key and cert files
|
||||||
|
|
||||||
|
let cert = &mut BufReader::new(cert.as_bytes());
|
||||||
|
let key = &mut BufReader::new(key.as_bytes());
|
||||||
|
|
||||||
|
let cert_chain = certs(cert).unwrap().into_iter().map(Certificate).collect();
|
||||||
|
let mut keys = pkcs8_private_keys(key).unwrap();
|
||||||
|
|
||||||
|
let mut config = ServerConfig::builder()
|
||||||
|
.with_safe_defaults()
|
||||||
|
.with_no_client_auth()
|
||||||
|
.with_single_cert(cert_chain, PrivateKey(keys.remove(0)))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
config.alpn_protocols = vec![b"http/1.1".to_vec()];
|
||||||
|
|
||||||
|
config
|
||||||
|
}
|
||||||
|
|
||||||
|
fn openssl_connector(cert: String, key: String) -> SslConnector {
|
||||||
|
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 = SslConnector::builder(SslMethod::tls()).unwrap();
|
||||||
|
ssl.set_verify(SslVerifyMode::NONE);
|
||||||
|
ssl.set_certificate(&cert).unwrap();
|
||||||
|
ssl.set_private_key(&key).unwrap();
|
||||||
|
ssl.set_alpn_protos(b"\x08http/1.1").unwrap();
|
||||||
|
|
||||||
|
ssl.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn accepts_connections() {
|
||||||
|
let (cert, key) = new_cert_and_key();
|
||||||
|
|
||||||
|
let srv = TestServer::with({
|
||||||
|
let cert = cert.clone();
|
||||||
|
let key = key.clone();
|
||||||
|
|
||||||
|
move || {
|
||||||
|
let tls_acceptor = Acceptor::new(rustls_server_config(cert.clone(), key.clone()));
|
||||||
|
|
||||||
|
tls_acceptor
|
||||||
|
.map_err(|err| println!("Rustls error: {:?}", err))
|
||||||
|
.and_then(move |_stream: TlsStream<TcpStream>| ok(()))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let sock = srv
|
||||||
|
.connect()
|
||||||
|
.expect("cannot connect to test server")
|
||||||
|
.into_std()
|
||||||
|
.unwrap();
|
||||||
|
sock.set_nonblocking(false).unwrap();
|
||||||
|
|
||||||
|
let connector = openssl_connector(cert, key);
|
||||||
|
|
||||||
|
let mut stream = connector
|
||||||
|
.connect("localhost", sock)
|
||||||
|
.expect("TLS handshake failed");
|
||||||
|
|
||||||
|
stream.do_handshake().expect("TLS handshake failed");
|
||||||
|
|
||||||
|
stream.flush().expect("TLS handshake failed");
|
||||||
|
}
|
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 bytes::Bytes;
|
||||||
use futures_util::sink::SinkExt;
|
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")]
|
#[cfg(feature = "openssl")]
|
||||||
#[actix_rt::test]
|
#[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 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());
|
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 addr = format!("localhost:{}", srv.port());
|
||||||
let con = conn.call(addr.into()).await.unwrap();
|
let con = conn.call(addr.into()).await.unwrap();
|
||||||
assert_eq!(con.peer_addr().unwrap(), srv.addr());
|
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
|
let info = ConnectInfo::new(srv.host().to_owned());
|
||||||
.call(Connect::with_addr("10", srv.addr()))
|
let connector = Connector::default().service();
|
||||||
.await
|
let conn = connector.call(info).await;
|
||||||
.unwrap();
|
assert!(conn.is_err());
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[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(|| {
|
let srv = TestServer::with(|| {
|
||||||
fn_service(|io: TcpStream| async {
|
fn_service(|io: TcpStream| async {
|
||||||
let mut framed = Framed::new(io, BytesCodec);
|
let mut framed = Framed::new(io, BytesCodec);
|
||||||
@@ -83,14 +89,11 @@ async fn test_new_service() {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
let factory = actix_connect::default_connector_factory();
|
let info = ConnectInfo::with_addr("10", srv.addr());
|
||||||
|
let factory = default_connector_factory();
|
||||||
let conn = factory.new_service(()).await.unwrap();
|
let connector = factory.new_service(()).await.unwrap();
|
||||||
let con = conn
|
let con = connector.call(info).await;
|
||||||
.call(Connect::with_addr("10", srv.addr()))
|
assert_eq!(con.unwrap().peer_addr().unwrap(), srv.addr());
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(con.peer_addr().unwrap(), srv.addr());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "openssl", feature = "uri"))]
|
#[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 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());
|
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 addr = http::Uri::try_from(format!("https://localhost:{}", srv.port())).unwrap();
|
||||||
let con = conn.call(addr.into()).await.unwrap();
|
let con = conn.call(addr.into()).await.unwrap();
|
||||||
assert_eq!(con.peer_addr().unwrap(), srv.addr());
|
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 local = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 3));
|
||||||
|
|
||||||
let (con, _) = conn
|
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
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_parts();
|
.into_parts();
|
||||||
|
@@ -10,7 +10,9 @@ use actix_server::TestServer;
|
|||||||
use actix_service::{fn_service, Service, ServiceFactory};
|
use actix_service::{fn_service, Service, ServiceFactory};
|
||||||
use futures_core::future::LocalBoxFuture;
|
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]
|
#[actix_rt::test]
|
||||||
async fn custom_resolver() {
|
async fn custom_resolver() {
|
||||||
@@ -36,6 +38,18 @@ async fn custom_resolver() {
|
|||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn custom_resolver_connect() {
|
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;
|
use trust_dns_resolver::TokioAsyncResolver;
|
||||||
|
|
||||||
let srv =
|
let srv =
|
||||||
@@ -68,12 +82,11 @@ async fn custom_resolver_connect() {
|
|||||||
trust_dns: TokioAsyncResolver::tokio_from_system_conf().unwrap(),
|
trust_dns: TokioAsyncResolver::tokio_from_system_conf().unwrap(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let resolver = Resolver::new_custom(resolver);
|
let factory = connector_factory(Resolver::custom(resolver));
|
||||||
let factory = new_connector_factory(resolver);
|
|
||||||
|
|
||||||
let conn = factory.new_service(()).await.unwrap();
|
let conn = factory.new_service(()).await.unwrap();
|
||||||
let con = conn
|
let con = conn
|
||||||
.call(Connect::with_addr("example.com", srv.addr()))
|
.call(ConnectInfo::with_addr("example.com", srv.addr()))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(con.peer_addr().unwrap(), srv.addr());
|
assert_eq!(con.peer_addr().unwrap(), srv.addr());
|
||||||
|
@@ -23,3 +23,4 @@ local-waker = "0.1"
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.0.0"
|
actix-rt = "2.0.0"
|
||||||
futures-util = { version = "0.3.7", default-features = false }
|
futures-util = { version = "0.3.7", default-features = false }
|
||||||
|
static_assertions = "1.1"
|
||||||
|
@@ -26,7 +26,7 @@ impl Counter {
|
|||||||
CounterGuard::new(self.0.clone())
|
CounterGuard::new(self.0.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Notify current task and return true if counter is at capacity.
|
/// Returns true if counter is below capacity. Otherwise, register to wake task when it is.
|
||||||
pub fn available(&self, cx: &mut task::Context<'_>) -> bool {
|
pub fn available(&self, cx: &mut task::Context<'_>) -> bool {
|
||||||
self.0.available(cx)
|
self.0.available(cx)
|
||||||
}
|
}
|
||||||
|
@@ -103,10 +103,16 @@ pub fn err<T, E>(err: E) -> Ready<Result<T, E>> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use futures_util::task::noop_waker;
|
use futures_util::task::noop_waker;
|
||||||
|
use static_assertions::{assert_impl_all, assert_not_impl_all};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
assert_impl_all!(Ready<()>: Send, Sync, Clone);
|
||||||
|
assert_not_impl_all!(Ready<Rc<()>>: Send, Sync);
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
fn multiple_poll_panics() {
|
fn multiple_poll_panics() {
|
||||||
|
@@ -10,7 +10,6 @@ keywords = ["string", "bytes", "utf8", "web", "actix"]
|
|||||||
categories = ["no-std", "web-programming"]
|
categories = ["no-std", "web-programming"]
|
||||||
homepage = "https://actix.rs"
|
homepage = "https://actix.rs"
|
||||||
repository = "https://github.com/actix/actix-net.git"
|
repository = "https://github.com/actix/actix-net.git"
|
||||||
documentation = "https://docs.rs/bytestring"
|
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
@@ -23,6 +22,7 @@ bytes = "1"
|
|||||||
serde = { version = "1.0", optional = true }
|
serde = { version = "1.0", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
ahash = { version = "0.7.6", default-features = false }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
# TODO: remove when ahash MSRV is restored
|
static_assertions = "1.1"
|
||||||
ahash = { version = "=0.7.4", default-features = false }
|
rustversion = "1"
|
||||||
|
@@ -2,8 +2,7 @@
|
|||||||
|
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
#![warn(missing_docs)]
|
||||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
|
||||||
|
|
||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
|
|
||||||
@@ -217,6 +216,16 @@ mod serde {
|
|||||||
String::deserialize(deserializer).map(ByteString::from)
|
String::deserialize(deserializer).map(ByteString::from)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod serde_impl_tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use static_assertions::assert_impl_all;
|
||||||
|
|
||||||
|
assert_impl_all!(ByteString: Serialize, DeserializeOwned);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -225,9 +234,24 @@ mod test {
|
|||||||
use core::hash::{Hash, Hasher};
|
use core::hash::{Hash, Hasher};
|
||||||
|
|
||||||
use ahash::AHasher;
|
use ahash::AHasher;
|
||||||
|
use static_assertions::assert_impl_all;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
assert_impl_all!(ByteString: Send, Sync, Unpin, Sized);
|
||||||
|
assert_impl_all!(ByteString: Clone, Default, Eq, PartialOrd, Ord);
|
||||||
|
assert_impl_all!(ByteString: fmt::Debug, fmt::Display);
|
||||||
|
|
||||||
|
#[rustversion::since(1.56)]
|
||||||
|
mod above_1_56_impls {
|
||||||
|
// `[Ref]UnwindSafe` traits were only in std until rust 1.56
|
||||||
|
|
||||||
|
use core::panic::{RefUnwindSafe, UnwindSafe};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
assert_impl_all!(ByteString: UnwindSafe, RefUnwindSafe);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_partial_eq() {
|
fn test_partial_eq() {
|
||||||
let s: ByteString = ByteString::from_static("test");
|
let s: ByteString = ByteString::from_static("test");
|
||||||
|
@@ -1,3 +1,8 @@
|
|||||||
//! Non-thread-safe channels.
|
//! Non-thread-safe channels.
|
||||||
|
|
||||||
|
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||||
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
|
extern crate alloc;
|
||||||
|
|
||||||
pub mod mpsc;
|
pub mod mpsc;
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
//! A non-thread-safe multi-producer, single-consumer, futures-aware, FIFO queue.
|
//! A non-thread-safe multi-producer, single-consumer, futures-aware, FIFO queue.
|
||||||
|
|
||||||
|
use alloc::{collections::VecDeque, rc::Rc};
|
||||||
use core::{
|
use core::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
fmt,
|
fmt,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
|
use std::error::Error;
|
||||||
use std::{collections::VecDeque, error::Error, rc::Rc};
|
|
||||||
|
|
||||||
use futures_core::stream::Stream;
|
use futures_core::stream::Stream;
|
||||||
use futures_sink::Sink;
|
use futures_sink::Sink;
|
||||||
|
@@ -3,6 +3,8 @@
|
|||||||
//! See docs for [`LocalWaker`].
|
//! See docs for [`LocalWaker`].
|
||||||
|
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||||
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
use core::{cell::Cell, fmt, marker::PhantomData, task::Waker};
|
use core::{cell::Cell, fmt, marker::PhantomData, task::Waker};
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user