1
0
mirror of https://github.com/fafhrd91/actix-net synced 2025-08-12 16:47:05 +02:00

Compare commits

..

44 Commits

Author SHA1 Message Date
Rob Ede
89a4c2ee27 prepare actix-tls release 3.0.0 2021-12-26 21:12:19 +00:00
Rob Ede
a4681831a7 fix changelogs 2021-12-18 03:35:18 +00:00
Rob Ede
5d2da0fdc7 prepare actix-service release 2.0.2 2021-12-18 03:26:59 +00:00
Rob Ede
ef18a8342e prepare local-waker release 0.1.2 2021-12-18 03:20:49 +00:00
Rob Ede
621deba990 fix changelog bullet points 2021-12-18 02:49:23 +00:00
Rob Ede
6a9f13c8b4 inline simple future functions 2021-12-18 02:18:01 +00:00
Rob Ede
705b31230f fix impl assertion tests 2021-12-18 02:17:48 +00:00
Rob Ede
eb490a9125 re-export openssl connector builder (#429) 2021-12-10 23:11:24 +00:00
Rob Ede
90f205a465 standardize crate level lints 2021-12-08 06:09:46 +00:00
Rob Ede
3a3d654cea use "physical" cpu cores as default worker count 2021-12-08 05:42:54 +00:00
Rob Ede
ba901c70df prepare actix-server release 2.0.0-rc.1 2021-12-05 19:34:36 +00:00
Ali MJ Al-Nasrawy
4e0dd091f5 Server: run after await (#426)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-12-05 19:22:47 +00:00
Ali MJ Al-Nasrawy
8c4ec34cd4 Server: hide internal structure (#424) 2021-12-05 16:44:06 +00:00
Ali MJ Al-Nasrawy
62ffe5f389 fix auto-traits for service types (#397) 2021-12-04 22:30:04 +00:00
Rob Ede
07e3b19461 dedupe tls changelog 2021-12-04 19:51:33 +00:00
Rob Ede
183bcf6ae3 prepare actix-tls release 3.0.0-rc.1 (#423) 2021-11-30 12:34:46 +00:00
Rob Ede
5dc2bfcb01 actix-tls release candidate prep (#422) 2021-11-29 23:53:06 +00:00
Rob Ede
5556afd524 fix bytestring test on older rust versions 2021-11-28 01:19:57 +00:00
Rob Ede
de5908bfe7 tls doc updates 2021-11-28 00:56:15 +00:00
Rob Ede
c63880a292 doc updates 2021-11-28 00:46:29 +00:00
Rob Ede
44e4381879 add some internal server documentation 2021-11-28 00:35:34 +00:00
Rob Ede
18eced7305 add static assertions to bytestring 2021-11-25 03:29:30 +00:00
Rob Ede
a2437eed29 prepare actix-tls release 3.0.0-beta.9 2021-11-22 13:34:54 +00:00
Rob Ede
67b357a175 add TlsError::into_service_error (#420) 2021-11-22 13:33:20 +00:00
Rob Ede
3597af5c45 prepare actix-rt release 2.5.0 2021-11-22 01:15:18 +00:00
Rob Ede
8891c2681e address unused warning 2021-11-21 23:42:51 +00:00
Rob Ede
233c61ba08 remove dead code 2021-11-21 23:29:25 +00:00
Rob Ede
161f239f12 server: panic earlier if neither runtime detected 2021-11-21 23:29:06 +00:00
fakeshadow
7e7df2f931 add timeout for accepting tls connections (#393)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-11-16 00:22:24 +00:00
Luca Bruno
ce8ec15eaa system: run and return exit code on stop (#411)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-11-15 18:49:02 +00:00
Rob Ede
ae28ce5377 update mio to 0.8 2021-11-15 18:48:37 +00:00
Rob Ede
54d1d9e520 prepare actix-tls release 3.0.0-beta.8 2021-11-15 17:55:23 +00:00
Alexander Polakov
0b0cbd5388 actix-tls: allow getting uri from Connect (#415)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-11-15 10:39:42 +00:00
Rob Ede
443a328fb4 prepare actix-server release 2.0.0-beta.9 2021-11-15 02:39:55 +00:00
Rob Ede
58a67ade32 improve docs of system_exit 2021-11-15 02:33:13 +00:00
Rob Ede
38caa8f088 Fix server arbiter support (#417) 2021-11-14 19:45:15 +00:00
Rob Ede
ed987eef06 prepare actix-server release 2.0.0-beta.8 2021-11-07 15:46:59 +00:00
fakeshadow
3658929010 fix io-uring feature for actix-server (#414)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-11-07 15:43:59 +00:00
fakeshadow
3f49d8ab54 remove usage of mio::net::TcpSocket (#413) 2021-11-07 14:18:23 +00:00
fakeshadow
161d1ee94b fix accept timeout and worker graceful shutdown (#412) 2021-11-07 13:00:19 +00:00
Rob Ede
81ba7cafaa fix server non-unix signal impl send (#410) 2021-11-05 02:16:13 +00:00
Rob Ede
f8f51a2240 prepare actix-server release 2.0.0-beta.7 2021-11-05 01:14:28 +00:00
Rob Ede
a2e765ea6e prepare actix-codec release 0.4.1 2021-11-05 01:05:51 +00:00
Rob Ede
03dae6a4a4 prepare actix-rt release 2.4.0 2021-11-05 00:51:34 +00:00
92 changed files with 3235 additions and 2468 deletions

View File

@@ -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"

View File

@@ -196,13 +196,6 @@ jobs:
- name: Cache Dependencies - name: Cache Dependencies
uses: Swatinem/rust-cache@v1.3.0 uses: Swatinem/rust-cache@v1.3.0
- name: Install cargo-hack - name: doc tests io-uring
uses: actions-rs/cargo@v1 run: |
with: sudo bash -c "ulimit -Sl 512 && ulimit -Hl 512 && PATH=$PATH:/usr/share/rust/.cargo/bin && RUSTUP_TOOLCHAIN=nightly cargo ci-doctest"
command: install
args: cargo-hack
- name: doc tests
uses: actions-rs/cargo@v1
timeout-minutes: 40
with: { command: ci-doctest }

View File

@@ -1,68 +1,71 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
* Added `LinesCodec.` [#338]
* `Framed::poll_ready` flushes when the buffer is full. [#409]
## 0.4.1 - 2021-11-05
- Added `LinesCodec.` [#338]
- `Framed::poll_ready` flushes when the buffer is full. [#409]
[#338]: https://github.com/actix/actix-net/pull/338 [#338]: https://github.com/actix/actix-net/pull/338
[#409]: https://github.com/actix/actix-net/pull/409 [#409]: https://github.com/actix/actix-net/pull/409
## 0.4.0 - 2021-04-20 ## 0.4.0 - 2021-04-20
* No significant changes since v0.4.0-beta.1. - No significant changes since v0.4.0-beta.1.
## 0.4.0-beta.1 - 2020-12-28 ## 0.4.0-beta.1 - 2020-12-28
* Replace `pin-project` with `pin-project-lite`. [#237] - Replace `pin-project` with `pin-project-lite`. [#237]
* Upgrade `tokio` dependency to `1`. [#237] - Upgrade `tokio` dependency to `1`. [#237]
* Upgrade `tokio-util` dependency to `0.6`. [#237] - Upgrade `tokio-util` dependency to `0.6`. [#237]
* Upgrade `bytes` dependency to `1`. [#237] - Upgrade `bytes` dependency to `1`. [#237]
[#237]: https://github.com/actix/actix-net/pull/237 [#237]: https://github.com/actix/actix-net/pull/237
## 0.3.0 - 2020-08-23 ## 0.3.0 - 2020-08-23
* No changes from beta 2. - No changes from beta 2.
## 0.3.0-beta.2 - 2020-08-19 ## 0.3.0-beta.2 - 2020-08-19
* Remove unused type parameter from `Framed::replace_codec`. - Remove unused type parameter from `Framed::replace_codec`.
## 0.3.0-beta.1 - 2020-08-19 ## 0.3.0-beta.1 - 2020-08-19
* Use `.advance()` instead of `.split_to()`. - Use `.advance()` instead of `.split_to()`.
* Upgrade `tokio-util` to `0.3`. - Upgrade `tokio-util` to `0.3`.
* Improve `BytesCodec::encode()` performance. - Improve `BytesCodec::encode()` performance.
* Simplify `BytesCodec::decode()`. - Simplify `BytesCodec::decode()`.
* Rename methods on `Framed` to better describe their use. - Rename methods on `Framed` to better describe their use.
* Add method on `Framed` to get a pinned reference to the underlying I/O. - Add method on `Framed` to get a pinned reference to the underlying I/O.
* Add method on `Framed` check emptiness of read buffer. - Add method on `Framed` check emptiness of read buffer.
## 0.2.0 - 2019-12-10 ## 0.2.0 - 2019-12-10
* Use specific futures dependencies. - Use specific futures dependencies.
## 0.2.0-alpha.4 ## 0.2.0-alpha.4
* Fix buffer remaining capacity calculation. - Fix buffer remaining capacity calculation.
## 0.2.0-alpha.3 ## 0.2.0-alpha.3
* Use tokio 0.2. - Use tokio 0.2.
* Fix low/high watermark for write/read buffers. - Fix low/high watermark for write/read buffers.
## 0.2.0-alpha.2 ## 0.2.0-alpha.2
* Migrated to `std::future`. - Migrated to `std::future`.
## 0.1.2 - 2019-03-27 ## 0.1.2 - 2019-03-27
* Added `Framed::map_io()` method. - Added `Framed::map_io()` method.
## 0.1.1 - 2019-03-06 ## 0.1.1 - 2019-03-06
* Added `FramedParts::with_read_buffer()` method. - Added `FramedParts::with_read_buffer()` method.
## 0.1.0 - 2018-12-09 ## 0.1.0 - 2018-12-09
* Move codec to separate crate. - Move codec to separate crate.

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-codec" name = "actix-codec"
version = "0.4.0" version = "0.4.1"
authors = [ authors = [
"Nikolay Kim <fafhrd91@gmail.com>", "Nikolay Kim <fafhrd91@gmail.com>",
"Rob Ede <robjtede@icloud.com>", "Rob Ede <robjtede@icloud.com>",

View File

@@ -1,10 +1,14 @@
use std::pin::Pin; use std::{
use std::task::{Context, Poll}; fmt, io,
use std::{fmt, io}; pin::Pin,
task::{Context, Poll},
};
use bitflags::bitflags;
use bytes::{Buf, BytesMut}; use bytes::{Buf, BytesMut};
use futures_core::{ready, Stream}; use futures_core::{ready, Stream};
use futures_sink::Sink; use futures_sink::Sink;
use pin_project_lite::pin_project;
use crate::{AsyncRead, AsyncWrite, Decoder, Encoder}; use crate::{AsyncRead, AsyncWrite, Decoder, Encoder};
@@ -13,14 +17,14 @@ const LW: usize = 1024;
/// High-water mark /// High-water mark
const HW: usize = 8 * 1024; const HW: usize = 8 * 1024;
bitflags::bitflags! { bitflags! {
struct Flags: u8 { struct Flags: u8 {
const EOF = 0b0001; const EOF = 0b0001;
const READABLE = 0b0010; const READABLE = 0b0010;
} }
} }
pin_project_lite::pin_project! { pin_project! {
/// A unified `Stream` and `Sink` interface to an underlying I/O object, using the `Encoder` and /// A unified `Stream` and `Sink` interface to an underlying I/O object, using the `Encoder` and
/// `Decoder` traits to encode and decode frames. /// `Decoder` traits to encode and decode frames.
/// ///

View File

@@ -7,8 +7,8 @@
//! [`Sink`]: futures_sink::Sink //! [`Sink`]: futures_sink::Sink
//! [`Stream`]: futures_core::Stream //! [`Stream`]: futures_core::Stream
#![deny(rust_2018_idioms, nonstandard_style, future_incompatible)] #![deny(rust_2018_idioms, nonstandard_style)]
#![warn(missing_docs)] #![warn(future_incompatible, 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")]

View File

@@ -4,43 +4,43 @@
## 0.2.3 - 2021-10-19 ## 0.2.3 - 2021-10-19
* Fix test macro in presence of other imports named "test". [#399] - Fix test macro in presence of other imports named "test". [#399]
[#399]: https://github.com/actix/actix-net/pull/399 [#399]: https://github.com/actix/actix-net/pull/399
## 0.2.2 - 2021-10-14 ## 0.2.2 - 2021-10-14
* Improve error recovery potential when macro input is invalid. [#391] - Improve error recovery potential when macro input is invalid. [#391]
* Allow custom `System`s on test macro. [#391] - Allow custom `System`s on test macro. [#391]
[#391]: https://github.com/actix/actix-net/pull/391 [#391]: https://github.com/actix/actix-net/pull/391
## 0.2.1 - 2021-02-02 ## 0.2.1 - 2021-02-02
* Add optional argument `system` to `main` macro which can be used to specify the path to `actix_rt::System` (useful for re-exports). [#363] - Add optional argument `system` to `main` macro which can be used to specify the path to `actix_rt::System` (useful for re-exports). [#363]
[#363]: https://github.com/actix/actix-net/pull/363 [#363]: https://github.com/actix/actix-net/pull/363
## 0.2.0 - 2021-02-02 ## 0.2.0 - 2021-02-02
* Update to latest `actix_rt::System::new` signature. [#261] - Update to latest `actix_rt::System::new` signature. [#261]
[#261]: https://github.com/actix/actix-net/pull/261 [#261]: https://github.com/actix/actix-net/pull/261
## 0.2.0-beta.1 - 2021-01-09 ## 0.2.0-beta.1 - 2021-01-09
* Remove `actix-reexport` feature. [#218] - Remove `actix-reexport` feature. [#218]
[#218]: https://github.com/actix/actix-net/pull/218 [#218]: https://github.com/actix/actix-net/pull/218
## 0.1.3 - 2020-12-03 ## 0.1.3 - 2020-12-03
* Add `actix-reexport` feature. [#218] - Add `actix-reexport` feature. [#218]
[#218]: https://github.com/actix/actix-net/pull/218 [#218]: https://github.com/actix/actix-net/pull/218
## 0.1.2 - 2020-05-18 ## 0.1.2 - 2020-05-18
* Forward actix_rt::test arguments to test function [#127] - Forward actix_rt::test arguments to test function [#127]
[#127]: https://github.com/actix/actix-net/pull/127 [#127]: https://github.com/actix/actix-net/pull/127

View File

@@ -9,6 +9,7 @@
//! See docs for the [`#[test]`](macro@test) macro. //! See docs for the [`#[test]`](macro@test) macro.
#![deny(rust_2018_idioms, nonstandard_style)] #![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)]
#![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")]

View File

@@ -1,23 +1,32 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
* 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]
## 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
- 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]
[#395]: https://github.com/actix/actix-net/pull/395 [#395]: https://github.com/actix/actix-net/pull/395
[#408]: https://github.com/actix/actix-net/pull/408 [#408]: https://github.com/actix/actix-net/pull/408
## 2.3.0 - 2021-10-11 ## 2.3.0 - 2021-10-11
* The `spawn` method can now resolve with non-unit outputs. [#369] - The `spawn` method can now resolve with non-unit outputs. [#369]
* Add experimental (semver-exempt) `io-uring` feature for enabling async file I/O on linux. [#374] - Add experimental (semver-exempt) `io-uring` feature for enabling async file I/O on linux. [#374]
[#369]: https://github.com/actix/actix-net/pull/369 [#369]: https://github.com/actix/actix-net/pull/369
[#374]: https://github.com/actix/actix-net/pull/374 [#374]: https://github.com/actix/actix-net/pull/374
## 2.2.0 - 2021-03-29 ## 2.2.0 - 2021-03-29
* **BREAKING** `ActixStream::{poll_read_ready, poll_write_ready}` methods now return - **BREAKING** `ActixStream::{poll_read_ready, poll_write_ready}` methods now return
`Ready` object in ok variant. [#293] `Ready` object in ok variant. [#293]
* Breakage is acceptable since `ActixStream` was not intended to be public. * Breakage is acceptable since `ActixStream` was not intended to be public.
@@ -25,51 +34,51 @@
## 2.1.0 - 2021-02-24 ## 2.1.0 - 2021-02-24
* Add `ActixStream` extension trait to include readiness methods. [#276] - Add `ActixStream` extension trait to include readiness methods. [#276]
* Re-export `tokio::net::TcpSocket` in `net` module [#282] - Re-export `tokio::net::TcpSocket` in `net` module [#282]
[#276]: https://github.com/actix/actix-net/pull/276 [#276]: https://github.com/actix/actix-net/pull/276
[#282]: https://github.com/actix/actix-net/pull/282 [#282]: https://github.com/actix/actix-net/pull/282
## 2.0.2 - 2021-02-06 ## 2.0.2 - 2021-02-06
* Add `Arbiter::handle` to get a handle of an owned Arbiter. [#274] - Add `Arbiter::handle` to get a handle of an owned Arbiter. [#274]
* Add `System::try_current` for situations where actix may or may not be running a System. [#275] - Add `System::try_current` for situations where actix may or may not be running a System. [#275]
[#274]: https://github.com/actix/actix-net/pull/274 [#274]: https://github.com/actix/actix-net/pull/274
[#275]: https://github.com/actix/actix-net/pull/275 [#275]: https://github.com/actix/actix-net/pull/275
## 2.0.1 - 2021-02-06 ## 2.0.1 - 2021-02-06
* Expose `JoinError` from Tokio. [#271] - Expose `JoinError` from Tokio. [#271]
[#271]: https://github.com/actix/actix-net/pull/271 [#271]: https://github.com/actix/actix-net/pull/271
## 2.0.0 - 2021-02-02 ## 2.0.0 - 2021-02-02
* Remove all Arbiter-local storage methods. [#262] - Remove all Arbiter-local storage methods. [#262]
* Re-export `tokio::pin`. [#262] - Re-export `tokio::pin`. [#262]
[#262]: https://github.com/actix/actix-net/pull/262 [#262]: https://github.com/actix/actix-net/pull/262
## 2.0.0-beta.3 - 2021-01-31 ## 2.0.0-beta.3 - 2021-01-31
* Remove `run_in_tokio`, `attach_to_tokio` and `AsyncSystemRunner`. [#253] - Remove `run_in_tokio`, `attach_to_tokio` and `AsyncSystemRunner`. [#253]
* Return `JoinHandle` from `actix_rt::spawn`. [#253] - Return `JoinHandle` from `actix_rt::spawn`. [#253]
* Remove old `Arbiter::spawn`. Implementation is now inlined into `actix_rt::spawn`. [#253] - Remove old `Arbiter::spawn`. Implementation is now inlined into `actix_rt::spawn`. [#253]
* Rename `Arbiter::{send => spawn}` and `Arbiter::{exec_fn => spawn_fn}`. [#253] - Rename `Arbiter::{send => spawn}` and `Arbiter::{exec_fn => spawn_fn}`. [#253]
* Remove `Arbiter::exec`. [#253] - Remove `Arbiter::exec`. [#253]
* Remove deprecated `Arbiter::local_join` and `Arbiter::is_running`. [#253] - Remove deprecated `Arbiter::local_join` and `Arbiter::is_running`. [#253]
* `Arbiter::spawn` now accepts !Unpin futures. [#256] - `Arbiter::spawn` now accepts !Unpin futures. [#256]
* `System::new` no longer takes arguments. [#257] - `System::new` no longer takes arguments. [#257]
* Remove `System::with_current`. [#257] - Remove `System::with_current`. [#257]
* Remove `Builder`. [#257] - Remove `Builder`. [#257]
* Add `System::with_init` as replacement for `Builder::run`. [#257] - Add `System::with_init` as replacement for `Builder::run`. [#257]
* Rename `System::{is_set => is_registered}`. [#257] - Rename `System::{is_set => is_registered}`. [#257]
* Add `ArbiterHandle` for sending messages to non-current-thread arbiters. [#257]. - Add `ArbiterHandle` for sending messages to non-current-thread arbiters. [#257].
* `System::arbiter` now returns an `&ArbiterHandle`. [#257] - `System::arbiter` now returns an `&ArbiterHandle`. [#257]
* `Arbiter::current` now returns an `ArbiterHandle` instead. [#257] - `Arbiter::current` now returns an `ArbiterHandle` instead. [#257]
* `Arbiter::join` now takes self by value. [#257] - `Arbiter::join` now takes self by value. [#257]
[#253]: https://github.com/actix/actix-net/pull/253 [#253]: https://github.com/actix/actix-net/pull/253
[#254]: https://github.com/actix/actix-net/pull/254 [#254]: https://github.com/actix/actix-net/pull/254
@@ -78,37 +87,37 @@
## 2.0.0-beta.2 - 2021-01-09 ## 2.0.0-beta.2 - 2021-01-09
* Add `task` mod with re-export of `tokio::task::{spawn_blocking, yield_now, JoinHandle}` [#245] - Add `task` mod with re-export of `tokio::task::{spawn_blocking, yield_now, JoinHandle}` [#245]
* Add default "macros" feature to allow faster compile times when using `default-features=false`. - Add default "macros" feature to allow faster compile times when using `default-features=false`.
[#245]: https://github.com/actix/actix-net/pull/245 [#245]: https://github.com/actix/actix-net/pull/245
## 2.0.0-beta.1 - 2020-12-28 ## 2.0.0-beta.1 - 2020-12-28
* Add `System::attach_to_tokio` method. [#173] - Add `System::attach_to_tokio` method. [#173]
* Update `tokio` dependency to `1.0`. [#236] - Update `tokio` dependency to `1.0`. [#236]
* Rename `time` module `delay_for` to `sleep`, `delay_until` to `sleep_until`, `Delay` to `Sleep` - Rename `time` module `delay_for` to `sleep`, `delay_until` to `sleep_until`, `Delay` to `Sleep`
to stay aligned with Tokio's naming. [#236] to stay aligned with Tokio's naming. [#236]
* Remove `'static` lifetime requirement for `Runtime::block_on` and `SystemRunner::block_on`. - Remove `'static` lifetime requirement for `Runtime::block_on` and `SystemRunner::block_on`.
* These methods now accept `&self` when calling. [#236] * These methods now accept `&self` when calling. [#236]
* Remove `'static` lifetime requirement for `System::run` and `Builder::run`. [#236] - Remove `'static` lifetime requirement for `System::run` and `Builder::run`. [#236]
* `Arbiter::spawn` now panics when `System` is not in scope. [#207] - `Arbiter::spawn` now panics when `System` is not in scope. [#207]
* Fix work load issue by removing `PENDING` thread local. [#207] - Fix work load issue by removing `PENDING` thread local. [#207]
[#207]: https://github.com/actix/actix-net/pull/207 [#207]: https://github.com/actix/actix-net/pull/207
[#236]: https://github.com/actix/actix-net/pull/236 [#236]: https://github.com/actix/actix-net/pull/236
## 1.1.1 - 2020-04-30 ## 1.1.1 - 2020-04-30
* Fix memory leak due to [#94] (see [#129] for more detail) - Fix memory leak due to [#94] (see [#129] for more detail)
[#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
to get be able to `await` for spawned futures [#94] to get be able to `await` for spawned futures [#94]
[#94]: https://github.com/actix/actix-net/pull/94 [#94]: https://github.com/actix/actix-net/pull/94
@@ -117,55 +126,55 @@
## 1.0.0 - 2019-12-11 ## 1.0.0 - 2019-12-11
* Update dependencies - Update dependencies
## 1.0.0-alpha.3 - 2019-12-07 ## 1.0.0-alpha.3 - 2019-12-07
* Migrate to tokio 0.2 - Migrate to tokio 0.2
* Fix compilation on non-unix platforms - Fix compilation on non-unix platforms
## 1.0.0-alpha.2 - 2019-12-02 ## 1.0.0-alpha.2 - 2019-12-02
* Export `main` and `test` attribute macros - Export `main` and `test` attribute macros
* Export `time` module (re-export of tokio-timer) - Export `time` module (re-export of tokio-timer)
* Export `net` module (re-export of tokio-net) - Export `net` module (re-export of tokio-net)
## 1.0.0-alpha.1 - 2019-11-22 ## 1.0.0-alpha.1 - 2019-11-22
* Migrate to std::future and tokio 0.2 - Migrate to std::future and tokio 0.2
## 0.2.6 - 2019-11-14 ## 0.2.6 - 2019-11-14
* Allow to join arbiter's thread. #60 - Allow to join arbiter's thread. #60
* Fix arbiter's thread panic message. - Fix arbiter's thread panic message.
## 0.2.5 - 2019-09-02 ## 0.2.5 - 2019-09-02
* Add arbiter specific storage - Add arbiter specific storage
## 0.2.4 - 2019-07-17 ## 0.2.4 - 2019-07-17
* Avoid a copy of the Future when initializing the Box. #29 - Avoid a copy of the Future when initializing the Box. #29
## 0.2.3 - 2019-06-22 ## 0.2.3 - 2019-06-22
* Allow to start System using existing CurrentThread Handle #22 - Allow to start System using existing CurrentThread Handle #22
## 0.2.2 - 2019-03-28 ## 0.2.2 - 2019-03-28
* Moved `blocking` module to `actix-threadpool` crate - Moved `blocking` module to `actix-threadpool` crate
## 0.2.1 - 2019-03-11 ## 0.2.1 - 2019-03-11
* Added `blocking` module - Added `blocking` module
* Added `Arbiter::exec_fn` - execute fn on the arbiter's thread - Added `Arbiter::exec_fn` - execute fn on the arbiter's thread
* Added `Arbiter::exec` - execute fn on the arbiter's thread and wait result - Added `Arbiter::exec` - execute fn on the arbiter's thread and wait result
## 0.2.0 - 2019-03-06 ## 0.2.0 - 2019-03-06
* `run` method returns `io::Result<()>` - `run` method returns `io::Result<()>`
* Removed `Handle` - Removed `Handle`
## 0.1.0 - 2018-12-09 ## 0.1.0 - 2018-12-09
* Initial release - Initial release

View File

@@ -1,9 +1,10 @@
[package] [package]
name = "actix-rt" name = "actix-rt"
version = "2.3.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"]

View File

@@ -3,11 +3,11 @@
> Tokio-based single-threaded async runtime for the Actix ecosystem. > Tokio-based single-threaded async runtime for the Actix ecosystem.
[![crates.io](https://img.shields.io/crates/v/actix-rt?label=latest)](https://crates.io/crates/actix-rt) [![crates.io](https://img.shields.io/crates/v/actix-rt?label=latest)](https://crates.io/crates/actix-rt)
[![Documentation](https://docs.rs/actix-rt/badge.svg?version=2.3.0)](https://docs.rs/actix-rt/2.3.0) [![Documentation](https://docs.rs/actix-rt/badge.svg?version=2.5.0)](https://docs.rs/actix-rt/2.5.0)
[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) [![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-rt.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-rt.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-rt/2.3.0/status.svg)](https://deps.rs/crate/actix-rt/2.3.0) [![dependency status](https://deps.rs/crate/actix-rt/2.5.0/status.svg)](https://deps.rs/crate/actix-rt/2.5.0)
![Download](https://img.shields.io/crates/d/actix-rt.svg) ![Download](https://img.shields.io/crates/d/actix-rt.svg)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/WghFtEH6Hb) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/WghFtEH6Hb)

View File

@@ -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)]
#![warn(future_incompatible, missing_docs)]
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
#![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")]

View File

@@ -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")
} }
} }
@@ -175,38 +171,37 @@ impl System {
} }
} }
#[cfg(not(feature = "io-uring"))]
/// 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"))]
#[must_use = "A SystemRunner does nothing unless `run` 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.
@@ -216,8 +211,8 @@ impl SystemRunner {
} }
} }
#[cfg(feature = "io-uring")]
/// 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")]
#[must_use = "A SystemRunner does nothing unless `run` 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.

View File

@@ -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());

View File

@@ -1,25 +1,48 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
* Server can be started in regular Tokio runtime. [#408]
* Expose new `Server` type whose `Future` impl resolves when server stops. [#408]
* Rename `Server` to `ServerHandle`. [#407] ## 2.0.0-rc.1 - 2021-12-05
* Add `Server::handle` to obtain handle to server. [#408] - Hide implementation details of `Server`. [#424]
* Rename `ServerBuilder::{maxconn => max_concurrent_connections}`. [#407] - `Server` now runs only after awaiting it. [#425]
* Deprecate crate-level `new` shortcut for server builder. [#408]
* Minimum supported Rust version (MSRV) is now 1.52. [#424]: https://github.com/actix/actix-net/pull/424
[#425]: https://github.com/actix/actix-net/pull/425
## 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]
[#410]: https://github.com/actix/actix-net/pull/410
## 2.0.0-beta.7 - 2021-11-05 _(YANKED)_
- Server can be started in regular Tokio runtime. [#408]
- Expose new `Server` type whose `Future` impl resolves when server stops. [#408]
- Rename `Server` to `ServerHandle`. [#407]
- Add `Server::handle` to obtain handle to server. [#408]
- Rename `ServerBuilder::{maxconn => max_concurrent_connections}`. [#407]
- Deprecate crate-level `new` shortcut for server builder. [#408]
- Minimum supported Rust version (MSRV) is now 1.52.
[#407]: https://github.com/actix/actix-net/pull/407 [#407]: https://github.com/actix/actix-net/pull/407
[#408]: https://github.com/actix/actix-net/pull/408 [#408]: https://github.com/actix/actix-net/pull/408
## 2.0.0-beta.6 - 2021-10-11 ## 2.0.0-beta.6 - 2021-10-11
* Add experimental (semver-exempt) `io-uring` feature for enabling async file I/O on linux. [#374] - Add experimental (semver-exempt) `io-uring` feature for enabling async file I/O on linux. [#374]
* Server no long listens to `SIGHUP` signal. Previously, the received was not used but did block - Server no long listens to `SIGHUP` signal. Previously, the received was not used but did block
subsequent exit signals from working. [#389] subsequent exit signals from working. [#389]
* Remove `config` module. `ServiceConfig`, `ServiceRuntime` public types are removed due to - Remove `config` module. `ServiceConfig`, `ServiceRuntime` public types are removed due to
this change. [#349] this change. [#349]
* Remove `ServerBuilder::configure` [#349] - Remove `ServerBuilder::configure` [#349]
[#374]: https://github.com/actix/actix-net/pull/374 [#374]: https://github.com/actix/actix-net/pull/374
[#349]: https://github.com/actix/actix-net/pull/349 [#349]: https://github.com/actix/actix-net/pull/349
@@ -27,23 +50,23 @@
## 2.0.0-beta.5 - 2021-04-20 ## 2.0.0-beta.5 - 2021-04-20
* Server shutdown notifies all workers to exit regardless if shutdown is graceful. This causes all - Server shutdown notifies all workers to exit regardless if shutdown is graceful. This causes all
workers to shutdown immediately in force shutdown case. [#333] workers to shutdown immediately in force shutdown case. [#333]
[#333]: https://github.com/actix/actix-net/pull/333 [#333]: https://github.com/actix/actix-net/pull/333
## 2.0.0-beta.4 - 2021-04-01 ## 2.0.0-beta.4 - 2021-04-01
* Prevent panic when `shutdown_timeout` is very large. [f9262db] - Prevent panic when `shutdown_timeout` is very large. [f9262db]
[f9262db]: https://github.com/actix/actix-net/commit/f9262db [f9262db]: https://github.com/actix/actix-net/commit/f9262db
## 2.0.0-beta.3 - 2021-02-06 ## 2.0.0-beta.3 - 2021-02-06
* Hidden `ServerBuilder::start` method has been removed. Use `ServerBuilder::run`. [#246] - Hidden `ServerBuilder::start` method has been removed. Use `ServerBuilder::run`. [#246]
* Add retry for EINTR signal (`io::Interrupted`) in `Accept`'s poll loop. [#264] - Add retry for EINTR signal (`io::Interrupted`) in `Accept`'s poll loop. [#264]
* Add `ServerBuilder::worker_max_blocking_threads` to customize blocking thread pool size. [#265] - Add `ServerBuilder::worker_max_blocking_threads` to customize blocking thread pool size. [#265]
* Update `actix-rt` to `2.0.0`. [#273] - Update `actix-rt` to `2.0.0`. [#273]
[#246]: https://github.com/actix/actix-net/pull/246 [#246]: https://github.com/actix/actix-net/pull/246
[#264]: https://github.com/actix/actix-net/pull/264 [#264]: https://github.com/actix/actix-net/pull/264
@@ -52,21 +75,21 @@
## 2.0.0-beta.2 - 2021-01-03 ## 2.0.0-beta.2 - 2021-01-03
* Merge `actix-testing` to `actix-server` as `test_server` mod. [#242] - Merge `actix-testing` to `actix-server` as `test_server` mod. [#242]
[#242]: https://github.com/actix/actix-net/pull/242 [#242]: https://github.com/actix/actix-net/pull/242
## 2.0.0-beta.1 - 2020-12-28 ## 2.0.0-beta.1 - 2020-12-28
* Added explicit info log message on accept queue pause. [#215] - Added explicit info log message on accept queue pause. [#215]
* Prevent double registration of sockets when back-pressure is resolved. [#223] - Prevent double registration of sockets when back-pressure is resolved. [#223]
* Update `mio` dependency to `0.7.3`. [#239] - Update `mio` dependency to `0.7.3`. [#239]
* Remove `socket2` dependency. [#239] - Remove `socket2` dependency. [#239]
* `ServerBuilder::backlog` now accepts `u32` instead of `i32`. [#239] - `ServerBuilder::backlog` now accepts `u32` instead of `i32`. [#239]
* Remove `AcceptNotify` type and pass `WakerQueue` to `Worker` to wake up `Accept`'s `Poll`. [#239] - Remove `AcceptNotify` type and pass `WakerQueue` to `Worker` to wake up `Accept`'s `Poll`. [#239]
* Convert `mio::net::TcpStream` to `actix_rt::net::TcpStream`(`UnixStream` for uds) using - Convert `mio::net::TcpStream` to `actix_rt::net::TcpStream`(`UnixStream` for uds) using
`FromRawFd` and `IntoRawFd`(`FromRawSocket` and `IntoRawSocket` on windows). [#239] `FromRawFd` and `IntoRawFd`(`FromRawSocket` and `IntoRawSocket` on windows). [#239]
* Remove `AsyncRead` and `AsyncWrite` trait bound for `socket::FromStream` trait. [#239] - Remove `AsyncRead` and `AsyncWrite` trait bound for `socket::FromStream` trait. [#239]
[#215]: https://github.com/actix/actix-net/pull/215 [#215]: https://github.com/actix/actix-net/pull/215
[#223]: https://github.com/actix/actix-net/pull/223 [#223]: https://github.com/actix/actix-net/pull/223
@@ -74,125 +97,125 @@
## 1.0.4 - 2020-09-12 ## 1.0.4 - 2020-09-12
* Update actix-codec to 0.3.0. - Update actix-codec to 0.3.0.
* Workers must be greater than 0. [#167] - Workers must be greater than 0. [#167]
[#167]: https://github.com/actix/actix-net/pull/167 [#167]: https://github.com/actix/actix-net/pull/167
## 1.0.3 - 2020-05-19 ## 1.0.3 - 2020-05-19
* Replace deprecated `net2` crate with `socket2` [#140] - Replace deprecated `net2` crate with `socket2` [#140]
[#140]: https://github.com/actix/actix-net/pull/140 [#140]: https://github.com/actix/actix-net/pull/140
## 1.0.2 - 2020-02-26 ## 1.0.2 - 2020-02-26
* Avoid error by calling `reregister()` on Windows [#103] - Avoid error by calling `reregister()` on Windows [#103]
[#103]: https://github.com/actix/actix-net/pull/103 [#103]: https://github.com/actix/actix-net/pull/103
## 1.0.1 - 2019-12-29 ## 1.0.1 - 2019-12-29
* Rename `.start()` method to `.run()` - Rename `.start()` method to `.run()`
## 1.0.0 - 2019-12-11 ## 1.0.0 - 2019-12-11
* Use actix-net releases - Use actix-net releases
## 1.0.0-alpha.4 - 2019-12-08 ## 1.0.0-alpha.4 - 2019-12-08
* Use actix-service 1.0.0-alpha.4 - Use actix-service 1.0.0-alpha.4
## 1.0.0-alpha.3 - 2019-12-07 ## 1.0.0-alpha.3 - 2019-12-07
* Migrate to tokio 0.2 - Migrate to tokio 0.2
* Fix compilation on non-unix platforms - Fix compilation on non-unix platforms
* Better handling server configuration - Better handling server configuration
## 1.0.0-alpha.2 - 2019-12-02 ## 1.0.0-alpha.2 - 2019-12-02
* Simplify server service (remove actix-server-config) - Simplify server service (remove actix-server-config)
* Allow to wait on `Server` until server stops - Allow to wait on `Server` until server stops
## 0.8.0-alpha.1 - 2019-11-22 ## 0.8.0-alpha.1 - 2019-11-22
* Migrate to `std::future` - Migrate to `std::future`
## 0.7.0 - 2019-10-04 ## 0.7.0 - 2019-10-04
* Update `rustls` to 0.16 - Update `rustls` to 0.16
* Minimum required Rust version upped to 1.37.0 - Minimum required Rust version upped to 1.37.0
## 0.6.1 - 2019-09-25 ## 0.6.1 - 2019-09-25
* Add UDS listening support to `ServerBuilder` - Add UDS listening support to `ServerBuilder`
## 0.6.0 - 2019-07-18 ## 0.6.0 - 2019-07-18
* Support Unix domain sockets #3 - Support Unix domain sockets #3
## 0.5.1 - 2019-05-18 ## 0.5.1 - 2019-05-18
* ServerBuilder::shutdown_timeout() accepts u64 - ServerBuilder::shutdown_timeout() accepts u64
## 0.5.0 - 2019-05-12 ## 0.5.0 - 2019-05-12
* Add `Debug` impl for `SslError` - Add `Debug` impl for `SslError`
* Derive debug for `Server` and `ServerCommand` - Derive debug for `Server` and `ServerCommand`
* Upgrade to actix-service 0.4 - Upgrade to actix-service 0.4
## 0.4.3 - 2019-04-16 ## 0.4.3 - 2019-04-16
* Re-export `IoStream` trait - Re-export `IoStream` trait
* Depend on `ssl` and `rust-tls` features from actix-server-config - Depend on `ssl` and `rust-tls` features from actix-server-config
## 0.4.2 - 2019-03-30 ## 0.4.2 - 2019-03-30
* Fix SIGINT force shutdown - Fix SIGINT force shutdown
## 0.4.1 - 2019-03-14 ## 0.4.1 - 2019-03-14
* `SystemRuntime::on_start()` - allow to run future before server service initialization - `SystemRuntime::on_start()` - allow to run future before server service initialization
## 0.4.0 - 2019-03-12 ## 0.4.0 - 2019-03-12
* Use `ServerConfig` for service factory - Use `ServerConfig` for service factory
* Wrap tcp socket to `Io` type - Wrap tcp socket to `Io` type
* Upgrade actix-service - Upgrade actix-service
## 0.3.1 - 2019-03-04 ## 0.3.1 - 2019-03-04
* Add `ServerBuilder::maxconnrate` sets the maximum per-worker number of concurrent connections - Add `ServerBuilder::maxconnrate` sets the maximum per-worker number of concurrent connections
* Add helper ssl error `SslError` - Add helper ssl error `SslError`
* Rename `StreamServiceFactory` to `ServiceFactory` - Rename `StreamServiceFactory` to `ServiceFactory`
* Deprecate `StreamServiceFactory` - Deprecate `StreamServiceFactory`
## 0.3.0 - 2019-03-02 ## 0.3.0 - 2019-03-02
* Use new `NewService` trait - Use new `NewService` trait
## 0.2.1 - 2019-02-09 ## 0.2.1 - 2019-02-09
* Drop service response - Drop service response
## 0.2.0 - 2019-02-01 ## 0.2.0 - 2019-02-01
* Migrate to actix-service 0.2 - Migrate to actix-service 0.2
* Updated rustls dependency - Updated rustls dependency
## 0.1.3 - 2018-12-21 ## 0.1.3 - 2018-12-21
* Fix max concurrent connections handling - Fix max concurrent connections handling
## 0.1.2 - 2018-12-12 ## 0.1.2 - 2018-12-12
* rename ServiceConfig::rt() to ServiceConfig::apply() - rename ServiceConfig::rt() to ServiceConfig::apply()
* Fix back-pressure for concurrent ssl handshakes - Fix back-pressure for concurrent ssl handshakes
## 0.1.1 - 2018-12-11 ## 0.1.1 - 2018-12-11
* Fix signal handling on windows - Fix signal handling on windows
## 0.1.0 - 2018-12-09 ## 0.1.0 - 2018-12-09
* Move server to separate crate - Move server to separate crate

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-server" name = "actix-server"
version = "2.0.0-beta.6" version = "2.0.0-rc.1"
authors = [ authors = [
"Nikolay Kim <fafhrd91@gmail.com>", "Nikolay Kim <fafhrd91@gmail.com>",
"fakeshadow <24548779@qq.com>", "fakeshadow <24548779@qq.com>",
@@ -18,24 +18,29 @@ path = "src/lib.rs"
[features] [features]
default = [] default = []
io-uring = ["actix-rt/io-uring"] io-uring = ["tokio-uring", "actix-rt/io-uring"]
[dependencies] [dependencies]
actix-rt = { version = "2.0.0", default-features = false } actix-rt = { version = "2.4.0", default-features = false }
actix-service = "2.0.0" 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"
tokio = { version = "1.5.1", features = ["sync"] } tokio = { version = "1.5.1", features = ["sync"] }
# runtime for io-uring feature
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"
futures-util = { version = "0.3.7", default-features = false, features = ["sink"] } futures-util = { version = "0.3.7", default-features = false, features = ["sink", "async-await-macro"] }
tokio = { version = "1.5.1", features = ["io-util", "rt-multi-thread", "macros"] } tokio = { version = "1.5.1", features = ["io-util", "rt-multi-thread", "macros"] }

View File

@@ -33,9 +33,9 @@ async fn run() -> io::Result<()> {
let addr = ("127.0.0.1", 8080); let addr = ("127.0.0.1", 8080);
info!("starting server on port: {}", &addr.0); info!("starting server on port: {}", &addr.0);
// Bind socket address and start worker(s). By default, the server uses the number of available // Bind socket address and start worker(s). By default, the server uses the number of physical
// logical CPU cores as the worker count. For this reason, the closure passed to bind needs // CPU cores as the worker count. For this reason, the closure passed to bind needs to return
// to return a service *factory*; so it can be created once per worker. // a service *factory*; so it can be created once per worker.
Server::build() Server::build()
.bind("echo", addr, move || { .bind("echo", addr, move || {
let count = Arc::clone(&count); let count = Arc::clone(&count);
@@ -82,7 +82,7 @@ async fn run() -> io::Result<()> {
ok(size) ok(size)
}) })
})? })?
.workers(1) .workers(2)
.run() .run()
.await .await
} }

View File

@@ -127,7 +127,7 @@ impl Accept {
let mut events = mio::Events::with_capacity(256); let mut events = mio::Events::with_capacity(256);
loop { loop {
if let Err(e) = self.poll.poll(&mut events, None) { if let Err(e) = self.poll.poll(&mut events, self.timeout) {
match e.kind() { match e.kind() {
io::ErrorKind::Interrupted => {} io::ErrorKind::Interrupted => {}
_ => panic!("Poll error: {}", e), _ => panic!("Poll error: {}", e),

View File

@@ -8,7 +8,7 @@ use crate::{
server::ServerCommand, server::ServerCommand,
service::{InternalServiceFactory, ServiceFactory, StreamNewService}, service::{InternalServiceFactory, ServiceFactory, StreamNewService},
socket::{ socket::{
MioListener, MioTcpListener, MioTcpSocket, StdSocketAddr, StdTcpListener, ToSocketAddrs, create_mio_tcp_listener, MioListener, MioTcpListener, StdTcpListener, ToSocketAddrs,
}, },
worker::ServerWorkerConfig, worker::ServerWorkerConfig,
Server, Server,
@@ -40,7 +40,7 @@ impl ServerBuilder {
let (cmd_tx, cmd_rx) = unbounded_channel(); let (cmd_tx, cmd_rx) = unbounded_channel();
ServerBuilder { ServerBuilder {
threads: num_cpus::get(), threads: num_cpus::get_physical(),
token: 0, token: 0,
factories: Vec::new(), factories: Vec::new(),
sockets: Vec::new(), sockets: Vec::new(),
@@ -55,8 +55,11 @@ impl ServerBuilder {
/// Set number of workers to start. /// Set number of workers to start.
/// ///
/// By default server uses number of available logical CPU as workers count. Workers must be /// `num` must be greater than 0.
/// greater than 0. ///
/// The default worker count is the number of physical CPU cores available. If your benchmark
/// testing indicates that simultaneous multi-threading is beneficial to your app, you can use
/// the [`num_cpus`] crate to acquire the _logical_ core count instead.
pub fn workers(mut self, num: usize) -> Self { pub fn workers(mut self, num: usize) -> Self {
assert_ne!(num, 0, "workers must be greater than 0"); assert_ne!(num, 0, "workers must be greater than 0");
self.threads = num; self.threads = num;
@@ -112,7 +115,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
@@ -242,7 +245,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,
@@ -263,7 +267,7 @@ pub(super) fn bind_addr<S: ToSocketAddrs>(
let mut success = false; let mut success = false;
let mut sockets = Vec::new(); let mut sockets = Vec::new();
for addr in addr.to_socket_addrs()? { for addr in addr.to_socket_addrs()? {
match create_tcp_listener(addr, backlog) { match create_mio_tcp_listener(addr, backlog) {
Ok(lst) => { Ok(lst) => {
success = true; success = true;
sockets.push(lst); sockets.push(lst);
@@ -283,14 +287,3 @@ pub(super) fn bind_addr<S: ToSocketAddrs>(
)) ))
} }
} }
fn create_tcp_listener(addr: StdSocketAddr, backlog: u32) -> io::Result<MioTcpListener> {
let socket = match addr {
StdSocketAddr::V4(_) => MioTcpSocket::new_v4()?,
StdSocketAddr::V6(_) => MioTcpSocket::new_v6()?,
};
socket.set_reuseaddr(true)?;
socket.bind(addr)?;
socket.listen(backlog)
}

View File

@@ -42,10 +42,13 @@ 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),
force_system_stop: false,
}); });
async { async {
let _ = rx.await; let _ = rx.await;
} }

View File

@@ -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());
}
} }

View File

@@ -1,6 +1,7 @@
//! General purpose TCP server. //! General purpose TCP server.
#![deny(rust_2018_idioms, nonstandard_style)] #![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)]
#![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")]

View File

@@ -7,19 +7,17 @@ use std::{
}; };
use actix_rt::{time::sleep, System}; use actix_rt::{time::sleep, System};
use futures_core::future::BoxFuture; use futures_core::{future::BoxFuture, Stream};
use futures_util::stream::StreamExt as _;
use log::{error, info}; use log::{error, info};
use tokio::sync::{ use tokio::sync::{mpsc::UnboundedReceiver, oneshot};
mpsc::{UnboundedReceiver, UnboundedSender},
oneshot,
};
use crate::{ use crate::{
accept::Accept, accept::Accept,
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,22 +25,31 @@ 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,
/// Return channel to notify caller that shutdown is complete. /// Return channel to notify caller that shutdown is complete.
completion: Option<oneshot::Sender<()>>, completion: Option<oneshot::Sender<()>>,
/// Force System exit when true, overriding `ServerBuilder::system_exit()` if it is false.
force_system_stop: bool,
}, },
} }
@@ -54,8 +61,8 @@ pub(crate) enum ServerCommand {
/// Creates a worker per CPU core (or the number specified in [`ServerBuilder::workers`]) and /// Creates a worker per CPU core (or the number specified in [`ServerBuilder::workers`]) and
/// distributes connections with a round-robin strategy. /// distributes connections with a round-robin strategy.
/// ///
/// The [Server] must be awaited to process stop commands and listen for OS signals. It will resolve /// The [Server] must be awaited or polled in order to start running. It will resolve when the
/// when the server has fully shut down. /// server has fully shut down.
/// ///
/// # Shutdown Signals /// # Shutdown Signals
/// On UNIX systems, `SIGQUIT` will start a graceful shutdown and `SIGTERM` or `SIGINT` will start a /// On UNIX systems, `SIGQUIT` will start a graceful shutdown and `SIGTERM` or `SIGINT` will start a
@@ -113,10 +120,10 @@ pub(crate) enum ServerCommand {
/// .await /// .await
/// } /// }
/// ``` /// ```
#[must_use = "futures do nothing unless you `.await` or poll them"] #[must_use = "Server does nothing unless you `.await` or poll it"]
pub enum Server { pub struct Server {
Server(ServerInner), handle: ServerHandle,
Error(Option<io::Error>), fut: BoxFuture<'static, io::Result<()>>,
} }
impl Server { impl Server {
@@ -125,22 +132,68 @@ impl Server {
ServerBuilder::default() ServerBuilder::default()
} }
pub(crate) fn new(mut builder: ServerBuilder) -> Self { pub(crate) fn new(builder: ServerBuilder) -> Self {
Server {
handle: ServerHandle::new(builder.cmd_tx.clone()),
fut: Box::pin(ServerInner::run(builder)),
}
}
/// Get a `Server` handle that can be used issue commands and change it's state.
///
/// See [ServerHandle](ServerHandle) for usage.
pub fn handle(&self) -> ServerHandle {
self.handle.clone()
}
}
impl Future for Server {
type Output = io::Result<()>;
#[inline]
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut Pin::into_inner(self).fut).poll(cx)
}
}
pub struct ServerInner {
worker_handles: Vec<WorkerHandleServer>,
worker_config: ServerWorkerConfig,
services: Vec<Box<dyn InternalServiceFactory>>,
waker_queue: WakerQueue,
system_stop: bool,
stopping: bool,
}
impl ServerInner {
async fn run(builder: ServerBuilder) -> io::Result<()> {
let (mut this, mut mux) = Self::run_sync(builder)?;
while let Some(cmd) = mux.next().await {
this.handle_cmd(cmd).await;
if this.stopping {
break;
}
}
Ok(())
}
fn run_sync(mut builder: ServerBuilder) -> io::Result<(Self, ServerEventMultiplexer)> {
let sockets = mem::take(&mut builder.sockets) let sockets = mem::take(&mut builder.sockets)
.into_iter() .into_iter()
.map(|t| (t.0, t.2)) .map(|t| (t.0, t.2))
.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 {
@@ -152,142 +205,67 @@ impl Server {
); );
} }
match Accept::start(sockets, &builder) { let (waker_queue, worker_handles) = Accept::start(sockets, &builder)?;
Ok((waker_queue, worker_handles)) => {
// construct OS signals listener future
let signals = (builder.listen_os_signals).then(Signals::new);
Self::Server(ServerInner { let mux = ServerEventMultiplexer {
cmd_tx: builder.cmd_tx.clone(), signal_fut: (builder.listen_os_signals).then(Signals::new),
cmd_rx: builder.cmd_rx, cmd_rx: builder.cmd_rx,
signals, };
waker_queue,
worker_handles,
worker_config: builder.worker_config,
services: builder.factories,
exit: builder.exit,
stop_task: None,
})
}
Err(err) => Self::Error(Some(err)), let server = ServerInner {
} waker_queue,
worker_handles,
worker_config: builder.worker_config,
services: builder.factories,
system_stop: builder.exit,
stopping: false,
};
Ok((server, mux))
} }
/// Get a handle for ServerFuture that can be used to change state of actix server. async fn handle_cmd(&mut self, item: ServerCommand) {
///
/// See [ServerHandle](ServerHandle) for usage.
pub fn handle(&self) -> ServerHandle {
match self {
Server::Server(inner) => ServerHandle::new(inner.cmd_tx.clone()),
Server::Error(err) => {
// TODO: i don't think this is the best way to handle server startup fail
panic!(
"server handle can not be obtained because server failed to start up: {}",
err.as_ref().unwrap()
);
}
}
}
}
impl Future for Server {
type Output = io::Result<()>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.as_mut().get_mut() {
Server::Error(err) => Poll::Ready(Err(err
.take()
.expect("Server future cannot be polled after error"))),
Server::Server(inner) => {
// poll Signals
if let Some(ref mut signals) = inner.signals {
if let Poll::Ready(signal) = Pin::new(signals).poll(cx) {
inner.stop_task = inner.handle_signal(signal);
// drop signals listener
inner.signals = None;
}
}
// handle stop tasks and eager drain command channel
loop {
if let Some(ref mut fut) = inner.stop_task {
// only resolve stop task and exit
return fut.as_mut().poll(cx).map(|_| Ok(()));
}
match Pin::new(&mut inner.cmd_rx).poll_recv(cx) {
Poll::Ready(Some(cmd)) => {
// if stop task is required, set it and loop
inner.stop_task = inner.handle_cmd(cmd);
}
_ => return Poll::Pending,
}
}
}
}
}
}
pub struct ServerInner {
worker_handles: Vec<WorkerHandleServer>,
worker_config: ServerWorkerConfig,
services: Vec<Box<dyn InternalServiceFactory>>,
exit: bool,
cmd_tx: UnboundedSender<ServerCommand>,
cmd_rx: UnboundedReceiver<ServerCommand>,
signals: Option<Signals>,
waker_queue: WakerQueue,
stop_task: Option<BoxFuture<'static, ()>>,
}
impl ServerInner {
fn handle_cmd(&mut self, item: ServerCommand) -> Option<BoxFuture<'static, ()>> {
match item { match item {
ServerCommand::Pause(tx) => { ServerCommand::Pause(tx) => {
self.waker_queue.wake(WakerInterest::Pause); self.waker_queue.wake(WakerInterest::Pause);
let _ = tx.send(()); let _ = tx.send(());
None
} }
ServerCommand::Resume(tx) => { ServerCommand::Resume(tx) => {
self.waker_queue.wake(WakerInterest::Resume); self.waker_queue.wake(WakerInterest::Resume);
let _ = tx.send(()); let _ = tx.send(());
None
} }
ServerCommand::Stop { ServerCommand::Stop {
graceful, graceful,
completion, completion,
force_system_stop,
} => { } => {
let exit = self.exit; self.stopping = true;
// stop accept thread // stop accept thread
self.waker_queue.wake(WakerInterest::Stop); self.waker_queue.wake(WakerInterest::Stop);
// stop workers // send stop signal to workers
let workers_stop = self let workers_stop = self
.worker_handles .worker_handles
.iter() .iter()
.map(|worker| worker.stop(graceful)) .map(|worker| worker.stop(graceful))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
Some(Box::pin(async move { if graceful {
if graceful { // wait for all workers to shut down
// wait for all workers to shut down let _ = join_all(workers_stop).await;
let _ = join_all(workers_stop).await; }
}
if let Some(tx) = completion { if let Some(tx) = completion {
let _ = tx.send(()); let _ = tx.send(());
} }
if exit { if self.system_stop || force_system_stop {
sleep(Duration::from_millis(300)).await; sleep(Duration::from_millis(300)).await;
System::try_current().as_ref().map(System::stop); System::try_current().as_ref().map(System::stop);
} }
}))
} }
ServerCommand::WorkerFaulted(idx) => { ServerCommand::WorkerFaulted(idx) => {
@@ -320,40 +298,60 @@ impl ServerInner {
Err(err) => error!("can not restart worker {}: {}", idx, err), Err(err) => error!("can not restart worker {}: {}", idx, err),
}; };
None
} }
} }
} }
fn handle_signal(&mut self, signal: Signal) -> Option<BoxFuture<'static, ()>> { fn map_signal(signal: SignalKind) -> ServerCommand {
match signal { match signal {
Signal::Int => { SignalKind::Int => {
info!("SIGINT received; starting forced shutdown"); info!("SIGINT received; starting forced shutdown");
self.exit = true; ServerCommand::Stop {
self.handle_cmd(ServerCommand::Stop {
graceful: false, graceful: false,
completion: None, completion: None,
}) force_system_stop: true,
}
} }
Signal::Term => { SignalKind::Term => {
info!("SIGTERM received; starting graceful shutdown"); info!("SIGTERM received; starting graceful shutdown");
self.exit = true; ServerCommand::Stop {
self.handle_cmd(ServerCommand::Stop {
graceful: true, graceful: true,
completion: None, completion: None,
}) force_system_stop: true,
}
} }
Signal::Quit => { SignalKind::Quit => {
info!("SIGQUIT received; starting forced shutdown"); info!("SIGQUIT received; starting forced shutdown");
self.exit = true; ServerCommand::Stop {
self.handle_cmd(ServerCommand::Stop {
graceful: false, graceful: false,
completion: None, completion: None,
}) force_system_stop: true,
}
} }
} }
} }
} }
struct ServerEventMultiplexer {
cmd_rx: UnboundedReceiver<ServerCommand>,
signal_fut: Option<Signals>,
}
impl Stream for ServerEventMultiplexer {
type Item = ServerCommand;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = Pin::into_inner(self);
if let Some(signal_fut) = &mut this.signal_fut {
if let Poll::Ready(signal) = Pin::new(signal_fut).poll(cx) {
this.signal_fut = None;
return Poll::Ready(Some(ServerInner::map_signal(signal)));
}
}
Pin::new(&mut this.cmd_rx).poll_recv(cx)
}
}

View File

@@ -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",
}) })
} }
} }
@@ -35,10 +35,10 @@ impl fmt::Display for Signal {
/// Process signal listener. /// Process signal listener.
pub(crate) struct Signals { pub(crate) struct Signals {
#[cfg(not(unix))] #[cfg(not(unix))]
signals: futures_core::future::LocalBoxFuture<'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);

View File

@@ -2,7 +2,7 @@ pub(crate) use std::net::{
SocketAddr as StdSocketAddr, TcpListener as StdTcpListener, ToSocketAddrs, SocketAddr as StdSocketAddr, TcpListener as StdTcpListener, ToSocketAddrs,
}; };
pub(crate) use mio::net::{TcpListener as MioTcpListener, TcpSocket as MioTcpSocket}; pub(crate) use mio::net::TcpListener as MioTcpListener;
#[cfg(unix)] #[cfg(unix)]
pub(crate) use { pub(crate) use {
mio::net::UnixListener as MioUnixListener, mio::net::UnixListener as MioUnixListener,
@@ -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) })
} }
} }
@@ -223,6 +223,22 @@ mod unix_impl {
} }
} }
pub(crate) fn create_mio_tcp_listener(
addr: StdSocketAddr,
backlog: u32,
) -> io::Result<MioTcpListener> {
use socket2::{Domain, Protocol, Socket, Type};
let socket = Socket::new(Domain::for_address(addr), Type::STREAM, Some(Protocol::TCP))?;
socket.set_reuse_address(true)?;
socket.set_nonblocking(true)?;
socket.bind(&addr.into())?;
socket.listen(backlog as i32)?;
Ok(MioTcpListener::from_std(StdTcpListener::from(socket)))
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -234,11 +250,8 @@ mod tests {
assert_eq!(format!("{}", addr), "127.0.0.1:8080"); assert_eq!(format!("{}", addr), "127.0.0.1:8080");
let addr: StdSocketAddr = "127.0.0.1:0".parse().unwrap(); let addr: StdSocketAddr = "127.0.0.1:0".parse().unwrap();
let socket = MioTcpSocket::new_v4().unwrap(); let lst = create_mio_tcp_listener(addr, 128).unwrap();
socket.set_reuseaddr(true).unwrap(); let lst = MioListener::Tcp(lst);
socket.bind(addr).unwrap();
let tcp = socket.listen(128).unwrap();
let lst = MioListener::Tcp(tcp);
assert!(format!("{:?}", lst).contains("TcpListener")); assert!(format!("{:?}", lst).contains("TcpListener"));
assert!(format!("{}", lst).contains("127.0.0.1")); assert!(format!("{}", lst).contains("127.0.0.1"));
} }

View File

@@ -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());
}
}

View File

@@ -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 {
@@ -245,7 +250,7 @@ pub(crate) struct ServerWorkerConfig {
impl Default for ServerWorkerConfig { impl Default for ServerWorkerConfig {
fn default() -> Self { fn default() -> Self {
// 512 is the default max blocking thread count of tokio runtime. // 512 is the default max blocking thread count of tokio runtime.
let max_blocking_threads = std::cmp::max(512 / num_cpus::get(), 1); let max_blocking_threads = std::cmp::max(512 / num_cpus::get_physical(), 1);
Self { Self {
shutdown_timeout: Duration::from_secs(30), shutdown_timeout: Duration::from_secs(30),
max_blocking_threads, max_blocking_threads,
@@ -277,105 +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();
// every worker runs in it's own arbiter.
// use a custom tokio runtime builder to change the settings of runtime.
#[cfg(all(target_os = "linux", feature = "io-uring"))]
let arbiter = {
// TODO: pass max blocking thread config when tokio-uring enable configuration
// on building runtime.
let _ = config.max_blocking_threads;
Arbiter::new()
};
// 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);
std::thread::Builder::new() // outline of following code:
.name(format!("actix-server worker {}", idx)) //
.spawn(move || { // if system exists
// forward existing actix system context // if uring enabled
if let Some(sys) = sys { // start arbiter using uring method
System::set_current(sys); // else
} // start arbiter with regular tokio
// else
// if uring enabled
// start uring in spawned thread
// else
// start regular tokio in spawned thread
let rt = tokio::runtime::Builder::new_current_thread() // every worker runs in it's own thread and tokio runtime.
.enable_all() // use a custom tokio runtime builder to change the settings of runtime.
.max_blocking_threads(config.max_blocking_threads)
.build()
.unwrap();
rt.block_on(tokio::task::LocalSet::new().run_until(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 to make sure ServerWorker runs as non boxed future.
spawn(async move {
ServerWorker {
conn_rx,
stop_rx,
services: worker_services.into_boxed_slice(),
counter: WorkerCounter::new(idx, waker_queue, counter),
factories: factories.into_boxed_slice(),
state: WorkerState::default(),
shutdown_timeout: config.shutdown_timeout,
}
.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));
}
})
.expect("cannot spawn server worker thread");
}
// with actix system
(Some(_sys), _) => {
#[cfg(all(target_os = "linux", feature = "io-uring"))]
let arbiter = {
// TODO: pass max blocking thread config when tokio-uring enable configuration
// on building runtime.
let _ = config.max_blocking_threads;
Arbiter::new()
};
#[cfg(not(all(target_os = "linux", feature = "io-uring")))]
let arbiter = {
Arbiter::with_tokio_rt(move || {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.max_blocking_threads(config.max_blocking_threads)
.build()
.unwrap()
})
};
arbiter.spawn(async move {
// spawn_local to run !Send future tasks.
spawn(async move {
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 { spawn(ServerWorker {
rx, conn_rx,
rx2, stop_rx,
services, services: worker_services.into_boxed_slice(),
counter: WorkerCounter::new(idx, waker_queue, counter_clone), counter: WorkerCounter::new(idx, waker_queue, counter),
factories: factories.into_boxed_slice(), factories: factories.into_boxed_slice(),
state: Default::default(), state: Default::default(),
shutdown_timeout: config.shutdown_timeout, shutdown_timeout: config.shutdown_timeout,
}) });
.await });
.expect("task 3 panic"); });
}) }
.await };
.expect("task 2 panic");
}))
})
.expect("worker thread error/panic");
// 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) {
@@ -413,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;
} }
@@ -424,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;
} }
@@ -432,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));
} }
} }
} }
@@ -478,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);
} }
} }
@@ -490,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 {
@@ -552,6 +648,14 @@ impl Future for ServerWorker {
self.poll(cx) self.poll(cx)
} }
WorkerState::Shutdown(ref mut shutdown) => { WorkerState::Shutdown(ref mut shutdown) => {
// drop all pending connections in rx channel.
while let Poll::Ready(Some(conn)) = Pin::new(&mut this.conn_rx).poll_recv(cx) {
// WorkerCounterGuard is needed as Accept thread has incremented counter.
// It's guard's job to decrement the counter together with drop of Conn.
let guard = this.counter.guard();
drop((conn, guard));
}
// wait for 1 second // wait for 1 second
ready!(shutdown.timer.as_mut().poll(cx)); ready!(shutdown.timer.as_mut().poll(cx));
@@ -592,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));
@@ -603,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
})
}

View File

@@ -1,18 +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;
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 = mio::net::TcpSocket::new_v4().unwrap();
socket.bind(addr).unwrap();
socket.set_reuseaddr(true).unwrap();
let tcp = socket.listen(32).unwrap();
tcp.local_addr().unwrap()
} }
#[test] #[test]
@@ -30,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()
@@ -61,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();
} }
@@ -280,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)
@@ -308,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();
} }
@@ -385,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;
@@ -449,6 +483,50 @@ 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]
fn no_runtime_on_init() {
use std::{thread::sleep, time::Duration};
let addr = unused_addr();
let counter = Arc::new(AtomicUsize::new(0));
let mut srv = Server::build()
.workers(2)
.disable_signals()
.bind("test", addr, {
let counter = counter.clone();
move || {
counter.fetch_add(1, Ordering::SeqCst);
fn_service(|_| async { Ok::<_, ()>(()) })
}
})
.unwrap()
.run();
fn is_send<T: Send>(_: &T) {}
is_send(&srv);
is_send(&srv.handle());
sleep(Duration::from_millis(1_000));
assert_eq!(counter.load(Ordering::SeqCst), 0);
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
rt.block_on(async move {
let _ = futures_util::poll!(&mut srv);
// available after the first poll
sleep(Duration::from_millis(500));
assert_eq!(counter.load(Ordering::SeqCst), 2);
let _ = srv.handle().stop(true);
srv.await
})
.unwrap();
}

View File

@@ -3,52 +3,60 @@
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
## 2.0.2 - 2021-12-18
- Service types can now be `Send` and `'static` regardless of request, response, and config types, etc. [#397]
[#397]: https://github.com/actix/actix-net/pull/397
## 2.0.1 - 2021-10-11 ## 2.0.1 - 2021-10-11
* Documentation fix. - Documentation fix. [#388]
[#388]: https://github.com/actix/actix-net/pull/388
## 2.0.0 - 2021-04-16 ## 2.0.0 - 2021-04-16
* Removed pipeline and related structs/functions. [#335] - Removed pipeline and related structs/functions. [#335]
[#335]: https://github.com/actix/actix-net/pull/335 [#335]: https://github.com/actix/actix-net/pull/335
## 2.0.0-beta.5 - 2021-03-15 ## 2.0.0-beta.5 - 2021-03-15
* Add default `Service` trait impl for `Rc<S: Service>` and `&S: Service`. [#288] - Add default `Service` trait impl for `Rc<S: Service>` and `&S: Service`. [#288]
* Add `boxed::rc_service` function for constructing `boxed::RcService` type [#290] - Add `boxed::rc_service` function for constructing `boxed::RcService` type [#290]
[#288]: https://github.com/actix/actix-net/pull/288 [#288]: https://github.com/actix/actix-net/pull/288
[#290]: https://github.com/actix/actix-net/pull/290 [#290]: https://github.com/actix/actix-net/pull/290
## 2.0.0-beta.4 - 2021-02-04 ## 2.0.0-beta.4 - 2021-02-04
* `Service::poll_ready` and `Service::call` receive `&self`. [#247] - `Service::poll_ready` and `Service::call` receive `&self`. [#247]
* `apply_fn` and `apply_fn_factory` now receive `Fn(Req, &Service)` function type. [#247] - `apply_fn` and `apply_fn_factory` now receive `Fn(Req, &Service)` function type. [#247]
* `apply_cfg` and `apply_cfg_factory` now receive `Fn(Req, &Service)` function type. [#247] - `apply_cfg` and `apply_cfg_factory` now receive `Fn(Req, &Service)` function type. [#247]
* `fn_service` and friends now receive `Fn(Req)` function type. [#247] - `fn_service` and friends now receive `Fn(Req)` function type. [#247]
[#247]: https://github.com/actix/actix-net/pull/247 [#247]: https://github.com/actix/actix-net/pull/247
## 2.0.0-beta.3 - 2021-01-09 ## 2.0.0-beta.3 - 2021-01-09
* The `forward_ready!` macro converts errors. [#246] - The `forward_ready!` macro converts errors. [#246]
[#246]: https://github.com/actix/actix-net/pull/246 [#246]: https://github.com/actix/actix-net/pull/246
## 2.0.0-beta.2 - 2021-01-03 ## 2.0.0-beta.2 - 2021-01-03
* Remove redundant type parameter from `map_config`. - Remove redundant type parameter from `map_config`.
## 2.0.0-beta.1 - 2020-12-28 ## 2.0.0-beta.1 - 2020-12-28
* `Service`, other traits, and many type signatures now take the the request type as a type - `Service`, other traits, and many type signatures now take the the request type as a type
parameter instead of an associated type. [#232] parameter instead of an associated type. [#232]
* Add `always_ready!` and `forward_ready!` macros. [#233] - Add `always_ready!` and `forward_ready!` macros. [#233]
* Crate is now `no_std`. [#233] - Crate is now `no_std`. [#233]
* Migrate pin projections to `pin-project-lite`. [#233] - Migrate pin projections to `pin-project-lite`. [#233]
* Remove `AndThenApplyFn` and Pipeline `and_then_apply_fn`. Use the - Remove `AndThenApplyFn` and Pipeline `and_then_apply_fn`. Use the
`.and_then(apply_fn(...))` construction. [#233] `.and_then(apply_fn(...))` construction. [#233]
* Move non-vital methods to `ServiceExt` and `ServiceFactoryExt` extension traits. [#235] - Move non-vital methods to `ServiceExt` and `ServiceFactoryExt` extension traits. [#235]
[#232]: https://github.com/actix/actix-net/pull/232 [#232]: https://github.com/actix/actix-net/pull/232
[#233]: https://github.com/actix/actix-net/pull/233 [#233]: https://github.com/actix/actix-net/pull/233
@@ -56,221 +64,124 @@
## 1.0.6 - 2020-08-09 ## 1.0.6 - 2020-08-09
- Removed unsound custom Cell implementation that allowed obtaining several mutable references to
### Fixed
* 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 ## 1.0.4 - 2020-01-15
- Revert 1.0.3 change
### Fixed
* Revert 1.0.3 change ## 1.0.3 - 2020-01-15
- Fixed unsoundness in `AndThenService` impl.
## [1.0.3] - 2020-01-15
### Fixed ## 1.0.2 - 2020-01-08
- Add `into_service` helper function.
* Fixed unsoundness in `AndThenService` impl
## [1.0.2] - 2020-01-08 ## 1.0.1 - 2019-12-22
- `map_config()` and `unit_config()` now accept `IntoServiceFactory` type.
### Added
* Add `into_service` helper function ## 1.0.0 - 2019-12-11
- Add Clone impl for Apply service
## [1.0.1] - 2019-12-22 ## 1.0.0-alpha.4 - 2019-12-08
- Renamed `service_fn` to `fn_service`
- Renamed `factory_fn` to `fn_factory`
- Renamed `factory_fn_cfg` to `fn_factory_with_config`
### Changed
* `map_config()` and `unit_config()` accepts `IntoServiceFactory` type ## 1.0.0-alpha.3 - 2019-12-06
- Add missing Clone impls
- Restore `Transform::map_init_err()` combinator
- Restore `Service/Factory::apply_fn()` in form of `Pipeline/Factory::and_then_apply_fn()`
- Optimize service combinators and futures memory layout
## [1.0.0] - 2019-12-11 ## 1.0.0-alpha.2 - 2019-12-02
- Use owned config value for service factory
- Renamed BoxedNewService/BoxedService to BoxServiceFactory/BoxService
### Added
* Add Clone impl for Apply service ## 1.0.0-alpha.1 - 2019-11-25
- Migrated to `std::future`
- `NewService` renamed to `ServiceFactory`
- Added `pipeline` and `pipeline_factory` function
## [1.0.0-alpha.4] - 2019-12-08 ## 0.4.2 - 2019-08-27
- Check service readiness for `new_apply_cfg` combinator
### Changed
* Renamed `service_fn` to `fn_service` ## 0.4.1 - 2019-06-06
- Add `new_apply_cfg` function
* Renamed `factory_fn` to `fn_factory`
* Renamed `factory_fn_cfg` to `fn_factory_with_config` ## 0.4.0 - 2019-05-12
- Add `NewService::map_config` and `NewService::unit_config` combinators.
- Use associated type for `NewService` config.
- Change `apply_cfg` function.
- Renamed helper functions.
## [1.0.0-alpha.3] - 2019-12-06 ## 0.3.6 - 2019-04-07
- Poll boxed service call result immediately
### Changed
* Add missing Clone impls ## 0.3.5 - 2019-03-29
- Add `impl<S: Service> Service for Rc<RefCell<S>>`.
* Restore `Transform::map_init_err()` combinator
* Restore `Service/Factory::apply_fn()` in form of `Pipeline/Factory::and_then_apply_fn()` ## 0.3.4 - 2019-03-12
- Add `Transform::from_err()` combinator
- Add `apply_fn` helper
- Add `apply_fn_factory` helper
- Add `apply_transform` helper
- Add `apply_cfg` helper
* Optimize service combinators and futures memory layout
## 0.3.3 - 2019-03-09
- Add `ApplyTransform` new service for transform and new service.
- Add `NewService::apply_cfg()` combinator, allows to use nested `NewService` with different config parameter.
- Revert IntoFuture change
## [1.0.0-alpha.2] - 2019-12-02
### Changed ## 0.3.2 - 2019-03-04
- Change `NewService::Future` and `Transform::Future` to the `IntoFuture` trait.
- Export `AndThenTransform` type
* Use owned config value for service factory
* Renamed BoxedNewService/BoxedService to BoxServiceFactory/BoxService ## 0.3.1 - 2019-03-04
- Simplify Transform trait
## [1.0.0-alpha.1] - 2019-11-25 ## 0.3.0 - 2019-03-02
- Added boxed NewService and Service.
- Added `Config` parameter to `NewService` trait.
- Added `Config` parameter to `NewTransform` trait.
### Changed
* Migraded to `std::future` ## 0.2.2 - 2019-02-19
- Added `NewService` impl for `Rc<S> where S: NewService`
- Added `NewService` impl for `Arc<S> where S: NewService`
* `NewService` renamed to `ServiceFactory`
* Added `pipeline` and `pipeline_factory` function ## 0.2.1 - 2019-02-03
- Generalize `.apply` combinator with Transform trait
## [0.4.2] - 2019-08-27 ## 0.2.0 - 2019-02-01
- Use associated type instead of generic for Service definition.
### Fixed
* Check service readiness for `new_apply_cfg` combinator
## [0.4.1] - 2019-06-06
### Added
* Add `new_apply_cfg` function
## [0.4.0] - 2019-05-12
### Changed
* Use associated type for `NewService` config
* Change `apply_cfg` function
* Renamed helper functions
### Added
* Add `NewService::map_config` and `NewService::unit_config` combinators
## [0.3.6] - 2019-04-07
### Changed
* Poll boxed service call result immediately
## [0.3.5] - 2019-03-29
### Added
* Add `impl<S: Service> Service for Rc<RefCell<S>>`
## [0.3.4] - 2019-03-12
### Added
* Add `Transform::from_err()` combinator
* Add `apply_fn` helper
* Add `apply_fn_factory` helper
* Add `apply_transform` helper
* Add `apply_cfg` helper
## [0.3.3] - 2019-03-09
### Added
* Add `ApplyTransform` new service for transform and new service.
* Add `NewService::apply_cfg()` combinator, allows to use
nested `NewService` with different config parameter.
### Changed
* Revert IntoFuture change
## [0.3.2] - 2019-03-04
### Changed
* Change `NewService::Future` and `Transform::Future` to the `IntoFuture` trait.
* Export `AndThenTransform` type
## [0.3.1] - 2019-03-04
### Changed
* Simplify Transform trait
## [0.3.0] - 2019-03-02
## Added
* Added boxed NewService and Service.
## Changed
* Added `Config` parameter to `NewService` trait.
* Added `Config` parameter to `NewTransform` trait.
## [0.2.2] - 2019-02-19
### Added
* Added `NewService` impl for `Rc<S> where S: NewService`
* Added `NewService` impl for `Arc<S> where S: NewService`
## [0.2.1] - 2019-02-03
### Changed
* Generalize `.apply` combinator with Transform trait
## [0.2.0] - 2019-02-01
### Changed
* 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 +189,6 @@
} }
``` ```
* After: * After:
```rust ```rust
impl Service for Client { impl Service for Client {
type Request = Request; type Request = Request;
@@ -288,50 +198,30 @@
``` ```
## [0.1.6] - 2019-01-24 ## 0.1.6 - 2019-01-24
- Use `FnMut` instead of `Fn` for .apply() and .map() combinators and `FnService` type
### Changed - Change `.apply()` error semantic, new service's error is `From<Self::Error>`
* 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>`
## [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
- Use `FnMut` instead of `Fn` for `FnService`
### Changed
* Use `FnMut` instead of `Fn` for `FnService`
## [0.1.3] - 2018-12-12 ## 0.1.3 - 2018-12-12
- Split service combinators to separate trait
### Changed
* Split service combinators to separate trait
## [0.1.2] - 2018-12-12 ## 0.1.2 - 2018-12-12
- Release future early for `.and_then()` and `.then()` combinators
### Fixed
* 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

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-service" name = "actix-service"
version = "2.0.1" version = "2.0.2"
authors = [ authors = [
"Nikolay Kim <fafhrd91@gmail.com>", "Nikolay Kim <fafhrd91@gmail.com>",
"Rob Ede <robjtede@icloud.com>", "Rob Ede <robjtede@icloud.com>",

View File

@@ -3,10 +3,10 @@
> Service trait and combinators for representing asynchronous request/response operations. > Service trait and combinators for representing asynchronous request/response operations.
[![crates.io](https://img.shields.io/crates/v/actix-service?label=latest)](https://crates.io/crates/actix-service) [![crates.io](https://img.shields.io/crates/v/actix-service?label=latest)](https://crates.io/crates/actix-service)
[![Documentation](https://docs.rs/actix-service/badge.svg?version=2.0.1)](https://docs.rs/actix-service/2.0.1) [![Documentation](https://docs.rs/actix-service/badge.svg?version=2.0.2)](https://docs.rs/actix-service/2.0.2)
[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) [![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
![License](https://img.shields.io/crates/l/actix-service.svg) ![License](https://img.shields.io/crates/l/actix-service.svg)
[![Dependency Status](https://deps.rs/crate/actix-service/2.0.1/status.svg)](https://deps.rs/crate/actix-service/2.0.1) [![Dependency Status](https://deps.rs/crate/actix-service/2.0.2/status.svg)](https://deps.rs/crate/actix-service/2.0.2)
![Download](https://img.shields.io/crates/d/actix-service.svg) ![Download](https://img.shields.io/crates/d/actix-service.svg)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)

View File

@@ -51,7 +51,7 @@ where
{ {
service: S, service: S,
wrap_fn: F, wrap_fn: F,
_phantom: PhantomData<(Req, In, Res, Err)>, _phantom: PhantomData<fn(Req) -> (In, Res, Err)>,
} }
impl<S, F, Fut, Req, In, Res, Err> Apply<S, F, Req, In, Res, Err> impl<S, F, Fut, Req, In, Res, Err> Apply<S, F, Req, In, Res, Err>
@@ -106,7 +106,7 @@ where
pub struct ApplyFactory<SF, F, Req, In, Res, Err> { pub struct ApplyFactory<SF, F, Req, In, Res, Err> {
factory: SF, factory: SF,
wrap_fn: F, wrap_fn: F,
_phantom: PhantomData<(Req, In, Res, Err)>, _phantom: PhantomData<fn(Req) -> (In, Res, Err)>,
} }
impl<SF, F, Fut, Req, In, Res, Err> ApplyFactory<SF, F, Req, In, Res, Err> impl<SF, F, Fut, Req, In, Res, Err> ApplyFactory<SF, F, Req, In, Res, Err>
@@ -171,7 +171,7 @@ pin_project! {
#[pin] #[pin]
fut: SF::Future, fut: SF::Future,
wrap_fn: Option<F>, wrap_fn: Option<F>,
_phantom: PhantomData<(Req, Res)>, _phantom: PhantomData<fn(Req) -> Res>,
} }
} }

View File

@@ -105,7 +105,7 @@ where
Fut: Future<Output = Result<Res, Err>>, Fut: Future<Output = Result<Res, Err>>,
{ {
f: F, f: F,
_t: PhantomData<Req>, _t: PhantomData<fn(Req)>,
} }
impl<F, Fut, Req, Res, Err> FnService<F, Fut, Req, Res, Err> impl<F, Fut, Req, Res, Err> FnService<F, Fut, Req, Res, Err>
@@ -160,7 +160,7 @@ where
Fut: Future<Output = Result<Res, Err>>, Fut: Future<Output = Result<Res, Err>>,
{ {
f: F, f: F,
_t: PhantomData<(Req, Cfg)>, _t: PhantomData<fn(Req, Cfg)>,
} }
impl<F, Fut, Req, Res, Err, Cfg> FnServiceFactory<F, Fut, Req, Res, Err, Cfg> impl<F, Fut, Req, Res, Err, Cfg> FnServiceFactory<F, Fut, Req, Res, Err, Cfg>
@@ -237,7 +237,7 @@ where
Srv: Service<Req>, Srv: Service<Req>,
{ {
f: F, f: F,
_t: PhantomData<(Fut, Cfg, Req, Srv, Err)>, _t: PhantomData<fn(Cfg, Req) -> (Fut, Srv, Err)>,
} }
impl<F, Fut, Cfg, Srv, Req, Err> FnServiceConfig<F, Fut, Cfg, Srv, Req, Err> impl<F, Fut, Cfg, Srv, Req, Err> FnServiceConfig<F, Fut, Cfg, Srv, Req, Err>
@@ -293,7 +293,7 @@ where
Fut: Future<Output = Result<Srv, Err>>, Fut: Future<Output = Result<Srv, Err>>,
{ {
f: F, f: F,
_t: PhantomData<(Cfg, Req)>, _t: PhantomData<fn(Cfg, Req)>,
} }
impl<F, Cfg, Srv, Req, Fut, Err> FnServiceNoConfig<F, Cfg, Srv, Req, Fut, Err> impl<F, Cfg, Srv, Req, Fut, Err> FnServiceNoConfig<F, Cfg, Srv, Req, Fut, Err>
@@ -391,4 +391,40 @@ mod tests {
assert!(res.is_ok()); assert!(res.is_ok());
assert_eq!(res.unwrap(), ("srv", 1)); assert_eq!(res.unwrap(), ("srv", 1));
} }
#[actix_rt::test]
async fn test_auto_impl_send() {
use crate::{map_config, ServiceExt, ServiceFactoryExt};
use alloc::rc::Rc;
let srv_1 = fn_service(|_: Rc<u8>| ok::<_, Rc<u8>>(Rc::new(0u8)));
let fac_1 = fn_factory_with_config(|_: Rc<u8>| {
ok::<_, Rc<u8>>(fn_service(|_: Rc<u8>| ok::<_, Rc<u8>>(Rc::new(0u8))))
});
let fac_2 = fn_factory(|| {
ok::<_, Rc<u8>>(fn_service(|_: Rc<u8>| ok::<_, Rc<u8>>(Rc::new(0u8))))
});
fn is_send<T: Send + Sync + Clone>(_: &T) {}
is_send(&fac_1);
is_send(&map_config(fac_1.clone(), |_: Rc<u8>| Rc::new(0u8)));
is_send(&fac_1.clone().map_err(|_| Rc::new(0u8)));
is_send(&fac_1.clone().map(|_| Rc::new(0u8)));
is_send(&fac_1.clone().map_init_err(|_| Rc::new(0u8)));
// `and_then` is always !Send
// is_send(&fac_1.clone().and_then(fac_1.clone()));
is_send(&fac_1.new_service(Rc::new(0u8)).await.unwrap());
is_send(&fac_2);
is_send(&fac_2.new_service(Rc::new(0u8)).await.unwrap());
is_send(&srv_1);
is_send(&ServiceExt::map(srv_1.clone(), |_| Rc::new(0u8)));
is_send(&ServiceExt::map_err(srv_1.clone(), |_| Rc::new(0u8)));
// `and_then` is always !Send
// is_send(&ServiceExt::and_then(srv_1.clone(), srv_1.clone()));
}
} }

View File

@@ -1,8 +1,8 @@
//! See [`Service`] docs for information on this crate's foundational trait. //! See [`Service`] docs for information on this crate's foundational trait.
#![no_std] #![no_std]
#![deny(rust_2018_idioms, nonstandard_style, future_incompatible)] #![deny(rust_2018_idioms, nonstandard_style)]
#![warn(missing_docs)] #![warn(future_incompatible, missing_docs)]
#![allow(clippy::type_complexity)] #![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")]

View File

@@ -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

View File

@@ -15,7 +15,7 @@ use super::{Service, ServiceFactory};
pub struct Map<A, F, Req, Res> { pub struct Map<A, F, Req, Res> {
service: A, service: A,
f: F, f: F,
_t: PhantomData<(Req, Res)>, _t: PhantomData<fn(Req) -> Res>,
} }
impl<A, F, Req, Res> Map<A, F, Req, Res> { impl<A, F, Req, Res> Map<A, F, Req, Res> {
@@ -107,7 +107,7 @@ where
pub struct MapServiceFactory<A, F, Req, Res> { pub struct MapServiceFactory<A, F, Req, Res> {
a: A, a: A,
f: F, f: F,
r: PhantomData<(Res, Req)>, r: PhantomData<fn(Req) -> Res>,
} }
impl<A, F, Req, Res> MapServiceFactory<A, F, Req, Res> { impl<A, F, Req, Res> MapServiceFactory<A, F, Req, Res> {

View File

@@ -28,7 +28,7 @@ where
pub struct MapConfig<SF, Req, F, Cfg> { pub struct MapConfig<SF, Req, F, Cfg> {
factory: SF, factory: SF,
cfg_mapper: F, cfg_mapper: F,
e: PhantomData<(Cfg, Req)>, e: PhantomData<fn(Cfg, Req)>,
} }
impl<SF, Req, F, Cfg> MapConfig<SF, Req, F, Cfg> { impl<SF, Req, F, Cfg> MapConfig<SF, Req, F, Cfg> {
@@ -82,7 +82,7 @@ where
/// `unit_config()` config combinator /// `unit_config()` config combinator
pub struct UnitConfig<SF, Cfg, Req> { pub struct UnitConfig<SF, Cfg, Req> {
factory: SF, factory: SF,
_phantom: PhantomData<(Cfg, Req)>, _phantom: PhantomData<fn(Cfg, Req)>,
} }
impl<SF, Cfg, Req> UnitConfig<SF, Cfg, Req> impl<SF, Cfg, Req> UnitConfig<SF, Cfg, Req>

View File

@@ -15,7 +15,7 @@ use super::{Service, ServiceFactory};
pub struct MapErr<S, Req, F, E> { pub struct MapErr<S, Req, F, E> {
service: S, service: S,
mapper: F, mapper: F,
_t: PhantomData<(E, Req)>, _t: PhantomData<fn(Req) -> E>,
} }
impl<S, Req, F, E> MapErr<S, Req, F, E> { impl<S, Req, F, E> MapErr<S, Req, F, E> {
@@ -111,7 +111,7 @@ where
{ {
a: SF, a: SF,
f: F, f: F,
e: PhantomData<(E, Req)>, e: PhantomData<fn(Req) -> E>,
} }
impl<SF, Req, F, E> MapErrServiceFactory<SF, Req, F, E> impl<SF, Req, F, E> MapErrServiceFactory<SF, Req, F, E>

View File

@@ -13,7 +13,7 @@ use super::ServiceFactory;
pub struct MapInitErr<A, F, Req, Err> { pub struct MapInitErr<A, F, Req, Err> {
a: A, a: A,
f: F, f: F,
e: PhantomData<(Req, Err)>, e: PhantomData<fn(Req) -> Err>,
} }
impl<A, F, Req, Err> MapInitErr<A, F, Req, Err> impl<A, F, Req, Err> MapInitErr<A, F, Req, Err>

View File

@@ -40,7 +40,7 @@ where
/// Pipeline service - pipeline allows to compose multiple service into one service. /// Pipeline service - pipeline allows to compose multiple service into one service.
pub(crate) struct Pipeline<S, Req> { pub(crate) struct Pipeline<S, Req> {
service: S, service: S,
_phantom: PhantomData<Req>, _phantom: PhantomData<fn(Req)>,
} }
impl<S, Req> Pipeline<S, Req> impl<S, Req> Pipeline<S, Req>
@@ -162,7 +162,7 @@ impl<S: Service<Req>, Req> Service<Req> for Pipeline<S, Req> {
/// Pipeline factory /// Pipeline factory
pub(crate) struct PipelineFactory<SF, Req> { pub(crate) struct PipelineFactory<SF, Req> {
factory: SF, factory: SF,
_phantom: PhantomData<Req>, _phantom: PhantomData<fn(Req)>,
} }
impl<SF, Req> PipelineFactory<SF, Req> impl<SF, Req> PipelineFactory<SF, Req>

View File

@@ -14,7 +14,7 @@ use super::Transform;
pub struct TransformMapInitErr<T, S, Req, F, E> { pub struct TransformMapInitErr<T, S, Req, F, E> {
transform: T, transform: T,
mapper: F, mapper: F,
_phantom: PhantomData<(S, Req, E)>, _phantom: PhantomData<fn(Req) -> (S, E)>,
} }
impl<T, S, F, E, Req> TransformMapInitErr<T, S, Req, F, E> { impl<T, S, F, E, Req> TransformMapInitErr<T, S, Req, F, E> {

View File

@@ -3,30 +3,89 @@
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
## 3.0.0 - 2021-12-26
* No significant changes since `3.0.0-rc.2`.
## 3.0.0-rc.2 - 2021-12-10
- Re-export `openssl::SslConnectorBuilder` in `connect::openssl::reexports`. [#429]
[#429]: https://github.com/actix/actix-net/pull/429
## 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
## 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]
[#401]: https://github.com/actix/actix-net/pull/401 [#401]: https://github.com/actix/actix-net/pull/401
## 3.0.0-beta.6 - 2021-10-19 ## 3.0.0-beta.6 - 2021-10-19
* Update `tokio-rustls` to `0.23` which uses `rustls` `0.20`. [#396] - Update `tokio-rustls` to `0.23` which uses `rustls` `0.20`. [#396]
* Removed a re-export of `Session` from `rustls` as it no longer exist. [#396] - Removed a re-export of `Session` from `rustls` as it no longer exist. [#396]
* Minimum supported Rust version (MSRV) is now 1.52. - Minimum supported Rust version (MSRV) is now 1.52.
[#396]: https://github.com/actix/actix-net/pull/396 [#396]: https://github.com/actix/actix-net/pull/396
## 3.0.0-beta.5 - 2021-03-29 ## 3.0.0-beta.5 - 2021-03-29
* Changed `connect::ssl::rustls::RustlsConnectorService` to return error when `DNSNameRef` - Changed `connect::ssl::rustls::RustlsConnectorService` to return error when `DNSNameRef`
generation failed instead of panic. [#296] generation failed instead of panic. [#296]
* Remove `connect::ssl::openssl::OpensslConnectServiceFactory`. [#297] - Remove `connect::ssl::openssl::OpensslConnectServiceFactory`. [#297]
* 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
@@ -35,72 +94,72 @@
## 3.0.0-beta.4 - 2021-02-24 ## 3.0.0-beta.4 - 2021-02-24
* Rename `accept::openssl::{SslStream => TlsStream}`. - Rename `accept::openssl::{SslStream => TlsStream}`.
* Add `connect::Connect::set_local_addr` to attach local `IpAddr`. [#282] - Add `connect::Connect::set_local_addr` to attach local `IpAddr`. [#282]
* `connector::TcpConnector` service will try to bind to local_addr of `IpAddr` when given. [#282] - `connector::TcpConnector` service will try to bind to local_addr of `IpAddr` when given. [#282]
[#282]: https://github.com/actix/actix-net/pull/282 [#282]: https://github.com/actix/actix-net/pull/282
## 3.0.0-beta.3 - 2021-02-06 ## 3.0.0-beta.3 - 2021-02-06
* Remove `trust-dns-proto` and `trust-dns-resolver`. [#248] - Remove `trust-dns-proto` and `trust-dns-resolver`. [#248]
* Use `std::net::ToSocketAddrs` as simple and basic default resolver. [#248] - Use `std::net::ToSocketAddrs` as simple and basic default resolver. [#248]
* Add `Resolve` trait for custom DNS resolvers. [#248] - Add `Resolve` trait for custom DNS resolvers. [#248]
* Add `Resolver::new_custom` function to construct custom resolvers. [#248] - Add `Resolver::new_custom` function to construct custom resolvers. [#248]
* Export `webpki_roots::TLS_SERVER_ROOTS` in `actix_tls::connect` mod and remove - Export `webpki_roots::TLS_SERVER_ROOTS` in `actix_tls::connect` mod and remove
the export from `actix_tls::accept` [#248] the export from `actix_tls::accept` [#248]
* Remove `ConnectTakeAddrsIter`. `Connect::take_addrs` now returns `ConnectAddrsIter<'static>` - Remove `ConnectTakeAddrsIter`. `Connect::take_addrs` now returns `ConnectAddrsIter<'static>`
as owned iterator. [#248] as owned iterator. [#248]
* Rename `Address::{host => hostname}` to more accurately describe which URL segment is returned. - Rename `Address::{host => hostname}` to more accurately describe which URL segment is returned.
* Update `actix-rt` to `2.0.0`. [#273] - Update `actix-rt` to `2.0.0`. [#273]
[#248]: https://github.com/actix/actix-net/pull/248 [#248]: https://github.com/actix/actix-net/pull/248
[#273]: https://github.com/actix/actix-net/pull/273 [#273]: https://github.com/actix/actix-net/pull/273
## 3.0.0-beta.2 - 2021-xx-xx ## 3.0.0-beta.2 - 2021-xx-xx
* Depend on stable trust-dns packages. [#204] - Depend on stable trust-dns packages. [#204]
[#204]: https://github.com/actix/actix-net/pull/204 [#204]: https://github.com/actix/actix-net/pull/204
## 3.0.0-beta.1 - 2020-12-29 ## 3.0.0-beta.1 - 2020-12-29
* Move acceptors under `accept` module. [#238] - Move acceptors under `accept` module. [#238]
* Merge `actix-connect` crate under `connect` module. [#238] - Merge `actix-connect` crate under `connect` module. [#238]
* Add feature flags to enable acceptors and/or connectors individually. [#238] - Add feature flags to enable acceptors and/or connectors individually. [#238]
[#238]: https://github.com/actix/actix-net/pull/238 [#238]: https://github.com/actix/actix-net/pull/238
## 2.0.0 - 2020-09-03 ## 2.0.0 - 2020-09-03
* `nativetls::NativeTlsAcceptor` is renamed to `nativetls::Acceptor`. - `nativetls::NativeTlsAcceptor` is renamed to `nativetls::Acceptor`.
* Where possible, "SSL" terminology is replaced with "TLS". - Where possible, "SSL" terminology is replaced with "TLS".
* `SslError` is renamed to `TlsError`. * `SslError` is renamed to `TlsError`.
* `TlsError::Ssl` enum variant is renamed to `TlsError::Tls`. * `TlsError::Ssl` enum variant is renamed to `TlsError::Tls`.
* `max_concurrent_ssl_connect` is renamed to `max_concurrent_tls_connect`. * `max_concurrent_ssl_connect` is renamed to `max_concurrent_tls_connect`.
## 2.0.0-alpha.2 - 2020-08-17 ## 2.0.0-alpha.2 - 2020-08-17
* Update `rustls` dependency to 0.18 - Update `rustls` dependency to 0.18
* Update `tokio-rustls` dependency to 0.14 - Update `tokio-rustls` dependency to 0.14
* Update `webpki-roots` dependency to 0.20 - Update `webpki-roots` dependency to 0.20
## [2.0.0-alpha.1] - 2020-03-03 ## [2.0.0-alpha.1] - 2020-03-03
* Update `rustls` dependency to 0.17 - Update `rustls` dependency to 0.17
* Update `tokio-rustls` dependency to 0.13 - Update `tokio-rustls` dependency to 0.13
* Update `webpki-roots` dependency to 0.19 - Update `webpki-roots` dependency to 0.19
## [1.0.0] - 2019-12-11 ## [1.0.0] - 2019-12-11
* 1.0.0 release - 1.0.0 release
## [1.0.0-alpha.3] - 2019-12-07 ## [1.0.0-alpha.3] - 2019-12-07
* Migrate to tokio 0.2 - Migrate to tokio 0.2
* Enable rustls acceptor service - Enable rustls acceptor service
* Enable native-tls acceptor service - Enable native-tls acceptor service
## [1.0.0-alpha.1] - 2019-12-02 ## [1.0.0-alpha.1] - 2019-12-02
* Split openssl acceptor from actix-server package - Split openssl acceptor from actix-server package

View File

@@ -1,23 +1,27 @@
[package] [package]
name = "actix-tls" name = "actix-tls"
version = "3.0.0-beta.7" version = "3.0.0"
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"
categories = ["network-programming", "asynchronous"] categories = ["network-programming", "asynchronous", "cryptography"]
license = "MIT OR Apache-2.0" 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.6" actix-server = "2.0.0-rc.1"
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"]

View File

@@ -1,4 +1,4 @@
//! TLS Acceptor Server //! No-Op TLS Acceptor Server
//! //!
//! Using either HTTPie (`http`) or cURL: //! Using either HTTPie (`http`) or cURL:
//! //!

View File

@@ -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();
}
} }

View File

@@ -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),
}
}) })
} }
} }

View File

@@ -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)),
}
} }
} }

View File

@@ -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)),
}
} }
} }

View File

@@ -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))
)
}
}

View 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<'_> {}

View 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
View 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,
}
}

View File

@@ -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,
}
}
}

View 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);
}
}

View 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);
}
}

View File

@@ -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)
}

View 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))
})
})
}
}

View File

@@ -0,0 +1,150 @@
//! 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, SslConnectorBuilder, 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
View 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),
}
}
} }

View 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),
}
}
}

View 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))
}
}
}
}

View File

@@ -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)),
}
}
}
}

View 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,
}
}

View File

@@ -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;

View File

@@ -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))
})
})
}
}

View File

@@ -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))))
}
}
}
}

View File

@@ -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))
}
}
}
}

View File

@@ -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,
} }
} }

View File

@@ -1,14 +1,20 @@
//! 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(future_incompatible, 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")]
// enable unstable doc_cfg feature only on on docs.rs where nightly compiler is used
#![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;

View 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");
}

View 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
View 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();

View File

@@ -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());

View File

@@ -2,4 +2,4 @@
## [0.1.0] - 2020-01-15 ## [0.1.0] - 2020-01-15
* Initial release - Initial release

View File

@@ -1,6 +1,7 @@
//! Actix tracing - support for tokio tracing with Actix services. //! Actix tracing - support for tokio tracing with Actix services.
#![deny(rust_2018_idioms, nonstandard_style)] #![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)]
#![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")]

View File

@@ -4,179 +4,179 @@
## 3.0.0 - 2021-04-16 ## 3.0.0 - 2021-04-16
* No significant changes from `3.0.0-beta.4`. - No significant changes from `3.0.0-beta.4`.
## 3.0.0-beta.4 - 2021-04-01 ## 3.0.0-beta.4 - 2021-04-01
* Add `future::Either` type. [#305] - Add `future::Either` type. [#305]
[#305]: https://github.com/actix/actix-net/pull/305 [#305]: https://github.com/actix/actix-net/pull/305
## 3.0.0-beta.3 - 2021-04-01 ## 3.0.0-beta.3 - 2021-04-01
* Moved `mpsc` to own crate `local-channel`. [#301] - Moved `mpsc` to own crate `local-channel`. [#301]
* Moved `task::LocalWaker` to own crate `local-waker`. [#301] - Moved `task::LocalWaker` to own crate `local-waker`. [#301]
* Remove `timeout` module. [#301] - Remove `timeout` module. [#301]
* Remove `dispatcher` module. [#301] - Remove `dispatcher` module. [#301]
* Expose `future` mod with `ready` and `poll_fn` helpers. [#301] - Expose `future` mod with `ready` and `poll_fn` helpers. [#301]
[#301]: https://github.com/actix/actix-net/pull/301 [#301]: https://github.com/actix/actix-net/pull/301
## 3.0.0-beta.2 - 2021-02-06 ## 3.0.0-beta.2 - 2021-02-06
* Update `actix-rt` to `2.0.0`. [#273] - Update `actix-rt` to `2.0.0`. [#273]
[#273]: https://github.com/actix/actix-net/pull/273 [#273]: https://github.com/actix/actix-net/pull/273
## 3.0.0-beta.1 - 2020-12-28 ## 3.0.0-beta.1 - 2020-12-28
* Update `bytes` dependency to `1`. [#237] - Update `bytes` dependency to `1`. [#237]
* Use `pin-project-lite` to replace `pin-project`. [#229] - Use `pin-project-lite` to replace `pin-project`. [#229]
* Remove `condition`,`either`,`inflight`,`keepalive`,`oneshot`,`order`,`stream` and `time` mods. [#229] - Remove `condition`,`either`,`inflight`,`keepalive`,`oneshot`,`order`,`stream` and `time` mods. [#229]
[#229]: https://github.com/actix/actix-net/pull/229 [#229]: https://github.com/actix/actix-net/pull/229
[#237]: https://github.com/actix/actix-net/pull/237 [#237]: https://github.com/actix/actix-net/pull/237
## 2.0.0 - 2020-08-23 ## 2.0.0 - 2020-08-23
* No changes from beta 1. - No changes from beta 1.
## 2.0.0-beta.1 - 2020-08-19 ## 2.0.0-beta.1 - 2020-08-19
* Upgrade `tokio-util` to `0.3`. - Upgrade `tokio-util` to `0.3`.
* Remove unsound custom Cell and use `std::cell::RefCell` instead, as well as `actix-service`. - Remove unsound custom Cell and use `std::cell::RefCell` instead, as well as `actix-service`.
* Rename method to correctly spelled `LocalWaker::is_registered`. - Rename method to correctly spelled `LocalWaker::is_registered`.
## 1.0.6 - 2020-01-08 ## 1.0.6 - 2020-01-08
* Add `Clone` impl for `condition::Waiter`. - Add `Clone` impl for `condition::Waiter`.
## 1.0.5 - 2020-01-08 ## 1.0.5 - 2020-01-08
* Add `Condition` type. - Add `Condition` type.
* Add `Pool` of one-shot's. - Add `Pool` of one-shot's.
## 1.0.4 - 2019-12-20 ## 1.0.4 - 2019-12-20
* Add methods to check `LocalWaker` registration state. - Add methods to check `LocalWaker` registration state.
## 1.0.3 - 2019-12-11 ## 1.0.3 - 2019-12-11
* Revert InOrder service changes - Revert InOrder service changes
## 1.0.2 - 2019-12-11 ## 1.0.2 - 2019-12-11
* Allow to create `framed::Dispatcher` with custom `mpsc::Receiver`. - Allow to create `framed::Dispatcher` with custom `mpsc::Receiver`.
* Add `oneshot::Sender::is_canceled()` method. - Add `oneshot::Sender::is_canceled()` method.
## 1.0.1 - 2019-12-11 ## 1.0.1 - 2019-12-11
* Optimize InOrder service. - Optimize InOrder service.
## 1.0.0 - 2019-12-11 ## 1.0.0 - 2019-12-11
* Simplify oneshot and mpsc implementations. - Simplify oneshot and mpsc implementations.
## 1.0.0-alpha.3 - 2019-12-07 ## 1.0.0-alpha.3 - 2019-12-07
* Migrate to tokio 0.2. - Migrate to tokio 0.2.
* Fix oneshot. - Fix oneshot.
## 1.0.0-alpha.2 - 2019-12-02 ## 1.0.0-alpha.2 - 2019-12-02
* Migrate to `std::future`. - Migrate to `std::future`.
## 0.4.7 - 2019-10-14 ## 0.4.7 - 2019-10-14
* Re-register task on every framed transport poll. - Re-register task on every framed transport poll.
## 0.4.6 - 2019-10-08 ## 0.4.6 - 2019-10-08
* Refactor `Counter` type. register current task in available method. - Refactor `Counter` type. register current task in available method.
## 0.4.5 - 2019-07-19 ## 0.4.5 - 2019-07-19
* Deprecated `CloneableService` as it is not safe. - Deprecated `CloneableService` as it is not safe.
## 0.4.4 - 2019-07-17 ## 0.4.4 - 2019-07-17
* Undeprecate `FramedTransport` as it is actually useful. - Undeprecate `FramedTransport` as it is actually useful.
## 0.4.3 - 2019-07-17 ## 0.4.3 - 2019-07-17
* Deprecate `CloneableService` as it is not safe and in general not very useful. - Deprecate `CloneableService` as it is not safe and in general not very useful.
* Deprecate `FramedTransport` in favor of `actix-ioframe`. - Deprecate `FramedTransport` in favor of `actix-ioframe`.
## 0.4.2 - 2019-06-26 ## 0.4.2 - 2019-06-26
* Do not block on sink drop for FramedTransport. - Do not block on sink drop for FramedTransport.
## 0.4.1 - 2019-05-15 ## 0.4.1 - 2019-05-15
* Change `Either` constructor. - Change `Either` constructor.
## 0.4.0 - 2019-05-11 ## 0.4.0 - 2019-05-11
* Change `Either` to handle two nexted services. - Change `Either` to handle two nexted services.
* Upgrade actix-service 0.4. - Upgrade actix-service 0.4.
* Removed framed related services. - Removed framed related services.
* Removed stream related services. - Removed stream related services.
## 0.3.5 - 2019-04-04 ## 0.3.5 - 2019-04-04
* Allow to send messages to `FramedTransport` via mpsc channel. - Allow to send messages to `FramedTransport` via mpsc channel.
* Remove `'static` constraint from Clonable service. - Remove `'static` constraint from Clonable service.
## 0.3.4 - 2019-03-12 ## 0.3.4 - 2019-03-12
* `TimeoutService`, `InOrderService`, `InFlightService` accepts generic IntoService services. - `TimeoutService`, `InOrderService`, `InFlightService` accepts generic IntoService services.
* Fix `InFlightService::poll_ready()` nested service readiness check. - Fix `InFlightService::poll_ready()` nested service readiness check.
* Fix `InOrderService::poll_ready()` nested service readiness check. - Fix `InOrderService::poll_ready()` nested service readiness check.
## 0.3.3 - 2019-03-09 ## 0.3.3 - 2019-03-09
* Revert IntoFuture change. - Revert IntoFuture change.
* Add generic config param for IntoFramed and TakeOne new services. - Add generic config param for IntoFramed and TakeOne new services.
## 0.3.2 - 2019-03-04 ## 0.3.2 - 2019-03-04
* Use IntoFuture for new services. - Use IntoFuture for new services.
## 0.3.1 - 2019-03-04 ## 0.3.1 - 2019-03-04
* Use new type of transform trait. - Use new type of transform trait.
## 0.3.0 - 2019-03-02 ## 0.3.0 - 2019-03-02
* Use new `NewService` trait - Use new `NewService` trait
* BoxedNewService` and `BoxedService` types moved to actix-service crate. - BoxedNewService` and `BoxedService` types moved to actix-service crate.
## 0.2.4 - 2019-02-21 ## 0.2.4 - 2019-02-21
* Custom `BoxedNewService` implementation. - Custom `BoxedNewService` implementation.
## 0.2.3 - 2019-02-21 ## 0.2.3 - 2019-02-21
* Add `BoxedNewService` and `BoxedService`. - Add `BoxedNewService` and `BoxedService`.
## 0.2.2 - 2019-02-11 ## 0.2.2 - 2019-02-11
* Add `Display` impl for `TimeoutError`. - Add `Display` impl for `TimeoutError`.
* Add `Display` impl for `InOrderError`. - Add `Display` impl for `InOrderError`.
## 0.2.1 - 2019-02-06 ## 0.2.1 - 2019-02-06
* Add `InOrder` service. the service yields responses as they become available, - Add `InOrder` service. the service yields responses as they become available,
in the order that their originating requests were submitted to the service. in the order that their originating requests were submitted to the service.
* Convert `Timeout` and `InFlight` services to a transforms. - Convert `Timeout` and `InFlight` services to a transforms.
## 0.2.0 - 2019-02-01 ## 0.2.0 - 2019-02-01
* Fix framed transport error handling. - Fix framed transport error handling.
* Added Clone impl for Either service. - Added Clone impl for Either service.
* Added Clone impl for Timeout service factory. - Added Clone impl for Timeout service factory.
* Added Service and NewService for Stream dispatcher. - Added Service and NewService for Stream dispatcher.
* Switch to actix-service 0.2. - Switch to actix-service 0.2.
## 0.1.0 - 2018-12-09 ## 0.1.0 - 2018-12-09
* Move utils services to separate crate. - Move utils services to separate crate.

View File

@@ -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"

View File

@@ -22,16 +22,19 @@ impl Counter {
} }
/// Create new counter guard, incrementing the counter. /// Create new counter guard, incrementing the counter.
#[inline]
pub fn get(&self) -> CounterGuard { pub fn get(&self) -> CounterGuard {
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.
#[inline]
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)
} }
/// Get total number of acquired guards. /// Get total number of acquired guards.
#[inline]
pub fn total(&self) -> usize { pub fn total(&self) -> usize {
self.0.count.get() self.0.count.get()
} }

View File

@@ -40,11 +40,13 @@ pin_project! {
impl<L, R> Either<L, R> { impl<L, R> Either<L, R> {
/// Creates new `Either` using left variant. /// Creates new `Either` using left variant.
#[inline]
pub fn left(value: L) -> Either<L, R> { pub fn left(value: L) -> Either<L, R> {
Either::Left { value } Either::Left { value }
} }
/// Creates new `Either` using right variant. /// Creates new `Either` using right variant.
#[inline]
pub fn right(value: R) -> Either<L, R> { pub fn right(value: R) -> Either<L, R> {
Either::Right { value } Either::Right { value }
} }
@@ -52,6 +54,7 @@ impl<L, R> Either<L, R> {
impl<T> Either<T, T> { impl<T> Either<T, T> {
/// Unwraps into inner value when left and right have a common type. /// Unwraps into inner value when left and right have a common type.
#[inline]
pub fn into_inner(self) -> T { pub fn into_inner(self) -> T {
match self { match self {
Either::Left { value } => value, Either::Left { value } => value,
@@ -67,6 +70,7 @@ where
{ {
type Output = L::Output; type Output = L::Output;
#[inline]
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.project() { match self.project() {
EitherProj::Left { value } => value.poll(cx), EitherProj::Left { value } => value.poll(cx),

View File

@@ -8,6 +8,7 @@ use core::{
}; };
/// Creates a future driven by the provided function that receives a task context. /// Creates a future driven by the provided function that receives a task context.
#[inline]
pub fn poll_fn<F, T>(f: F) -> PollFn<F> pub fn poll_fn<F, T>(f: F) -> PollFn<F>
where where
F: FnMut(&mut Context<'_>) -> Poll<T>, F: FnMut(&mut Context<'_>) -> Poll<T>,
@@ -34,6 +35,7 @@ where
{ {
type Output = T; type Output = T;
#[inline]
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> {
(self.f)(cx) (self.f)(cx)
} }

View File

@@ -65,6 +65,7 @@ impl<T> Future for Ready<T> {
/// let a = ready(1); /// let a = ready(1);
/// assert_eq!(a.into_inner(), 1); /// assert_eq!(a.into_inner(), 1);
/// ``` /// ```
#[inline]
pub fn ready<T>(val: T) -> Ready<T> { pub fn ready<T>(val: T) -> Ready<T> {
Ready { val: Some(val) } Ready { val: Some(val) }
} }
@@ -80,6 +81,7 @@ pub fn ready<T>(val: T) -> Ready<T> {
/// assert_eq!(a.await, Ok(1)); /// assert_eq!(a.await, Ok(1));
/// # } /// # }
/// ``` /// ```
#[inline]
pub fn ok<T, E>(val: T) -> Ready<Result<T, E>> { pub fn ok<T, E>(val: T) -> Ready<Result<T, E>> {
Ready { val: Some(Ok(val)) } Ready { val: Some(Ok(val)) }
} }
@@ -95,6 +97,7 @@ pub fn ok<T, E>(val: T) -> Ready<Result<T, E>> {
/// assert_eq!(a.await, Err(1)); /// assert_eq!(a.await, Err(1));
/// # } /// # }
/// ``` /// ```
#[inline]
pub fn err<T, E>(err: E) -> Ready<Result<T, E>> { pub fn err<T, E>(err: E) -> Ready<Result<T, E>> {
Ready { Ready {
val: Some(Err(err)), val: Some(Err(err)),
@@ -103,10 +106,17 @@ 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_any};
use super::*; use super::*;
assert_impl_all!(Ready<()>: Send, Sync, Unpin, Clone);
assert_impl_all!(Ready<Rc<()>>: Unpin, Clone);
assert_not_impl_any!(Ready<Rc<()>>: Send, Sync);
#[test] #[test]
#[should_panic] #[should_panic]
fn multiple_poll_panics() { fn multiple_poll_panics() {

View File

@@ -1,7 +1,7 @@
//! Various utilities used in the Actix ecosystem. //! Various utilities used in the Actix ecosystem.
#![deny(rust_2018_idioms, nonstandard_style)] #![deny(rust_2018_idioms, nonstandard_style)]
#![warn(missing_docs)] #![warn(future_incompatible, 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")]

View File

@@ -4,33 +4,33 @@
## 1.0.0 - 2020-12-31 ## 1.0.0 - 2020-12-31
* Update `bytes` dependency to `1`. - Update `bytes` dependency to `1`.
* Add array and slice of `u8` impls of `TryFrom` up to 32 in length. - Add array and slice of `u8` impls of `TryFrom` up to 32 in length.
* Rename `get_ref` to `as_bytes` and rename `into_inner` to `into_bytes`. - Rename `get_ref` to `as_bytes` and rename `into_inner` to `into_bytes`.
* `ByteString::new` is now a `const fn`. - `ByteString::new` is now a `const fn`.
* Crate is now `#[no_std]` compatible. - Crate is now `#[no_std]` compatible.
## 0.1.5 - 2020-03-30 ## 0.1.5 - 2020-03-30
* Serde support - Serde support
## 0.1.4 - 2020-01-14 ## 0.1.4 - 2020-01-14
* Fix `AsRef<str>` impl - Fix `AsRef<str>` impl
## 0.1.3 - 2020-01-13 ## 0.1.3 - 2020-01-13
* Add `PartialEq<T: AsRef<str>>`, `AsRef<[u8]>` impls - Add `PartialEq<T: AsRef<str>>`, `AsRef<[u8]>` impls
## 0.1.2 - 2019-12-22 ## 0.1.2 - 2019-12-22
* Fix `new()` method - Fix `new()` method
* Make `ByteString::from_static()` and `ByteString::from_bytes_unchecked()` methods const. - Make `ByteString::from_static()` and `ByteString::from_bytes_unchecked()` methods const.
## 0.1.1 - 2019-12-07 ## 0.1.1 - 2019-12-07
* Fix hash impl - Fix hash impl
## 0.1.0 - 2019-12-07 ## 0.1.0 - 2019-12-07
* Initial release - Initial release

View File

@@ -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"

View File

@@ -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(future_incompatible, 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");

View File

@@ -3,5 +3,9 @@
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
## 0.1.2 - 2021-04-01
- No significant changes from `0.1.1`.
## 0.1.1 - 2021-03-29 ## 0.1.1 - 2021-03-29
* Move local mpsc channel to it's own crate. - Move local mpsc channel to it's own crate.

View File

@@ -1,3 +1,8 @@
//! Non-thread-safe channels. //! Non-thread-safe channels.
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible, missing_docs)]
extern crate alloc;
pub mod mpsc; pub mod mpsc;

View File

@@ -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;

View File

@@ -3,9 +3,9 @@
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
## 0.1.2 - 2021-04-01 ## 0.1.2 - 2021-12-18
* Fix crate metadata. - Fix crate metadata.
## 0.1.1 - 2021-03-29 ## 0.1.1 - 2021-03-29
* Move `LocalWaker` to it's own crate. - Move `LocalWaker` to it's own crate.

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "local-waker" name = "local-waker"
version = "0.1.1" version = "0.1.2"
description = "A synchronization primitive for thread-local task wakeup" description = "A synchronization primitive for thread-local task wakeup"
authors = [ authors = [
"Nikolay Kim <fafhrd91@gmail.com>", "Nikolay Kim <fafhrd91@gmail.com>",

View File

@@ -3,6 +3,8 @@
//! See docs for [`LocalWaker`]. //! See docs for [`LocalWaker`].
#![no_std] #![no_std]
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible, missing_docs)]
use core::{cell::Cell, fmt, marker::PhantomData, task::Waker}; use core::{cell::Cell, fmt, marker::PhantomData, task::Waker};

View File

@@ -1,5 +1,2 @@
max_width = 96 max_width = 96
reorder_imports = true reorder_imports = true
#wrap_comments = true
#fn_args_density = "Compressed"
#use_small_heuristics = false