1
0
mirror of https://github.com/fafhrd91/actix-net synced 2025-08-15 21:33:54 +02:00

Compare commits

..

17 Commits

Author SHA1 Message Date
Rob Ede
5285656bdc prepare next beta releases 2021-01-03 04:39:37 +00:00
Rob Ede
296294061f update readme 2020-12-31 02:52:55 +00:00
Rob Ede
93865de848 move router to actix-router 2020-12-31 02:29:27 +00:00
Rob Ede
6bcf6d8160 use bytestring crate name as dir name 2020-12-31 02:21:50 +00:00
Rob Ede
14ff379150 prepare bytestring release 1.0.0 (#243) 2020-12-31 02:20:49 +00:00
fakeshadow
647817ef14 tokio 1.0 and mio 0.7 (#204) 2020-12-30 22:11:50 +00:00
fakeshadow
b5eefb4d42 merge actix-testing into actix-server (#242) 2020-12-29 21:20:24 +00:00
fakeshadow
03eb96d6d4 fix actix-tls tests (#241) 2020-12-29 11:36:17 +00:00
Rob Ede
0934078947 prepare tls beta release 2020-12-29 01:04:21 +00:00
Rob Ede
5759c9e144 merge -connect and -tls and upgrade to rt v2 (#238) 2020-12-29 00:38:41 +00:00
Rob Ede
3c6de3a81b use correct service version for tracing 2020-12-29 00:08:59 +00:00
Rob Ede
ef83647ac9 prepare testing beta release 2020-12-28 23:54:21 +00:00
Rob Ede
98a17081b8 prepare server beta release 2020-12-28 23:50:00 +00:00
fakeshadow
b7202db8fd update actix-server and actix-testing to tokio 1.0 (#239) 2020-12-28 23:44:53 +00:00
Rob Ede
a09f9abfcb prepare utils release 3.0.0-beta.1 2020-12-28 03:32:28 +00:00
Rob Ede
e4a44b77e6 prepare codec release 0.4.0-beta.1 2020-12-28 03:24:43 +00:00
fakeshadow
2ee8f45f5d update actix-codec and actix-utils to tokio 1.0 (#237) 2020-12-28 03:16:37 +00:00
76 changed files with 1677 additions and 1839 deletions

View File

@@ -1,31 +1,27 @@
[workspace] [workspace]
members = [ members = [
"actix-codec", "actix-codec",
"actix-connect",
"actix-rt",
"actix-macros", "actix-macros",
"actix-service", "actix-router",
"actix-rt",
"actix-server", "actix-server",
"actix-testing", "actix-service",
"actix-threadpool", "actix-threadpool",
"actix-tls", "actix-tls",
"actix-tracing", "actix-tracing",
"actix-utils", "actix-utils",
"router", "bytestring",
"string",
] ]
[patch.crates-io] [patch.crates-io]
actix-codec = { path = "actix-codec" } actix-codec = { path = "actix-codec" }
actix-connect = { path = "actix-connect" }
actix-rt = { git = "https://github.com/actix/actix-net.git", rev = "ba44ea7d0bafaf5fccb9a34003d503e1910943ee" }
actix-macros = { path = "actix-macros" } actix-macros = { path = "actix-macros" }
actix-router = { path = "actix-router" }
actix-rt = { path = "actix-rt" }
actix-server = { path = "actix-server" } actix-server = { path = "actix-server" }
actix-service = { path = "actix-service" } actix-service = { path = "actix-service" }
actix-testing = { path = "actix-testing" }
actix-threadpool = { path = "actix-threadpool" } actix-threadpool = { path = "actix-threadpool" }
actix-tls = { path = "actix-tls" } actix-tls = { path = "actix-tls" }
actix-tracing = { path = "actix-tracing" } actix-tracing = { path = "actix-tracing" }
actix-utils = { path = "actix-utils" } actix-utils = { path = "actix-utils" }
actix-router = { path = "router" } bytestring = { path = "bytestring" }
bytestring = { path = "string" }

View File

@@ -1,9 +1,12 @@
# Actix net [![codecov](https://codecov.io/gh/actix/actix-net/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-net) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) # Actix Net
Actix net - framework for composable network services > A collection of lower-level libraries for composable network services.
![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-server)
[![codecov](https://codecov.io/gh/actix/actix-net/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-net)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
## Build statuses ## Build statuses
| Platform | Build Status | | Platform | Build Status |
| ---------------- | ------------ | | ---------------- | ------------ |
| Linux | [![build status](https://github.com/actix/actix-net/workflows/CI%20%28Linux%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-net/actions?query=workflow%3A"CI+(Linux)") | | Linux | [![build status](https://github.com/actix/actix-net/workflows/CI%20%28Linux%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-net/actions?query=workflow%3A"CI+(Linux)") |
@@ -11,59 +14,13 @@ Actix net - framework for composable network services
| Windows | [![build status](https://github.com/actix/actix-net/workflows/CI%20%28Windows%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-net/actions?query=workflow%3A"CI+(Windows)") | | Windows | [![build status](https://github.com/actix/actix-net/workflows/CI%20%28Windows%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-net/actions?query=workflow%3A"CI+(Windows)") |
| Windows (MinGW) | [![build status](https://github.com/actix/actix-net/workflows/CI%20%28Windows-mingw%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-net/actions?query=workflow%3A"CI+(Windows-mingw)") | | Windows (MinGW) | [![build status](https://github.com/actix/actix-net/workflows/CI%20%28Windows-mingw%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-net/actions?query=workflow%3A"CI+(Windows-mingw)") |
## Documentation & community resources
* [Chat on Gitter](https://gitter.im/actix/actix)
* Minimum supported Rust version: 1.46 or later
## Example ## Example
See `actix-server/examples` and `actix-tls/examples` for some basic examples.
```rust ### MSRV
fn main() -> io::Result<()> { This repo's Minimum Supported Rust Version (MSRV) is 1.46.0.
// load ssl keys
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
builder.set_private_key_file("./examples/key.pem", SslFiletype::PEM).unwrap();
builder.set_certificate_chain_file("./examples/cert.pem").unwrap();
let acceptor = builder.build();
let num = Arc::new(AtomicUsize::new(0));
// bind socket address and start workers. By default server uses number of
// available logical cpu as threads count. actix net start separate
// instances of service pipeline in each worker.
Server::build()
.bind(
// configure service pipeline
"basic", "0.0.0.0:8443",
move || {
let num = num.clone();
let acceptor = acceptor.clone();
// construct transformation pipeline
pipeline(
// service for converting incoming TcpStream to a SslStream<TcpStream>
fn_service(move |stream: actix_rt::net::TcpStream| async move {
SslAcceptorExt::accept_async(&acceptor, stream.into_parts().0).await
.map_err(|e| println!("Openssl error: {}", e))
}))
// .and_then() combinator chains result of previos service call to argument
/// for next service calll. in this case, on success we chain
/// ssl stream to the `logger` service.
.and_then(fn_service(logger))
// Next service counts number of connections
.and_then(move |_| {
let num = num.fetch_add(1, Ordering::Relaxed);
println!("got ssl connection {:?}", num);
future::ok(())
})
},
)?
.run()
}
```
## License ## License
This project is licensed under either of This project is licensed under either of
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)) * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0))
@@ -73,6 +30,5 @@ at your option.
## Code of Conduct ## Code of Conduct
Contribution to the actix-net crate is organized under the terms of the Contribution to the actix-net repo is organized under the terms of the Contributor Covenant.
Contributor Covenant, the maintainer of actix-net, @fafhrd91, promises to The Actix team promises to intervene to uphold that code of conduct.
intervene to uphold that code of conduct.

View File

@@ -1,14 +1,25 @@
# Changes # Changes
## Unreleased - 2020-xx-xx ## Unreleased - 2021-xx-xx
* Upgrade `pin-project` to `1.0`.
## 0.4.0-beta.1 - 2020-12-28
* Replace `pin-project` with `pin-project-lite`. [#237]
* Upgrade `tokio` dependency to `1`. [#237]
* Upgrade `tokio-util` dependency to `0.6`. [#237]
* Upgrade `bytes` dependency to `1`. [#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`.
@@ -18,32 +29,31 @@
* 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,8 +1,8 @@
[package] [package]
name = "actix-codec" name = "actix-codec"
version = "0.3.0" version = "0.4.0-beta.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Codec utilities for working with framed protocols." description = "Codec utilities for working with framed protocols"
keywords = ["network", "framework", "async", "futures"] keywords = ["network", "framework", "async", "futures"]
homepage = "https://actix.rs" homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git" repository = "https://github.com/actix/actix-net.git"
@@ -17,10 +17,10 @@ path = "src/lib.rs"
[dependencies] [dependencies]
bitflags = "1.2.1" bitflags = "1.2.1"
bytes = "0.5.2" bytes = "1"
futures-core = { version = "0.3.4", default-features = false } futures-core = { version = "0.3.7", default-features = false }
futures-sink = { version = "0.3.4", default-features = false } futures-sink = { version = "0.3.7", default-features = false }
log = "0.4" log = "0.4"
pin-project = "1.0.0" pin-project-lite = "0.2"
tokio = { version = "0.2.5", default-features = false } tokio = "1"
tokio-util = { version = "0.3.1", default-features = false, features = ["codec"] } tokio-util = { version = "0.6", features = ["codec", "io"] }

View File

@@ -14,7 +14,7 @@ impl Encoder<Bytes> for BytesCodec {
#[inline] #[inline]
fn encode(&mut self, item: Bytes, dst: &mut BytesMut) -> Result<(), Self::Error> { fn encode(&mut self, item: Bytes, dst: &mut BytesMut) -> Result<(), Self::Error> {
dst.extend_from_slice(item.bytes()); dst.extend_from_slice(item.chunk());
Ok(()) Ok(())
} }
} }

View File

@@ -5,7 +5,6 @@ use std::{fmt, io};
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::pin_project;
use crate::{AsyncRead, AsyncWrite, Decoder, Encoder}; use crate::{AsyncRead, AsyncWrite, Decoder, Encoder};
@@ -21,22 +20,23 @@ bitflags::bitflags! {
} }
} }
/// A unified `Stream` and `Sink` interface to an underlying I/O object, using pin_project_lite::pin_project! {
/// the `Encoder` and `Decoder` traits to encode and decode frames. /// A unified `Stream` and `Sink` interface to an underlying I/O object, using
/// /// the `Encoder` and `Decoder` traits to encode and decode frames.
/// Raw I/O objects work with byte sequences, but higher-level code usually ///
/// wants to batch these into meaningful chunks, called "frames". This /// Raw I/O objects work with byte sequences, but higher-level code usually
/// method layers framing on top of an I/O object, by using the `Encoder`/`Decoder` /// wants to batch these into meaningful chunks, called "frames". This
/// traits to handle encoding and decoding of message frames. Note that /// method layers framing on top of an I/O object, by using the `Encoder`/`Decoder`
/// the incoming and outgoing frame types may be distinct. /// traits to handle encoding and decoding of message frames. Note that
#[pin_project] /// the incoming and outgoing frame types may be distinct.
pub struct Framed<T, U> { pub struct Framed<T, U> {
#[pin] #[pin]
io: T, io: T,
codec: U, codec: U,
flags: Flags, flags: Flags,
read_buf: BytesMut, read_buf: BytesMut,
write_buf: BytesMut, write_buf: BytesMut,
}
} }
impl<T, U> Framed<T, U> impl<T, U> Framed<T, U>
@@ -220,7 +220,8 @@ impl<T, U> Framed<T, U> {
if remaining < LW { if remaining < LW {
this.read_buf.reserve(HW - remaining) this.read_buf.reserve(HW - remaining)
} }
let cnt = match this.io.poll_read_buf(cx, &mut this.read_buf) {
let cnt = match tokio_util::io::poll_read_buf(this.io, cx, this.read_buf) {
Poll::Pending => return Poll::Pending, Poll::Pending => return Poll::Pending,
Poll::Ready(Err(e)) => return Poll::Ready(Some(Err(e.into()))), Poll::Ready(Err(e)) => return Poll::Ready(Some(Err(e.into()))),
Poll::Ready(Ok(cnt)) => cnt, Poll::Ready(Ok(cnt)) => cnt,

View File

@@ -1,4 +1,4 @@
//! Utilities for encoding and decoding frames. //! Codec utilities for working with framed protocols.
//! //!
//! Contains adapters to go from streams of bytes, [`AsyncRead`] and //! Contains adapters to go from streams of bytes, [`AsyncRead`] and
//! [`AsyncWrite`], to framed streams implementing [`Sink`] and [`Stream`]. //! [`AsyncWrite`], to framed streams implementing [`Sink`] and [`Stream`].
@@ -18,5 +18,6 @@ mod framed;
pub use self::bcodec::BytesCodec; pub use self::bcodec::BytesCodec;
pub use self::framed::{Framed, FramedParts}; pub use self::framed::{Framed, FramedParts};
pub use tokio::io::{AsyncRead, AsyncWrite}; pub use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
pub use tokio_util::codec::{Decoder, Encoder}; pub use tokio_util::codec::{Decoder, Encoder};
pub use tokio_util::io::poll_read_buf;

View File

@@ -1,154 +0,0 @@
# Changes
## Unreleased - 2020-xx-xx
## 2.0.0 - 2020-09-02
- No significant changes from `2.0.0-alpha.4`.
## 2.0.0-alpha.4 - 2020-08-17
### Changed
* Update `rustls` dependency to 0.18
* Update `tokio-rustls` dependency to 0.14
## [2.0.0-alpha.3] - 2020-05-08
### Fixed
* Corrected spelling of `ConnectError::Unresolverd` to `ConnectError::Unresolved`
## [2.0.0-alpha.2] - 2020-03-08
### Changed
* Update `trust-dns-proto` dependency to 0.19. [#116]
* Update `trust-dns-resolver` dependency to 0.19. [#116]
* `Address` trait is now required to have static lifetime. [#116]
* `start_resolver` and `start_default_resolver` are now `async` and may return a `ConnectError`. [#116]
[#116]: https://github.com/actix/actix-net/pull/116
## [2.0.0-alpha.1] - 2020-03-03
### Changed
* Update `rustls` dependency to 0.17
* Update `tokio-rustls` dependency to 0.13
## [1.0.2] - 2020-01-15
* Fix actix-service 1.0.3 compatibility
## [1.0.1] - 2019-12-15
* Fix trust-dns-resolver compilation
## [1.0.0] - 2019-12-11
* Release
## [1.0.0-alpha.3] - 2019-12-07
### Changed
* Migrate to tokio 0.2
## [1.0.0-alpha.2] - 2019-12-02
### Changed
* Migrated to `std::future`
## [0.3.0] - 2019-10-03
### Changed
* Update `rustls` to 0.16
* Minimum required Rust version upped to 1.37.0
## [0.2.5] - 2019-09-05
* Add `TcpConnectService`
## [0.2.4] - 2019-09-02
* Use arbiter's storage for default async resolver
## [0.2.3] - 2019-08-05
* Add `ConnectService` and `OpensslConnectService`
## [0.2.2] - 2019-07-24
* Add `rustls` support
## [0.2.1] - 2019-07-17
### Added
* Expose Connect addrs #30
### Changed
* Update `derive_more` to 0.15
## [0.2.0] - 2019-05-12
### Changed
* Upgrade to actix-service 0.4
## [0.1.5] - 2019-04-19
### Added
* `Connect::set_addr()`
### Changed
* Use trust-dns-resolver 0.11.0
## [0.1.4] - 2019-04-12
### Changed
* Do not start default resolver immediately for default connector.
## [0.1.3] - 2019-04-11
### Changed
* Start trust-dns default resolver on first use
## [0.1.2] - 2019-04-04
### Added
* Log error if dns system config could not be loaded.
### Changed
* Rename connect Connector to TcpConnector #10
## [0.1.1] - 2019-03-15
### Fixed
* Fix error handling for single address
## [0.1.0] - 2019-03-14
* Refactor resolver and connector services
* Rename crate

View File

@@ -1,58 +0,0 @@
[package]
name = "actix-connect"
version = "2.0.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "TCP connector service for Actix ecosystem."
keywords = ["network", "framework", "async", "futures"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-connect/"
categories = ["network-programming", "asynchronous"]
license = "MIT OR Apache-2.0"
edition = "2018"
[package.metadata.docs.rs]
features = ["openssl", "rustls", "uri"]
[lib]
name = "actix_connect"
path = "src/lib.rs"
[features]
default = ["uri"]
# openssl
openssl = ["open-ssl", "tokio-openssl"]
# rustls
rustls = ["rust-tls", "tokio-rustls", "webpki"]
# support http::Uri as connect address
uri = ["http"]
[dependencies]
actix-service = "1.0.6"
actix-codec = "0.3.0"
actix-utils = "2.0.0"
actix-rt = "1.1.1"
derive_more = "0.99.2"
either = "1.5.3"
futures-util = { version = "0.3.4", default-features = false }
http = { version = "0.2.0", optional = true }
log = "0.4"
trust-dns-proto = { version = "0.19", default-features = false, features = ["tokio-runtime"] }
trust-dns-resolver = { version = "0.19", default-features = false, features = ["tokio-runtime", "system-config"] }
# openssl
open-ssl = { package = "openssl", version = "0.10", optional = true }
tokio-openssl = { version = "0.4.0", optional = true }
# rustls
rust-tls = { package = "rustls", version = "0.18.0", optional = true }
tokio-rustls = { version = "0.14.0", optional = true }
webpki = { version = "0.21", optional = true }
[dev-dependencies]
bytes = "0.5.3"
actix-testing = "1.0.0"

View File

@@ -1,6 +1,6 @@
# Changes # Changes
## Unreleased - 2020-xx-xx ## Unreleased - 2021-xx-xx
## 0.2.5 - 2020-09-20 ## 0.2.5 - 2020-09-20

View File

@@ -22,8 +22,8 @@ regex = "1.3.1"
serde = "1.0.104" serde = "1.0.104"
bytestring = "0.1.2" bytestring = "0.1.2"
log = "0.4.8" log = "0.4.8"
http = { version = "0.2.0", optional = true } http = { version = "0.2.2", optional = true }
[dev-dependencies] [dev-dependencies]
http = "0.2.0" http = "0.2.2"
serde_derive = "1.0" serde_derive = "1.0"

View File

@@ -1,6 +1,6 @@
# Changes # Changes
## Unreleased - 2020-xx-xx ## Unreleased - 2021-xx-xx
## 2.0.0-beta.1 - 2020-12-28 ## 2.0.0-beta.1 - 2020-12-28

View File

@@ -1,11 +1,28 @@
# Changes # Changes
## Unreleased - 2020-xx-xx ## Unreleased - 2021-xx-xx
## 2.0.0-beta.2 - 2021-01-03
* Merge `actix-testing` to `actix-server` as `test_server` mod. [#242]
[#242]: https://github.com/actix/actix-net/pull/242
## 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]
* Remove `socket2` dependency. [#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]
* Convert `mio::net::TcpStream` to `actix_rt::net::TcpStream`(`UnixStream` for uds) using
`FromRawFd` and `IntoRawFd`(`FromRawSocket` and `IntoRawSocket` on windows). [#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
[#239]: https://github.com/actix/actix-net/pull/239
## 1.0.4 - 2020-09-12 ## 1.0.4 - 2020-09-12

View File

@@ -1,7 +1,10 @@
[package] [package]
name = "actix-server" name = "actix-server"
version = "1.0.4" version = "2.0.0-beta.2"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = [
"Nikolay Kim <fafhrd91@gmail.com>",
"fakeshadow <24548779@qq.com>",
]
description = "General purpose TCP server built for the Actix ecosystem" description = "General purpose TCP server built for the Actix ecosystem"
keywords = ["network", "framework", "async", "futures"] keywords = ["network", "framework", "async", "futures"]
homepage = "https://actix.rs" homepage = "https://actix.rs"
@@ -20,25 +23,20 @@ path = "src/lib.rs"
default = [] default = []
[dependencies] [dependencies]
actix-service = "1.0.6" actix-codec = "0.4.0-beta.1"
actix-rt = "1.1.1" actix-rt = "2.0.0-beta.1"
actix-codec = "0.3.0" actix-service = "2.0.0-beta.2"
actix-utils = "2.0.0" actix-utils = "3.0.0-beta.1"
futures-core = { version = "0.3.7", default-features = false }
log = "0.4" log = "0.4"
mio = { version = "0.7.6", features = ["os-poll", "net"] }
num_cpus = "1.13" num_cpus = "1.13"
mio = "0.6.19"
socket2 = "0.3"
futures-channel = { version = "0.3.4", default-features = false }
futures-util = { version = "0.3.4", default-features = false, features = ["sink"] }
slab = "0.4" slab = "0.4"
tokio = { version = "1", features = ["sync"] }
# unix domain sockets
# FIXME: Remove it and use mio own uds feature once mio 0.7 is released
mio-uds = { version = "0.6.7" }
[dev-dependencies] [dev-dependencies]
bytes = "0.5" bytes = "1"
env_logger = "0.7" env_logger = "0.8"
actix-testing = "1.0.0" futures-util = { version = "0.3.7", default-features = false, features = ["sink"] }
tokio = { version = "0.2", features = ["io-util"] } tokio = { version = "1", features = ["io-util"] }

View File

@@ -1,120 +1,86 @@
use std::sync::mpsc as sync_mpsc;
use std::time::Duration; use std::time::Duration;
use std::{io, thread}; use std::{io, thread};
use actix_rt::time::{delay_until, Instant}; use actix_rt::time::{sleep_until, Instant};
use actix_rt::System; use actix_rt::System;
use log::{error, info}; use log::{error, info};
use mio::{Interest, Poll, Token as MioToken};
use slab::Slab; use slab::Slab;
use crate::server::Server; use crate::server::Server;
use crate::socket::{SocketAddr, SocketListener, StdListener}; use crate::socket::{MioListener, SocketAddr};
use crate::worker::{Conn, WorkerClient}; use crate::waker_queue::{WakerInterest, WakerQueue, WAKER_TOKEN};
use crate::worker::{Conn, WorkerHandle};
use crate::Token; use crate::Token;
pub(crate) enum Command {
Pause,
Resume,
Stop,
Worker(WorkerClient),
}
struct ServerSocketInfo { struct ServerSocketInfo {
// addr for socket. mainly used for logging.
addr: SocketAddr, addr: SocketAddr,
// be ware this is the crate token for identify socket and should not be confused with
// mio::Token
token: Token, token: Token,
sock: SocketListener, lst: MioListener,
// timeout is used to mark the deadline when this socket's listener should be registered again
// after an error.
timeout: Option<Instant>, timeout: Option<Instant>,
} }
#[derive(Clone)] /// Accept loop would live with `ServerBuilder`.
pub(crate) struct AcceptNotify(mio::SetReadiness); ///
/// It's tasked with construct `Poll` instance and `WakerQueue` which would be distributed to
impl AcceptNotify { /// `Accept` and `Worker`.
pub(crate) fn new(ready: mio::SetReadiness) -> Self { ///
AcceptNotify(ready) /// It would also listen to `ServerCommand` and push interests to `WakerQueue`.
}
pub(crate) fn notify(&self) {
let _ = self.0.set_readiness(mio::Ready::readable());
}
}
impl Default for AcceptNotify {
fn default() -> Self {
AcceptNotify::new(mio::Registration::new2().1)
}
}
pub(crate) struct AcceptLoop { pub(crate) struct AcceptLoop {
cmd_reg: Option<mio::Registration>,
cmd_ready: mio::SetReadiness,
notify_reg: Option<mio::Registration>,
notify_ready: mio::SetReadiness,
tx: sync_mpsc::Sender<Command>,
rx: Option<sync_mpsc::Receiver<Command>>,
srv: Option<Server>, srv: Option<Server>,
poll: Option<Poll>,
waker: WakerQueue,
} }
impl AcceptLoop { impl AcceptLoop {
pub fn new(srv: Server) -> AcceptLoop { pub fn new(srv: Server) -> Self {
let (tx, rx) = sync_mpsc::channel(); let poll = Poll::new().unwrap_or_else(|e| panic!("Can not create `mio::Poll`: {}", e));
let (cmd_reg, cmd_ready) = mio::Registration::new2(); let waker = WakerQueue::new(poll.registry())
let (notify_reg, notify_ready) = mio::Registration::new2(); .unwrap_or_else(|e| panic!("Can not create `mio::Waker`: {}", e));
AcceptLoop { Self {
tx,
cmd_ready,
cmd_reg: Some(cmd_reg),
notify_ready,
notify_reg: Some(notify_reg),
rx: Some(rx),
srv: Some(srv), srv: Some(srv),
poll: Some(poll),
waker,
} }
} }
pub fn send(&self, msg: Command) { pub(crate) fn waker_owned(&self) -> WakerQueue {
let _ = self.tx.send(msg); self.waker.clone()
let _ = self.cmd_ready.set_readiness(mio::Ready::readable());
} }
pub fn get_notify(&self) -> AcceptNotify { pub fn wake(&self, i: WakerInterest) {
AcceptNotify::new(self.notify_ready.clone()) self.waker.wake(i);
} }
pub(crate) fn start( pub(crate) fn start(
&mut self, &mut self,
socks: Vec<(Token, StdListener)>, socks: Vec<(Token, MioListener)>,
workers: Vec<WorkerClient>, handles: Vec<WorkerHandle>,
) { ) {
let srv = self.srv.take().expect("Can not re-use AcceptInfo"); let srv = self.srv.take().expect("Can not re-use AcceptInfo");
let poll = self.poll.take().unwrap();
let waker = self.waker.clone();
Accept::start( Accept::start(poll, waker, socks, srv, handles);
self.rx.take().expect("Can not re-use AcceptInfo"),
self.cmd_reg.take().expect("Can not re-use AcceptInfo"),
self.notify_reg.take().expect("Can not re-use AcceptInfo"),
socks,
srv,
workers,
);
} }
} }
/// poll instance of the server.
struct Accept { struct Accept {
poll: mio::Poll, poll: Poll,
rx: sync_mpsc::Receiver<Command>, waker: WakerQueue,
sockets: Slab<ServerSocketInfo>, handles: Vec<WorkerHandle>,
workers: Vec<WorkerClient>,
srv: Server, srv: Server,
timer: (mio::Registration, mio::SetReadiness),
next: usize, next: usize,
backpressure: bool, backpressure: bool,
} }
const DELTA: usize = 100;
const CMD: mio::Token = mio::Token(0);
const TIMER: mio::Token = mio::Token(1);
const NOTIFY: mio::Token = mio::Token(2);
/// This function defines errors that are per-connection. Which basically /// This function defines errors that are per-connection. Which basically
/// means that if we get this error from `accept()` system call it means /// means that if we get this error from `accept()` system call it means
/// next connection might be ready to be accepted. /// next connection might be ready to be accepted.
@@ -129,326 +95,290 @@ fn connection_error(e: &io::Error) -> bool {
} }
impl Accept { impl Accept {
#![allow(clippy::too_many_arguments)]
pub(crate) fn start( pub(crate) fn start(
rx: sync_mpsc::Receiver<Command>, poll: Poll,
cmd_reg: mio::Registration, waker: WakerQueue,
notify_reg: mio::Registration, socks: Vec<(Token, MioListener)>,
socks: Vec<(Token, StdListener)>,
srv: Server, srv: Server,
workers: Vec<WorkerClient>, handles: Vec<WorkerHandle>,
) { ) {
// Accept runs in its own thread and would want to spawn additional futures to current
// actix system.
let sys = System::current(); let sys = System::current();
thread::Builder::new()
// start accept thread
let _ = thread::Builder::new()
.name("actix-server accept loop".to_owned()) .name("actix-server accept loop".to_owned())
.spawn(move || { .spawn(move || {
System::set_current(sys); System::set_current(sys);
let mut accept = Accept::new(rx, socks, workers, srv); let (mut accept, sockets) =
Accept::new_with_sockets(poll, waker, socks, handles, srv);
// Start listening for incoming commands accept.poll_with(sockets);
if let Err(err) = accept.poll.register( })
&cmd_reg, .unwrap();
CMD,
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
panic!("Can not register Registration: {}", err);
}
// Start listening for notify updates
if let Err(err) = accept.poll.register(
&notify_reg,
NOTIFY,
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
panic!("Can not register Registration: {}", err);
}
accept.poll();
});
} }
fn new( fn new_with_sockets(
rx: sync_mpsc::Receiver<Command>, poll: Poll,
socks: Vec<(Token, StdListener)>, waker: WakerQueue,
workers: Vec<WorkerClient>, socks: Vec<(Token, MioListener)>,
handles: Vec<WorkerHandle>,
srv: Server, srv: Server,
) -> Accept { ) -> (Accept, Slab<ServerSocketInfo>) {
// Create a poll instance
let poll = match mio::Poll::new() {
Ok(poll) => poll,
Err(err) => panic!("Can not create mio::Poll: {}", err),
};
// Start accept
let mut sockets = Slab::new(); let mut sockets = Slab::new();
for (hnd_token, lst) in socks.into_iter() { for (hnd_token, mut lst) in socks.into_iter() {
let addr = lst.local_addr(); let addr = lst.local_addr();
let server = lst.into_listener();
let entry = sockets.vacant_entry(); let entry = sockets.vacant_entry();
let token = entry.key(); let token = entry.key();
// Start listening for incoming connections // Start listening for incoming connections
if let Err(err) = poll.register( poll.registry()
&server, .register(&mut lst, MioToken(token), Interest::READABLE)
mio::Token(token + DELTA), .unwrap_or_else(|e| panic!("Can not register io: {}", e));
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
panic!("Can not register io: {}", err);
}
entry.insert(ServerSocketInfo { entry.insert(ServerSocketInfo {
addr, addr,
token: hnd_token, token: hnd_token,
sock: server, lst,
timeout: None, timeout: None,
}); });
} }
// Timer let accept = Accept {
let (tm, tmr) = mio::Registration::new2();
if let Err(err) =
poll.register(&tm, TIMER, mio::Ready::readable(), mio::PollOpt::edge())
{
panic!("Can not register Registration: {}", err);
}
Accept {
poll, poll,
rx, waker,
sockets, handles,
workers,
srv, srv,
next: 0, next: 0,
timer: (tm, tmr),
backpressure: false, backpressure: false,
} };
(accept, sockets)
} }
fn poll(&mut self) { fn poll_with(&mut self, mut sockets: Slab<ServerSocketInfo>) {
// Create storage for events
let mut events = mio::Events::with_capacity(128); let mut events = mio::Events::with_capacity(128);
loop { loop {
if let Err(err) = self.poll.poll(&mut events, None) { self.poll
panic!("Poll error: {}", err); .poll(&mut events, None)
} .unwrap_or_else(|e| panic!("Poll error: {}", e));
for event in events.iter() { for event in events.iter() {
let token = event.token(); let token = event.token();
match token { match token {
CMD => { // This is a loop because interests for command from previous version was
if !self.process_cmd() { // a loop that would try to drain the command channel. It's yet unknown
return; // if it's necessary/good practice to actively drain the waker queue.
WAKER_TOKEN => 'waker: loop {
// take guard with every iteration so no new interest can be added
// until the current task is done.
let mut guard = self.waker.guard();
match guard.pop_front() {
// worker notify it becomes available. we may want to recover
// from backpressure.
Some(WakerInterest::WorkerAvailable) => {
drop(guard);
self.maybe_backpressure(&mut sockets, false);
}
// a new worker thread is made and it's handle would be added
// to Accept
Some(WakerInterest::Worker(handle)) => {
drop(guard);
// maybe we want to recover from a backpressure.
self.maybe_backpressure(&mut sockets, false);
self.handles.push(handle);
}
// got timer interest and it's time to try register socket(s)
// again.
Some(WakerInterest::Timer) => {
drop(guard);
self.process_timer(&mut sockets)
}
Some(WakerInterest::Pause) => {
drop(guard);
sockets.iter_mut().for_each(|(_, info)| {
match self.deregister(info) {
Ok(_) => info!(
"Paused accepting connections on {}",
info.addr
),
Err(e) => {
error!("Can not deregister server socket {}", e)
}
}
});
}
Some(WakerInterest::Resume) => {
drop(guard);
sockets.iter_mut().for_each(|(token, info)| {
self.register_logged(token, info);
});
}
Some(WakerInterest::Stop) => {
return self.deregister_all(&mut sockets);
}
// waker queue is drained.
None => {
// Reset the WakerQueue before break so it does not grow
// infinitely.
WakerQueue::reset(&mut guard);
break 'waker;
}
} }
} },
TIMER => self.process_timer(),
NOTIFY => self.backpressure(false),
_ => { _ => {
let token = usize::from(token); let token = usize::from(token);
if token < DELTA { self.accept(&mut sockets, token);
continue;
}
self.accept(token - DELTA);
} }
} }
} }
} }
} }
fn process_timer(&mut self) { fn process_timer(&self, sockets: &mut Slab<ServerSocketInfo>) {
let now = Instant::now(); let now = Instant::now();
for (token, info) in self.sockets.iter_mut() { sockets.iter_mut().for_each(|(token, info)| {
// only the ServerSocketInfo have an associate timeout value was de registered.
if let Some(inst) = info.timeout.take() { if let Some(inst) = info.timeout.take() {
if now > inst { if now > inst {
if let Err(err) = self.poll.register( self.register_logged(token, info);
&info.sock,
mio::Token(token + DELTA),
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
error!("Can not register server socket {}", err);
} else {
info!("Resume accepting connections on {}", info.addr);
}
} else { } else {
info.timeout = Some(inst); info.timeout = Some(inst);
} }
} }
} });
}
fn process_cmd(&mut self) -> bool {
loop {
match self.rx.try_recv() {
Ok(cmd) => match cmd {
Command::Pause => {
for (_, info) in self.sockets.iter_mut() {
if let Err(err) = self.poll.deregister(&info.sock) {
error!("Can not deregister server socket {}", err);
} else {
info!("Paused accepting connections on {}", info.addr);
}
}
}
Command::Resume => {
for (token, info) in self.sockets.iter() {
if let Err(err) = self.register(token, info) {
error!("Can not resume socket accept process: {}", err);
} else {
info!(
"Accepting connections on {} has been resumed",
info.addr
);
}
}
}
Command::Stop => {
for (_, info) in self.sockets.iter() {
let _ = self.poll.deregister(&info.sock);
}
return false;
}
Command::Worker(worker) => {
self.backpressure(false);
self.workers.push(worker);
}
},
Err(err) => match err {
sync_mpsc::TryRecvError::Empty => break,
sync_mpsc::TryRecvError::Disconnected => {
for (_, info) in self.sockets.iter() {
let _ = self.poll.deregister(&info.sock);
}
return false;
}
},
}
}
true
} }
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
fn register(&self, token: usize, info: &ServerSocketInfo) -> io::Result<()> { fn register(&self, token: usize, info: &mut ServerSocketInfo) -> io::Result<()> {
self.poll.register( self.poll
&info.sock, .registry()
mio::Token(token + DELTA), .register(&mut info.lst, MioToken(token), Interest::READABLE)
mio::Ready::readable(),
mio::PollOpt::edge(),
)
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
fn register(&self, token: usize, info: &ServerSocketInfo) -> io::Result<()> { fn register(&self, token: usize, info: &mut ServerSocketInfo) -> io::Result<()> {
// On windows, calling register without deregister cause an error. // On windows, calling register without deregister cause an error.
// See https://github.com/actix/actix-web/issues/905 // See https://github.com/actix/actix-web/issues/905
// Calling reregister seems to fix the issue. // Calling reregister seems to fix the issue.
self.poll self.poll
.register( .registry()
&info.sock, .register(&mut info.lst, mio::Token(token), Interest::READABLE)
mio::Token(token + DELTA),
mio::Ready::readable(),
mio::PollOpt::edge(),
)
.or_else(|_| { .or_else(|_| {
self.poll.reregister( self.poll.registry().reregister(
&info.sock, &mut info.lst,
mio::Token(token + DELTA), mio::Token(token),
mio::Ready::readable(), Interest::READABLE,
mio::PollOpt::edge(),
) )
}) })
} }
fn backpressure(&mut self, on: bool) { fn register_logged(&self, token: usize, info: &mut ServerSocketInfo) {
match self.register(token, info) {
Ok(_) => info!("Resume accepting connections on {}", info.addr),
Err(e) => error!("Can not register server socket {}", e),
}
}
fn deregister(&self, info: &mut ServerSocketInfo) -> io::Result<()> {
self.poll.registry().deregister(&mut info.lst)
}
fn deregister_all(&self, sockets: &mut Slab<ServerSocketInfo>) {
sockets.iter_mut().for_each(|(_, info)| {
info!("Accepting connections on {} has been paused", info.addr);
let _ = self.deregister(info);
});
}
fn maybe_backpressure(&mut self, sockets: &mut Slab<ServerSocketInfo>, on: bool) {
if self.backpressure { if self.backpressure {
if !on { if !on {
self.backpressure = false; self.backpressure = false;
for (token, info) in self.sockets.iter() { for (token, info) in sockets.iter_mut() {
if info.timeout.is_some() { if info.timeout.is_some() {
// socket will attempt to re-register itself when its timeout completes // socket will attempt to re-register itself when its timeout completes
continue; continue;
} }
self.register_logged(token, info);
if let Err(err) = self.register(token, info) {
error!("Can not resume socket accept process: {}", err);
} else {
info!("Accepting connections on {} has been resumed", info.addr);
}
} }
} }
} else if on { } else if on {
self.backpressure = true; self.backpressure = true;
for (_, info) in self.sockets.iter() { self.deregister_all(sockets);
let _ = self.poll.deregister(&info.sock);
info!("Accepting connections on {} has been paused", info.addr);
}
} }
} }
fn accept_one(&mut self, mut msg: Conn) { fn accept_one(&mut self, sockets: &mut Slab<ServerSocketInfo>, mut msg: Conn) {
if self.backpressure { if self.backpressure {
while !self.workers.is_empty() { while !self.handles.is_empty() {
match self.workers[self.next].send(msg) { match self.handles[self.next].send(msg) {
Ok(_) => (), Ok(_) => {
self.set_next();
break;
}
Err(tmp) => { Err(tmp) => {
self.srv.worker_faulted(self.workers[self.next].idx); // worker lost contact and could be gone. a message is sent to
// `ServerBuilder` future to notify it a new worker should be made.
// after that remove the fault worker.
self.srv.worker_faulted(self.handles[self.next].idx);
msg = tmp; msg = tmp;
self.workers.swap_remove(self.next); self.handles.swap_remove(self.next);
if self.workers.is_empty() { if self.handles.is_empty() {
error!("No workers"); error!("No workers");
return; return;
} else if self.workers.len() <= self.next { } else if self.handles.len() <= self.next {
self.next = 0; self.next = 0;
} }
continue; continue;
} }
} }
self.next = (self.next + 1) % self.workers.len();
break;
} }
} else { } else {
let mut idx = 0; let mut idx = 0;
while idx < self.workers.len() { while idx < self.handles.len() {
idx += 1; idx += 1;
if self.workers[self.next].available() { if self.handles[self.next].available() {
match self.workers[self.next].send(msg) { match self.handles[self.next].send(msg) {
Ok(_) => { Ok(_) => {
self.next = (self.next + 1) % self.workers.len(); self.set_next();
return; return;
} }
// worker lost contact and could be gone. a message is sent to
// `ServerBuilder` future to notify it a new worker should be made.
// after that remove the fault worker and enter backpressure if necessary.
Err(tmp) => { Err(tmp) => {
self.srv.worker_faulted(self.workers[self.next].idx); self.srv.worker_faulted(self.handles[self.next].idx);
msg = tmp; msg = tmp;
self.workers.swap_remove(self.next); self.handles.swap_remove(self.next);
if self.workers.is_empty() { if self.handles.is_empty() {
error!("No workers"); error!("No workers");
self.backpressure(true); self.maybe_backpressure(sockets, true);
return; return;
} else if self.workers.len() <= self.next { } else if self.handles.len() <= self.next {
self.next = 0; self.next = 0;
} }
continue; continue;
} }
} }
} }
self.next = (self.next + 1) % self.workers.len(); self.set_next();
} }
// enable backpressure // enable backpressure
self.backpressure(true); self.maybe_backpressure(sockets, true);
self.accept_one(msg); self.accept_one(sockets, msg);
} }
} }
fn accept(&mut self, token: usize) { // set next worker handle that would accept work.
fn set_next(&mut self) {
self.next = (self.next + 1) % self.handles.len();
}
fn accept(&mut self, sockets: &mut Slab<ServerSocketInfo>, token: usize) {
loop { loop {
let msg = if let Some(info) = self.sockets.get_mut(token) { let msg = if let Some(info) = sockets.get_mut(token) {
match info.sock.accept() { match info.lst.accept() {
Ok(Some((io, addr))) => Conn { Ok(Some((io, addr))) => Conn {
io, io,
token: info.token, token: info.token,
@@ -458,18 +388,22 @@ impl Accept {
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => return, Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => return,
Err(ref e) if connection_error(e) => continue, Err(ref e) if connection_error(e) => continue,
Err(e) => { Err(e) => {
// deregister listener temporary
error!("Error accepting connection: {}", e); error!("Error accepting connection: {}", e);
if let Err(err) = self.poll.deregister(&info.sock) { if let Err(err) = self.deregister(info) {
error!("Can not deregister server socket {}", err); error!("Can not deregister server socket {}", err);
} }
// sleep after error // sleep after error. write the timeout to socket info as later the poll
// would need it mark which socket and when it's listener should be
// registered.
info.timeout = Some(Instant::now() + Duration::from_millis(500)); info.timeout = Some(Instant::now() + Duration::from_millis(500));
let r = self.timer.1.clone(); // after the sleep a Timer interest is sent to Accept Poll
let waker = self.waker.clone();
System::current().arbiter().send(Box::pin(async move { System::current().arbiter().send(Box::pin(async move {
delay_until(Instant::now() + Duration::from_millis(510)).await; sleep_until(Instant::now() + Duration::from_millis(510)).await;
let _ = r.set_readiness(mio::Ready::readable()); waker.wake(WakerInterest::Timer);
})); }));
return; return;
} }
@@ -478,7 +412,7 @@ impl Accept {
return; return;
}; };
self.accept_one(msg); self.accept_one(sockets, msg);
} }
} }
} }

View File

@@ -1,36 +1,35 @@
use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use std::time::Duration; use std::time::Duration;
use std::{io, mem, net}; use std::{io, mem};
use actix_rt::net::TcpStream; use actix_rt::net::TcpStream;
use actix_rt::time::{delay_until, Instant}; use actix_rt::time::{sleep_until, Instant};
use actix_rt::{spawn, System}; use actix_rt::{spawn, System};
use futures_channel::mpsc::{unbounded, UnboundedReceiver};
use futures_channel::oneshot;
use futures_util::future::ready;
use futures_util::stream::FuturesUnordered;
use futures_util::{future::Future, ready, stream::Stream, FutureExt, StreamExt};
use log::{error, info}; use log::{error, info};
use socket2::{Domain, Protocol, Socket, Type}; use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver};
use tokio::sync::oneshot;
use crate::accept::{AcceptLoop, AcceptNotify, Command}; use crate::accept::AcceptLoop;
use crate::config::{ConfiguredService, ServiceConfig}; use crate::config::{ConfiguredService, ServiceConfig};
use crate::server::{Server, ServerCommand}; use crate::server::{Server, ServerCommand};
use crate::service::{InternalServiceFactory, ServiceFactory, StreamNewService}; use crate::service::{InternalServiceFactory, ServiceFactory, StreamNewService};
use crate::signals::{Signal, Signals}; use crate::signals::{Signal, Signals};
use crate::socket::StdListener; use crate::socket::{MioListener, StdSocketAddr, StdTcpListener, ToSocketAddrs};
use crate::worker::{self, Worker, WorkerAvailability, WorkerClient}; use crate::socket::{MioTcpListener, MioTcpSocket};
use crate::Token; use crate::waker_queue::{WakerInterest, WakerQueue};
use crate::worker::{self, Worker, WorkerAvailability, WorkerHandle};
use crate::{join_all, Token};
/// Server builder /// Server builder
pub struct ServerBuilder { pub struct ServerBuilder {
threads: usize, threads: usize,
token: Token, token: Token,
backlog: i32, backlog: u32,
workers: Vec<(usize, WorkerClient)>, handles: Vec<(usize, WorkerHandle)>,
services: Vec<Box<dyn InternalServiceFactory>>, services: Vec<Box<dyn InternalServiceFactory>>,
sockets: Vec<(Token, String, StdListener)>, sockets: Vec<(Token, String, MioListener)>,
accept: AcceptLoop, accept: AcceptLoop,
exit: bool, exit: bool,
shutdown_timeout: Duration, shutdown_timeout: Duration,
@@ -49,13 +48,13 @@ impl Default for ServerBuilder {
impl ServerBuilder { impl ServerBuilder {
/// Create new Server builder instance /// Create new Server builder instance
pub fn new() -> ServerBuilder { pub fn new() -> ServerBuilder {
let (tx, rx) = unbounded(); let (tx, rx) = unbounded_channel();
let server = Server::new(tx); let server = Server::new(tx);
ServerBuilder { ServerBuilder {
threads: num_cpus::get(), threads: num_cpus::get(),
token: Token(0), token: Token::default(),
workers: Vec::new(), handles: Vec::new(),
services: Vec::new(), services: Vec::new(),
sockets: Vec::new(), sockets: Vec::new(),
accept: AcceptLoop::new(server.clone()), accept: AcceptLoop::new(server.clone()),
@@ -89,7 +88,7 @@ impl ServerBuilder {
/// Generally set in the 64-2048 range. Default value is 2048. /// Generally set in the 64-2048 range. Default value is 2048.
/// ///
/// This method should be called before `bind()` method call. /// This method should be called before `bind()` method call.
pub fn backlog(mut self, num: i32) -> Self { pub fn backlog(mut self, num: u32) -> Self {
self.backlog = num; self.backlog = num;
self self
} }
@@ -147,7 +146,7 @@ impl ServerBuilder {
for (name, lst) in cfg.services { for (name, lst) in cfg.services {
let token = self.token.next(); let token = self.token.next();
srv.stream(token, name.clone(), lst.local_addr()?); srv.stream(token, name.clone(), lst.local_addr()?);
self.sockets.push((token, name, StdListener::Tcp(lst))); self.sockets.push((token, name, MioListener::Tcp(lst)));
} }
self.services.push(Box::new(srv)); self.services.push(Box::new(srv));
} }
@@ -160,7 +159,7 @@ impl ServerBuilder {
pub fn bind<F, U, N: AsRef<str>>(mut self, name: N, addr: U, factory: F) -> io::Result<Self> pub fn bind<F, U, N: AsRef<str>>(mut self, name: N, addr: U, factory: F) -> io::Result<Self>
where where
F: ServiceFactory<TcpStream>, F: ServiceFactory<TcpStream>,
U: net::ToSocketAddrs, U: ToSocketAddrs,
{ {
let sockets = bind_addr(addr, self.backlog)?; let sockets = bind_addr(addr, self.backlog)?;
@@ -173,12 +172,12 @@ impl ServerBuilder {
lst.local_addr()?, lst.local_addr()?,
)); ));
self.sockets self.sockets
.push((token, name.as_ref().to_string(), StdListener::Tcp(lst))); .push((token, name.as_ref().to_string(), MioListener::Tcp(lst)));
} }
Ok(self) Ok(self)
} }
#[cfg(all(unix))] #[cfg(unix)]
/// Add new unix domain service to the server. /// Add new unix domain service to the server.
pub fn bind_uds<F, U, N>(self, name: N, addr: U, factory: F) -> io::Result<Self> pub fn bind_uds<F, U, N>(self, name: N, addr: U, factory: F) -> io::Result<Self>
where where
@@ -186,8 +185,6 @@ impl ServerBuilder {
N: AsRef<str>, N: AsRef<str>,
U: AsRef<std::path::Path>, U: AsRef<std::path::Path>,
{ {
use std::os::unix::net::UnixListener;
// The path must not exist when we try to bind. // The path must not exist when we try to bind.
// Try to remove it to avoid bind error. // Try to remove it to avoid bind error.
if let Err(e) = std::fs::remove_file(addr.as_ref()) { if let Err(e) = std::fs::remove_file(addr.as_ref()) {
@@ -197,26 +194,27 @@ impl ServerBuilder {
} }
} }
let lst = UnixListener::bind(addr)?; let lst = crate::socket::StdUnixListener::bind(addr)?;
self.listen_uds(name, lst, factory) self.listen_uds(name, lst, factory)
} }
#[cfg(all(unix))] #[cfg(unix)]
/// Add new unix domain service to the server. /// Add new unix domain service to the server.
/// Useful when running as a systemd service and /// Useful when running as a systemd service and
/// a socket FD can be acquired using the systemd crate. /// a socket FD can be acquired using the systemd crate.
pub fn listen_uds<F, N: AsRef<str>>( pub fn listen_uds<F, N: AsRef<str>>(
mut self, mut self,
name: N, name: N,
lst: std::os::unix::net::UnixListener, lst: crate::socket::StdUnixListener,
factory: F, factory: F,
) -> io::Result<Self> ) -> io::Result<Self>
where where
F: ServiceFactory<actix_rt::net::UnixStream>, F: ServiceFactory<actix_rt::net::UnixStream>,
{ {
use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::net::{IpAddr, Ipv4Addr};
lst.set_nonblocking(true)?;
let token = self.token.next(); let token = self.token.next();
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); let addr = StdSocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
self.services.push(StreamNewService::create( self.services.push(StreamNewService::create(
name.as_ref().to_string(), name.as_ref().to_string(),
token, token,
@@ -224,7 +222,7 @@ impl ServerBuilder {
addr, addr,
)); ));
self.sockets self.sockets
.push((token, name.as_ref().to_string(), StdListener::Uds(lst))); .push((token, name.as_ref().to_string(), MioListener::from(lst)));
Ok(self) Ok(self)
} }
@@ -232,21 +230,25 @@ impl ServerBuilder {
pub fn listen<F, N: AsRef<str>>( pub fn listen<F, N: AsRef<str>>(
mut self, mut self,
name: N, name: N,
lst: net::TcpListener, lst: StdTcpListener,
factory: F, factory: F,
) -> io::Result<Self> ) -> io::Result<Self>
where where
F: ServiceFactory<TcpStream>, F: ServiceFactory<TcpStream>,
{ {
lst.set_nonblocking(true)?;
let addr = lst.local_addr()?;
let token = self.token.next(); let token = self.token.next();
self.services.push(StreamNewService::create( self.services.push(StreamNewService::create(
name.as_ref().to_string(), name.as_ref().to_string(),
token, token,
factory, factory,
lst.local_addr()?, addr,
)); ));
self.sockets self.sockets
.push((token, name.as_ref().to_string(), StdListener::Tcp(lst))); .push((token, name.as_ref().to_string(), MioListener::from(lst)));
Ok(self) Ok(self)
} }
@@ -263,12 +265,12 @@ impl ServerBuilder {
info!("Starting {} workers", self.threads); info!("Starting {} workers", self.threads);
// start workers // start workers
let workers = (0..self.threads) let handles = (0..self.threads)
.map(|idx| { .map(|idx| {
let worker = self.start_worker(idx, self.accept.get_notify()); let handle = self.start_worker(idx, self.accept.waker_owned());
self.workers.push((idx, worker.clone())); self.handles.push((idx, handle.clone()));
worker handle
}) })
.collect(); .collect();
@@ -281,7 +283,7 @@ impl ServerBuilder {
.into_iter() .into_iter()
.map(|t| (t.0, t.2)) .map(|t| (t.0, t.2))
.collect(), .collect(),
workers, handles,
); );
// handle signals // handle signals
@@ -296,10 +298,9 @@ impl ServerBuilder {
} }
} }
fn start_worker(&self, idx: usize, notify: AcceptNotify) -> WorkerClient { fn start_worker(&self, idx: usize, waker: WakerQueue) -> WorkerHandle {
let avail = WorkerAvailability::new(notify); let avail = WorkerAvailability::new(waker);
let services: Vec<Box<dyn InternalServiceFactory>> = let services = self.services.iter().map(|v| v.clone_factory()).collect();
self.services.iter().map(|v| v.clone_factory()).collect();
Worker::start(idx, services, avail, self.shutdown_timeout) Worker::start(idx, services, avail, self.shutdown_timeout)
} }
@@ -307,11 +308,11 @@ impl ServerBuilder {
fn handle_cmd(&mut self, item: ServerCommand) { fn handle_cmd(&mut self, item: ServerCommand) {
match item { match item {
ServerCommand::Pause(tx) => { ServerCommand::Pause(tx) => {
self.accept.send(Command::Pause); self.accept.wake(WakerInterest::Pause);
let _ = tx.send(()); let _ = tx.send(());
} }
ServerCommand::Resume(tx) => { ServerCommand::Resume(tx) => {
self.accept.send(Command::Resume); self.accept.wake(WakerInterest::Resume);
let _ = tx.send(()); let _ = tx.send(());
} }
ServerCommand::Signal(sig) => { ServerCommand::Signal(sig) => {
@@ -355,50 +356,41 @@ impl ServerBuilder {
let exit = self.exit; let exit = self.exit;
// stop accept thread // stop accept thread
self.accept.send(Command::Stop); self.accept.wake(WakerInterest::Stop);
let notify = std::mem::take(&mut self.notify); let notify = std::mem::take(&mut self.notify);
// stop workers // stop workers
if !self.workers.is_empty() && graceful { if !self.handles.is_empty() && graceful {
spawn( let iter = self
self.workers .handles
.iter() .iter()
.map(move |worker| worker.1.stop(graceful)) .map(move |worker| worker.1.stop(graceful))
.collect::<FuturesUnordered<_>>() .collect();
.collect::<Vec<_>>()
.then(move |_| { let fut = join_all(iter);
if let Some(tx) = completion {
let _ = tx.send(()); spawn(async move {
} let _ = fut.await;
for tx in notify { if let Some(tx) = completion {
let _ = tx.send(()); let _ = tx.send(());
} }
if exit { for tx in notify {
spawn( let _ = tx.send(());
async { }
delay_until( if exit {
Instant::now() + Duration::from_millis(300), spawn(async {
) sleep_until(Instant::now() + Duration::from_millis(300)).await;
.await; System::current().stop();
System::current().stop(); });
} }
.boxed(), })
);
}
ready(())
}),
)
} else { } else {
// we need to stop system if server was spawned // we need to stop system if server was spawned
if self.exit { if self.exit {
spawn( spawn(async {
delay_until(Instant::now() + Duration::from_millis(300)).then( sleep_until(Instant::now() + Duration::from_millis(300)).await;
|_| { System::current().stop();
System::current().stop(); });
ready(())
},
),
);
} }
if let Some(tx) = completion { if let Some(tx) = completion {
let _ = tx.send(()); let _ = tx.send(());
@@ -410,9 +402,9 @@ impl ServerBuilder {
} }
ServerCommand::WorkerFaulted(idx) => { ServerCommand::WorkerFaulted(idx) => {
let mut found = false; let mut found = false;
for i in 0..self.workers.len() { for i in 0..self.handles.len() {
if self.workers[i].0 == idx { if self.handles[i].0 == idx {
self.workers.swap_remove(i); self.handles.swap_remove(i);
found = true; found = true;
break; break;
} }
@@ -421,10 +413,10 @@ impl ServerBuilder {
if found { if found {
error!("Worker has died {:?}, restarting", idx); error!("Worker has died {:?}, restarting", idx);
let mut new_idx = self.workers.len(); let mut new_idx = self.handles.len();
'found: loop { 'found: loop {
for i in 0..self.workers.len() { for i in 0..self.handles.len() {
if self.workers[i].0 == new_idx { if self.handles[i].0 == new_idx {
new_idx += 1; new_idx += 1;
continue 'found; continue 'found;
} }
@@ -432,9 +424,9 @@ impl ServerBuilder {
break; break;
} }
let worker = self.start_worker(new_idx, self.accept.get_notify()); let handle = self.start_worker(new_idx, self.accept.waker_owned());
self.workers.push((new_idx, worker.clone())); self.handles.push((new_idx, handle.clone()));
self.accept.send(Command::Worker(worker)); self.accept.wake(WakerInterest::Worker(handle));
} }
} }
} }
@@ -446,20 +438,18 @@ impl Future for ServerBuilder {
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> {
loop { loop {
match ready!(Pin::new(&mut self.cmd).poll_next(cx)) { match Pin::new(&mut self.cmd).poll_recv(cx) {
Some(it) => self.as_mut().get_mut().handle_cmd(it), Poll::Ready(Some(it)) => self.as_mut().get_mut().handle_cmd(it),
None => { _ => return Poll::Pending,
return Poll::Pending;
}
} }
} }
} }
} }
pub(super) fn bind_addr<S: net::ToSocketAddrs>( pub(super) fn bind_addr<S: ToSocketAddrs>(
addr: S, addr: S,
backlog: i32, backlog: u32,
) -> io::Result<Vec<net::TcpListener>> { ) -> io::Result<Vec<MioTcpListener>> {
let mut err = None; let mut err = None;
let mut succ = false; let mut succ = false;
let mut sockets = Vec::new(); let mut sockets = Vec::new();
@@ -487,14 +477,13 @@ pub(super) fn bind_addr<S: net::ToSocketAddrs>(
} }
} }
fn create_tcp_listener(addr: net::SocketAddr, backlog: i32) -> io::Result<net::TcpListener> { fn create_tcp_listener(addr: StdSocketAddr, backlog: u32) -> io::Result<MioTcpListener> {
let domain = match addr { let socket = match addr {
net::SocketAddr::V4(_) => Domain::ipv4(), StdSocketAddr::V4(_) => MioTcpSocket::new_v4()?,
net::SocketAddr::V6(_) => Domain::ipv6(), StdSocketAddr::V6(_) => MioTcpSocket::new_v6()?,
}; };
let socket = Socket::new(domain, Type::stream(), Some(Protocol::tcp()))?;
socket.set_reuse_address(true)?; socket.set_reuseaddr(true)?;
socket.bind(&addr.into())?; socket.bind(addr)?;
socket.listen(backlog)?; socket.listen(backlog)
Ok(socket.into_tcp_listener())
} }

View File

@@ -1,5 +1,6 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::{fmt, io, net}; use std::future::Future;
use std::{fmt, io};
use actix_rt::net::TcpStream; use actix_rt::net::TcpStream;
use actix_service::{ use actix_service::{
@@ -7,23 +8,23 @@ use actix_service::{
ServiceFactory as BaseServiceFactory, ServiceFactory as BaseServiceFactory,
}; };
use actix_utils::counter::CounterGuard; use actix_utils::counter::CounterGuard;
use futures_util::future::{ok, Future, FutureExt, LocalBoxFuture}; use futures_core::future::LocalBoxFuture;
use log::error; use log::error;
use super::builder::bind_addr; use crate::builder::bind_addr;
use super::service::{BoxedServerService, InternalServiceFactory, StreamService}; use crate::service::{BoxedServerService, InternalServiceFactory, StreamService};
use super::Token; use crate::socket::{MioStream, MioTcpListener, StdSocketAddr, StdTcpListener, ToSocketAddrs};
use crate::socket::StdStream; use crate::{ready, Token};
pub struct ServiceConfig { pub struct ServiceConfig {
pub(crate) services: Vec<(String, net::TcpListener)>, pub(crate) services: Vec<(String, MioTcpListener)>,
pub(crate) apply: Option<Box<dyn ServiceRuntimeConfiguration>>, pub(crate) apply: Option<Box<dyn ServiceRuntimeConfiguration>>,
pub(crate) threads: usize, pub(crate) threads: usize,
pub(crate) backlog: i32, pub(crate) backlog: u32,
} }
impl ServiceConfig { impl ServiceConfig {
pub(super) fn new(threads: usize, backlog: i32) -> ServiceConfig { pub(super) fn new(threads: usize, backlog: u32) -> ServiceConfig {
ServiceConfig { ServiceConfig {
threads, threads,
backlog, backlog,
@@ -43,24 +44,20 @@ impl ServiceConfig {
/// Add new service to server /// Add new service to server
pub fn bind<U, N: AsRef<str>>(&mut self, name: N, addr: U) -> io::Result<&mut Self> pub fn bind<U, N: AsRef<str>>(&mut self, name: N, addr: U) -> io::Result<&mut Self>
where where
U: net::ToSocketAddrs, U: ToSocketAddrs,
{ {
let sockets = bind_addr(addr, self.backlog)?; let sockets = bind_addr(addr, self.backlog)?;
for lst in sockets { for lst in sockets {
self.listen(name.as_ref(), lst); self._listen(name.as_ref(), lst);
} }
Ok(self) Ok(self)
} }
/// Add new service to server /// Add new service to server
pub fn listen<N: AsRef<str>>(&mut self, name: N, lst: net::TcpListener) -> &mut Self { pub fn listen<N: AsRef<str>>(&mut self, name: N, lst: StdTcpListener) -> &mut Self {
if self.apply.is_none() { self._listen(name, MioTcpListener::from_std(lst))
self.apply = Some(Box::new(not_configured));
}
self.services.push((name.as_ref().to_string(), lst));
self
} }
/// Register service configuration function. This function get called /// Register service configuration function. This function get called
@@ -72,11 +69,19 @@ impl ServiceConfig {
self.apply = Some(Box::new(f)); self.apply = Some(Box::new(f));
Ok(()) Ok(())
} }
fn _listen<N: AsRef<str>>(&mut self, name: N, lst: MioTcpListener) -> &mut Self {
if self.apply.is_none() {
self.apply = Some(Box::new(not_configured));
}
self.services.push((name.as_ref().to_string(), lst));
self
}
} }
pub(super) struct ConfiguredService { pub(super) struct ConfiguredService {
rt: Box<dyn ServiceRuntimeConfiguration>, rt: Box<dyn ServiceRuntimeConfiguration>,
names: HashMap<Token, (String, net::SocketAddr)>, names: HashMap<Token, (String, StdSocketAddr)>,
topics: HashMap<String, Token>, topics: HashMap<String, Token>,
services: Vec<Token>, services: Vec<Token>,
} }
@@ -91,7 +96,7 @@ impl ConfiguredService {
} }
} }
pub(super) fn stream(&mut self, token: Token, name: String, addr: net::SocketAddr) { pub(super) fn stream(&mut self, token: Token, name: String, addr: StdSocketAddr) {
self.names.insert(token, (name.clone(), addr)); self.names.insert(token, (name.clone(), addr));
self.topics.insert(name, token); self.topics.insert(name, token);
self.services.push(token); self.services.push(token);
@@ -121,7 +126,7 @@ impl InternalServiceFactory for ConfiguredService {
let tokens = self.services.clone(); let tokens = self.services.clone();
// construct services // construct services
async move { Box::pin(async move {
let mut services = rt.services; let mut services = rt.services;
// TODO: Proper error handling here // TODO: Proper error handling here
for f in rt.onstart.into_iter() { for f in rt.onstart.into_iter() {
@@ -146,14 +151,13 @@ impl InternalServiceFactory for ConfiguredService {
token, token,
Box::new(StreamService::new(fn_service(move |_: TcpStream| { Box::new(StreamService::new(fn_service(move |_: TcpStream| {
error!("Service {:?} is not configured", name); error!("Service {:?} is not configured", name);
ok::<_, ()>(()) ready::<Result<_, ()>>(Ok(()))
}))), }))),
)); ));
}; };
} }
Ok(res) Ok(res)
} })
.boxed_local()
} }
} }
@@ -233,13 +237,13 @@ impl ServiceRuntime {
where where
F: Future<Output = ()> + 'static, F: Future<Output = ()> + 'static,
{ {
self.onstart.push(fut.boxed_local()) self.onstart.push(Box::pin(fut))
} }
} }
type BoxedNewService = Box< type BoxedNewService = Box<
dyn BaseServiceFactory< dyn BaseServiceFactory<
(Option<CounterGuard>, StdStream), (Option<CounterGuard>, MioStream),
Response = (), Response = (),
Error = (), Error = (),
InitError = (), InitError = (),
@@ -253,7 +257,7 @@ struct ServiceFactory<T> {
inner: T, inner: T,
} }
impl<T> BaseServiceFactory<(Option<CounterGuard>, StdStream)> for ServiceFactory<T> impl<T> BaseServiceFactory<(Option<CounterGuard>, MioStream)> for ServiceFactory<T>
where where
T: BaseServiceFactory<TcpStream, Config = ()>, T: BaseServiceFactory<TcpStream, Config = ()>,
T::Future: 'static, T::Future: 'static,
@@ -270,7 +274,7 @@ where
fn new_service(&self, _: ()) -> Self::Future { fn new_service(&self, _: ()) -> Self::Future {
let fut = self.inner.new_service(()); let fut = self.inner.new_service(());
async move { Box::pin(async move {
match fut.await { match fut.await {
Ok(s) => Ok(Box::new(StreamService::new(s)) as BoxedServerService), Ok(s) => Ok(Box::new(StreamService::new(s)) as BoxedServerService),
Err(e) => { Err(e) => {
@@ -278,7 +282,6 @@ where
Err(()) Err(())
} }
} }
} })
.boxed_local()
} }
} }

View File

@@ -11,21 +11,38 @@ mod server;
mod service; mod service;
mod signals; mod signals;
mod socket; mod socket;
mod test_server;
mod waker_queue;
mod worker; mod worker;
pub use self::builder::ServerBuilder; pub use self::builder::ServerBuilder;
pub use self::config::{ServiceConfig, ServiceRuntime}; pub use self::config::{ServiceConfig, ServiceRuntime};
pub use self::server::Server; pub use self::server::Server;
pub use self::service::ServiceFactory; pub use self::service::ServiceFactory;
pub use self::test_server::TestServer;
#[doc(hidden)] #[doc(hidden)]
pub use self::socket::FromStream; pub use self::socket::FromStream;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
/// Socket ID token /// Socket ID token
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) struct Token(usize); pub(crate) struct Token(usize);
impl Default for Token {
fn default() -> Self {
Self::new()
}
}
impl Token { impl Token {
fn new() -> Self {
Self(0)
}
pub(crate) fn next(&mut self) -> Token { pub(crate) fn next(&mut self) -> Token {
let token = Token(self.0); let token = Token(self.0);
self.0 += 1; self.0 += 1;
@@ -37,3 +54,90 @@ impl Token {
pub fn new() -> ServerBuilder { pub fn new() -> ServerBuilder {
ServerBuilder::default() ServerBuilder::default()
} }
// temporary Ready type for std::future::{ready, Ready}; Can be removed when MSRV surpass 1.48
#[doc(hidden)]
pub struct Ready<T>(Option<T>);
pub(crate) fn ready<T>(t: T) -> Ready<T> {
Ready(Some(t))
}
impl<T> Unpin for Ready<T> {}
impl<T> Future for Ready<T> {
type Output = T;
fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Ready(self.get_mut().0.take().unwrap())
}
}
// 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 JoinAll<T> {
fut: Vec<JoinFuture<T>>,
}
pub(crate) fn join_all<T>(fut: Vec<impl Future<Output = T> + 'static>) -> JoinAll<T> {
let fut = fut
.into_iter()
.map(|f| JoinFuture::Future(Box::pin(f)))
.collect();
JoinAll { fut }
}
enum JoinFuture<T> {
Future(Pin<Box<dyn Future<Output = T>>>),
Result(Option<T>),
}
impl<T> Unpin for JoinAll<T> {}
impl<T> Future for JoinAll<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 JoinFuture::Future(f) = fut {
match f.as_mut().poll(cx) {
Poll::Ready(t) => {
*fut = JoinFuture::Result(Some(t));
}
Poll::Pending => ready = false,
}
}
}
if ready {
let mut res = Vec::new();
for fut in this.fut.iter_mut() {
if let JoinFuture::Result(f) = fut {
res.push(f.take().unwrap());
}
}
Poll::Ready(res)
} else {
Poll::Pending
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[actix_rt::test]
async fn test_join_all() {
let futs = vec![ready(Ok(1)), ready(Err(3)), ready(Ok(9))];
let mut res = join_all(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

@@ -3,9 +3,8 @@ use std::io;
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use futures_channel::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
use futures_channel::oneshot; use tokio::sync::oneshot;
use futures_util::FutureExt;
use crate::builder::ServerBuilder; use crate::builder::ServerBuilder;
use crate::signals::Signal; use crate::signals::Signal;
@@ -42,11 +41,11 @@ impl Server {
} }
pub(crate) fn signal(&self, sig: Signal) { pub(crate) fn signal(&self, sig: Signal) {
let _ = self.0.unbounded_send(ServerCommand::Signal(sig)); let _ = self.0.send(ServerCommand::Signal(sig));
} }
pub(crate) fn worker_faulted(&self, idx: usize) { pub(crate) fn worker_faulted(&self, idx: usize) {
let _ = self.0.unbounded_send(ServerCommand::WorkerFaulted(idx)); let _ = self.0.send(ServerCommand::WorkerFaulted(idx));
} }
/// Pause accepting incoming connections /// Pause accepting incoming connections
@@ -55,15 +54,19 @@ impl Server {
/// All opened connection remains active. /// All opened connection remains active.
pub fn pause(&self) -> impl Future<Output = ()> { pub fn pause(&self) -> impl Future<Output = ()> {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
let _ = self.0.unbounded_send(ServerCommand::Pause(tx)); let _ = self.0.send(ServerCommand::Pause(tx));
rx.map(|_| ()) async {
let _ = rx.await;
}
} }
/// Resume accepting incoming connections /// Resume accepting incoming connections
pub fn resume(&self) -> impl Future<Output = ()> { pub fn resume(&self) -> impl Future<Output = ()> {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
let _ = self.0.unbounded_send(ServerCommand::Resume(tx)); let _ = self.0.send(ServerCommand::Resume(tx));
rx.map(|_| ()) async {
let _ = rx.await;
}
} }
/// Stop incoming connection processing, stop all workers and exit. /// Stop incoming connection processing, stop all workers and exit.
@@ -71,11 +74,13 @@ impl Server {
/// If server starts with `spawn()` method, then spawned thread get terminated. /// If server starts with `spawn()` method, then spawned thread get terminated.
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.0.unbounded_send(ServerCommand::Stop { let _ = self.0.send(ServerCommand::Stop {
graceful, graceful,
completion: Some(tx), completion: Some(tx),
}); });
rx.map(|_| ()) async {
let _ = rx.await;
}
} }
} }
@@ -93,7 +98,7 @@ impl Future for Server {
if this.1.is_none() { if this.1.is_none() {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
if this.0.unbounded_send(ServerCommand::Notify(tx)).is_err() { if this.0.send(ServerCommand::Notify(tx)).is_err() {
return Poll::Ready(Ok(())); return Poll::Ready(Ok(()));
} }
this.1 = Some(rx); this.1 = Some(rx);
@@ -101,8 +106,7 @@ impl Future for Server {
match Pin::new(this.1.as_mut().unwrap()).poll(cx) { match Pin::new(this.1.as_mut().unwrap()).poll(cx) {
Poll::Pending => Poll::Pending, Poll::Pending => Poll::Pending,
Poll::Ready(Ok(_)) => Poll::Ready(Ok(())), Poll::Ready(_) => Poll::Ready(Ok(())),
Poll::Ready(Err(_)) => Poll::Ready(Ok(())),
} }
} }
} }

View File

@@ -2,15 +2,13 @@ use std::marker::PhantomData;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use actix_rt::spawn;
use actix_service::{Service, ServiceFactory as BaseServiceFactory}; use actix_service::{Service, ServiceFactory as BaseServiceFactory};
use actix_utils::counter::CounterGuard; use actix_utils::counter::CounterGuard;
use futures_util::future::{err, ok, LocalBoxFuture, Ready}; use futures_core::future::LocalBoxFuture;
use futures_util::{FutureExt, TryFutureExt};
use log::error; use log::error;
use super::Token; use crate::socket::{FromStream, MioStream};
use crate::socket::{FromStream, StdStream}; use crate::{ready, Ready, Token};
pub trait ServiceFactory<Stream: FromStream>: Send + Clone + 'static { pub trait ServiceFactory<Stream: FromStream>: Send + Clone + 'static {
type Factory: BaseServiceFactory<Stream, Config = ()>; type Factory: BaseServiceFactory<Stream, Config = ()>;
@@ -28,7 +26,7 @@ pub(crate) trait InternalServiceFactory: Send {
pub(crate) type BoxedServerService = Box< pub(crate) type BoxedServerService = Box<
dyn Service< dyn Service<
(Option<CounterGuard>, StdStream), (Option<CounterGuard>, MioStream),
Response = (), Response = (),
Error = (), Error = (),
Future = Ready<Result<(), ()>>, Future = Ready<Result<(), ()>>,
@@ -49,7 +47,7 @@ impl<S, I> StreamService<S, I> {
} }
} }
impl<S, I> Service<(Option<CounterGuard>, StdStream)> for StreamService<S, I> impl<S, I> Service<(Option<CounterGuard>, MioStream)> for StreamService<S, I>
where where
S: Service<I>, S: Service<I>,
S::Future: 'static, S::Future: 'static,
@@ -64,21 +62,21 @@ where
self.service.poll_ready(ctx).map_err(|_| ()) self.service.poll_ready(ctx).map_err(|_| ())
} }
fn call(&mut self, (guard, req): (Option<CounterGuard>, StdStream)) -> Self::Future { fn call(&mut self, (guard, req): (Option<CounterGuard>, MioStream)) -> Self::Future {
match FromStream::from_stdstream(req) { ready(match FromStream::from_mio(req) {
Ok(stream) => { Ok(stream) => {
let f = self.service.call(stream); let f = self.service.call(stream);
spawn(async move { actix_rt::spawn(async move {
let _ = f.await; let _ = f.await;
drop(guard); drop(guard);
}); });
ok(()) Ok(())
} }
Err(e) => { Err(e) => {
error!("Can not convert to an async tcp stream: {}", e); error!("Can not convert to an async tcp stream: {}", e);
err(()) Err(())
} }
} })
} }
} }
@@ -132,15 +130,16 @@ where
fn create(&self) -> LocalBoxFuture<'static, Result<Vec<(Token, BoxedServerService)>, ()>> { fn create(&self) -> LocalBoxFuture<'static, Result<Vec<(Token, BoxedServerService)>, ()>> {
let token = self.token; let token = self.token;
self.inner let fut = self.inner.create().new_service(());
.create() Box::pin(async move {
.new_service(()) match fut.await {
.map_err(|_| ()) Ok(inner) => {
.map_ok(move |inner| { let service = Box::new(StreamService::new(inner)) as _;
let service: BoxedServerService = Box::new(StreamService::new(inner)); Ok(vec![(token, service)])
vec![(token, service)] }
}) Err(_) => Err(()),
.boxed_local() }
})
} }
} }

View File

@@ -2,7 +2,7 @@ use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use futures_util::future::lazy; use futures_core::future::LocalBoxFuture;
use crate::server::Server; use crate::server::Server;
@@ -23,48 +23,51 @@ pub(crate) enum Signal {
pub(crate) struct Signals { pub(crate) struct Signals {
srv: Server, srv: Server,
#[cfg(not(unix))] #[cfg(not(unix))]
stream: Pin<Box<dyn Future<Output = std::io::Result<()>>>>, signals: LocalBoxFuture<'static, std::io::Result<()>>,
#[cfg(unix)] #[cfg(unix)]
streams: Vec<(Signal, actix_rt::signal::unix::Signal)>, signals: Vec<(Signal, LocalBoxFuture<'static, ()>)>,
} }
impl Signals { impl Signals {
pub(crate) fn start(srv: Server) { pub(crate) fn start(srv: Server) {
actix_rt::spawn(lazy(|_| { #[cfg(not(unix))]
#[cfg(not(unix))] {
{ actix_rt::spawn(Signals {
actix_rt::spawn(Signals { srv,
srv, signals: Box::pin(actix_rt::signal::ctrl_c()),
stream: Box::pin(actix_rt::signal::ctrl_c()), });
}); }
} #[cfg(unix)]
#[cfg(unix)] {
{ use actix_rt::signal::unix;
use actix_rt::signal::unix;
let mut streams = Vec::new(); let sig_map = [
(unix::SignalKind::interrupt(), Signal::Int),
(unix::SignalKind::hangup(), Signal::Hup),
(unix::SignalKind::terminate(), Signal::Term),
(unix::SignalKind::quit(), Signal::Quit),
];
let sig_map = [ let mut signals = Vec::new();
(unix::SignalKind::interrupt(), Signal::Int),
(unix::SignalKind::hangup(), Signal::Hup),
(unix::SignalKind::terminate(), Signal::Term),
(unix::SignalKind::quit(), Signal::Quit),
];
for (kind, sig) in sig_map.iter() { for (kind, sig) in sig_map.iter() {
match unix::signal(*kind) { match unix::signal(*kind) {
Ok(stream) => streams.push((*sig, stream)), Ok(mut stream) => {
Err(e) => log::error!( let fut = Box::pin(async move {
"Can not initialize stream handler for {:?} err: {}", let _ = stream.recv().await;
sig, }) as _;
e signals.push((*sig, fut));
),
} }
Err(e) => log::error!(
"Can not initialize stream handler for {:?} err: {}",
sig,
e
),
} }
actix_rt::spawn(Signals { srv, streams })
} }
}));
actix_rt::spawn(Signals { srv, signals });
}
} }
} }
@@ -73,25 +76,20 @@ impl Future for Signals {
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))]
match Pin::new(&mut self.stream).poll(cx) { match self.signals.as_mut().poll(cx) {
Poll::Ready(_) => { Poll::Ready(_) => {
self.srv.signal(Signal::Int); self.srv.signal(Signal::Int);
Poll::Ready(()) Poll::Ready(())
} }
Poll::Pending => return Poll::Pending, Poll::Pending => Poll::Pending,
} }
#[cfg(unix)] #[cfg(unix)]
{ {
for idx in 0..self.streams.len() { for (sig, fut) in self.signals.iter_mut() {
loop { if fut.as_mut().poll(cx).is_ready() {
match self.streams[idx].1.poll_recv(cx) { let sig = *sig;
Poll::Ready(None) => return Poll::Ready(()), self.srv.signal(sig);
Poll::Pending => break, return Poll::Ready(());
Poll::Ready(Some(_)) => {
let sig = self.streams[idx].0;
self.srv.signal(sig);
}
}
} }
} }
Poll::Pending Poll::Pending

View File

@@ -1,135 +1,91 @@
use std::{fmt, io, net}; pub(crate) use std::net::{
SocketAddr as StdSocketAddr, TcpListener as StdTcpListener, ToSocketAddrs,
};
pub(crate) use mio::net::{TcpListener as MioTcpListener, TcpSocket as MioTcpSocket};
#[cfg(unix)]
pub(crate) use {
mio::net::UnixListener as MioUnixListener,
std::os::unix::net::UnixListener as StdUnixListener,
};
use std::{fmt, io};
use actix_codec::{AsyncRead, AsyncWrite};
use actix_rt::net::TcpStream; use actix_rt::net::TcpStream;
use mio::event::Source;
use mio::net::TcpStream as MioTcpStream;
use mio::{Interest, Registry, Token};
pub(crate) enum StdListener { #[cfg(windows)]
Tcp(net::TcpListener), use std::os::windows::io::{FromRawSocket, IntoRawSocket};
#[cfg(all(unix))] #[cfg(unix)]
Uds(std::os::unix::net::UnixListener), use {
actix_rt::net::UnixStream,
mio::net::{SocketAddr as MioSocketAddr, UnixStream as MioUnixStream},
std::os::unix::io::{FromRawFd, IntoRawFd},
};
pub(crate) enum MioListener {
Tcp(MioTcpListener),
#[cfg(unix)]
Uds(MioUnixListener),
} }
pub(crate) enum SocketAddr { impl MioListener {
Tcp(net::SocketAddr),
#[cfg(all(unix))]
Uds(std::os::unix::net::SocketAddr),
}
impl fmt::Display for SocketAddr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
SocketAddr::Tcp(ref addr) => write!(f, "{}", addr),
#[cfg(all(unix))]
SocketAddr::Uds(ref addr) => write!(f, "{:?}", addr),
}
}
}
impl fmt::Debug for SocketAddr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
SocketAddr::Tcp(ref addr) => write!(f, "{:?}", addr),
#[cfg(all(unix))]
SocketAddr::Uds(ref addr) => write!(f, "{:?}", addr),
}
}
}
impl fmt::Display for StdListener {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
StdListener::Tcp(ref lst) => write!(f, "{}", lst.local_addr().ok().unwrap()),
#[cfg(all(unix))]
StdListener::Uds(ref lst) => write!(f, "{:?}", lst.local_addr().ok().unwrap()),
}
}
}
impl StdListener {
pub(crate) fn local_addr(&self) -> SocketAddr { pub(crate) fn local_addr(&self) -> SocketAddr {
match self {
StdListener::Tcp(lst) => SocketAddr::Tcp(lst.local_addr().unwrap()),
#[cfg(all(unix))]
StdListener::Uds(lst) => SocketAddr::Uds(lst.local_addr().unwrap()),
}
}
pub(crate) fn into_listener(self) -> SocketListener {
match self {
StdListener::Tcp(lst) => SocketListener::Tcp(
mio::net::TcpListener::from_std(lst)
.expect("Can not create mio::net::TcpListener"),
),
#[cfg(all(unix))]
StdListener::Uds(lst) => SocketListener::Uds(
mio_uds::UnixListener::from_listener(lst)
.expect("Can not create mio_uds::UnixListener"),
),
}
}
}
#[derive(Debug)]
pub enum StdStream {
Tcp(std::net::TcpStream),
#[cfg(all(unix))]
Uds(std::os::unix::net::UnixStream),
}
pub(crate) enum SocketListener {
Tcp(mio::net::TcpListener),
#[cfg(all(unix))]
Uds(mio_uds::UnixListener),
}
impl SocketListener {
pub(crate) fn accept(&self) -> io::Result<Option<(StdStream, SocketAddr)>> {
match *self { match *self {
SocketListener::Tcp(ref lst) => lst MioListener::Tcp(ref lst) => SocketAddr::Tcp(lst.local_addr().unwrap()),
.accept_std() #[cfg(unix)]
.map(|(stream, addr)| Some((StdStream::Tcp(stream), SocketAddr::Tcp(addr)))), MioListener::Uds(ref lst) => SocketAddr::Uds(lst.local_addr().unwrap()),
#[cfg(all(unix))] }
SocketListener::Uds(ref lst) => lst.accept_std().map(|res| { }
res.map(|(stream, addr)| (StdStream::Uds(stream), SocketAddr::Uds(addr)))
}), pub(crate) fn accept(&self) -> io::Result<Option<(MioStream, SocketAddr)>> {
match *self {
MioListener::Tcp(ref lst) => lst
.accept()
.map(|(stream, addr)| Some((MioStream::Tcp(stream), SocketAddr::Tcp(addr)))),
#[cfg(unix)]
MioListener::Uds(ref lst) => lst
.accept()
.map(|(stream, addr)| Some((MioStream::Uds(stream), SocketAddr::Uds(addr)))),
} }
} }
} }
impl mio::Evented for SocketListener { impl Source for MioListener {
fn register( fn register(
&self, &mut self,
poll: &mio::Poll, registry: &Registry,
token: mio::Token, token: Token,
interest: mio::Ready, interests: Interest,
opts: mio::PollOpt,
) -> io::Result<()> { ) -> io::Result<()> {
match *self { match *self {
SocketListener::Tcp(ref lst) => lst.register(poll, token, interest, opts), MioListener::Tcp(ref mut lst) => lst.register(registry, token, interests),
#[cfg(all(unix))] #[cfg(unix)]
SocketListener::Uds(ref lst) => lst.register(poll, token, interest, opts), MioListener::Uds(ref mut lst) => lst.register(registry, token, interests),
} }
} }
fn reregister( fn reregister(
&self, &mut self,
poll: &mio::Poll, registry: &Registry,
token: mio::Token, token: Token,
interest: mio::Ready, interests: Interest,
opts: mio::PollOpt,
) -> io::Result<()> { ) -> io::Result<()> {
match *self { match *self {
SocketListener::Tcp(ref lst) => lst.reregister(poll, token, interest, opts), MioListener::Tcp(ref mut lst) => lst.reregister(registry, token, interests),
#[cfg(all(unix))] #[cfg(unix)]
SocketListener::Uds(ref lst) => lst.reregister(poll, token, interest, opts), MioListener::Uds(ref mut lst) => lst.reregister(registry, token, interests),
} }
} }
fn deregister(&self, poll: &mio::Poll) -> io::Result<()> {
fn deregister(&mut self, registry: &Registry) -> io::Result<()> {
match *self { match *self {
SocketListener::Tcp(ref lst) => lst.deregister(poll), MioListener::Tcp(ref mut lst) => lst.deregister(registry),
#[cfg(all(unix))] #[cfg(unix)]
SocketListener::Uds(ref lst) => { MioListener::Uds(ref mut lst) => {
let res = lst.deregister(poll); let res = lst.deregister(registry);
// cleanup file path // cleanup file path
if let Ok(addr) = lst.local_addr() { if let Ok(addr) = lst.local_addr() {
@@ -143,28 +99,156 @@ impl mio::Evented for SocketListener {
} }
} }
pub trait FromStream: AsyncRead + AsyncWrite + Sized { impl From<StdTcpListener> for MioListener {
fn from_stdstream(sock: StdStream) -> io::Result<Self>; fn from(lst: StdTcpListener) -> Self {
MioListener::Tcp(MioTcpListener::from_std(lst))
}
} }
impl FromStream for TcpStream { #[cfg(unix)]
fn from_stdstream(sock: StdStream) -> io::Result<Self> { impl From<StdUnixListener> for MioListener {
match sock { fn from(lst: StdUnixListener) -> Self {
StdStream::Tcp(stream) => TcpStream::from_std(stream), MioListener::Uds(MioUnixListener::from_std(lst))
}
}
impl fmt::Debug for MioListener {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
MioListener::Tcp(ref lst) => write!(f, "{:?}", lst),
#[cfg(all(unix))] #[cfg(all(unix))]
StdStream::Uds(_) => { MioListener::Uds(ref lst) => write!(f, "{:?}", lst),
}
}
}
impl fmt::Display for MioListener {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
MioListener::Tcp(ref lst) => write!(f, "{}", lst.local_addr().ok().unwrap()),
#[cfg(unix)]
MioListener::Uds(ref lst) => write!(f, "{:?}", lst.local_addr().ok().unwrap()),
}
}
}
pub(crate) enum SocketAddr {
Tcp(StdSocketAddr),
#[cfg(unix)]
Uds(MioSocketAddr),
}
impl fmt::Display for SocketAddr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
SocketAddr::Tcp(ref addr) => write!(f, "{}", addr),
#[cfg(unix)]
SocketAddr::Uds(ref addr) => write!(f, "{:?}", addr),
}
}
}
impl fmt::Debug for SocketAddr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
SocketAddr::Tcp(ref addr) => write!(f, "{:?}", addr),
#[cfg(unix)]
SocketAddr::Uds(ref addr) => write!(f, "{:?}", addr),
}
}
}
#[derive(Debug)]
pub enum MioStream {
Tcp(MioTcpStream),
#[cfg(unix)]
Uds(MioUnixStream),
}
/// helper trait for converting mio stream to tokio stream.
pub trait FromStream: Sized {
fn from_mio(sock: MioStream) -> io::Result<Self>;
}
// FIXME: This is a workaround and we need an efficient way to convert between mio and tokio stream
#[cfg(unix)]
impl FromStream for TcpStream {
fn from_mio(sock: MioStream) -> io::Result<Self> {
match sock {
MioStream::Tcp(mio) => {
let raw = IntoRawFd::into_raw_fd(mio);
// SAFETY: This is a in place conversion from mio stream to tokio stream.
TcpStream::from_std(unsafe { FromRawFd::from_raw_fd(raw) })
}
MioStream::Uds(_) => {
panic!("Should not happen, bug in server impl"); panic!("Should not happen, bug in server impl");
} }
} }
} }
} }
#[cfg(all(unix))] // FIXME: This is a workaround and we need an efficient way to convert between mio and tokio stream
impl FromStream for actix_rt::net::UnixStream { #[cfg(windows)]
fn from_stdstream(sock: StdStream) -> io::Result<Self> { impl FromStream for TcpStream {
fn from_mio(sock: MioStream) -> io::Result<Self> {
match sock { match sock {
StdStream::Tcp(_) => panic!("Should not happen, bug in server impl"), MioStream::Tcp(mio) => {
StdStream::Uds(stream) => actix_rt::net::UnixStream::from_std(stream), let raw = IntoRawSocket::into_raw_socket(mio);
// SAFETY: This is a in place conversion from mio stream to tokio stream.
TcpStream::from_std(unsafe { FromRawSocket::from_raw_socket(raw) })
}
}
}
}
// FIXME: This is a workaround and we need an efficient way to convert between mio and tokio stream
#[cfg(unix)]
impl FromStream for UnixStream {
fn from_mio(sock: MioStream) -> io::Result<Self> {
match sock {
MioStream::Tcp(_) => panic!("Should not happen, bug in server impl"),
MioStream::Uds(mio) => {
let raw = IntoRawFd::into_raw_fd(mio);
// SAFETY: This is a in place conversion from mio stream to tokio stream.
UnixStream::from_std(unsafe { FromRawFd::from_raw_fd(raw) })
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn socket_addr() {
let addr = SocketAddr::Tcp("127.0.0.1:8080".parse().unwrap());
assert!(format!("{:?}", addr).contains("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 socket = MioTcpSocket::new_v4().unwrap();
socket.set_reuseaddr(true).unwrap();
socket.bind(addr).unwrap();
let tcp = socket.listen(128).unwrap();
let lst = MioListener::Tcp(tcp);
assert!(format!("{:?}", lst).contains("TcpListener"));
assert!(format!("{}", lst).contains("127.0.0.1"));
}
#[test]
#[cfg(unix)]
fn uds() {
let _ = std::fs::remove_file("/tmp/sock.xxxxx");
if let Ok(socket) = MioUnixListener::bind("/tmp/sock.xxxxx") {
let addr = socket.local_addr().expect("Couldn't get local address");
let a = SocketAddr::Uds(addr);
assert!(format!("{:?}", a).contains("/tmp/sock.xxxxx"));
assert!(format!("{}", a).contains("/tmp/sock.xxxxx"));
let lst = MioListener::Uds(socket);
assert!(format!("{:?}", lst).contains("/tmp/sock.xxxxx"));
assert!(format!("{}", lst).contains("/tmp/sock.xxxxx"));
} }
} }
} }

View File

@@ -1,19 +1,9 @@
//! Various helpers for Actix applications to use during testing.
#![deny(rust_2018_idioms, nonstandard_style)]
#![allow(clippy::type_complexity, clippy::needless_doctest_main)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
use std::sync::mpsc; use std::sync::mpsc;
use std::{net, thread}; use std::{net, thread};
use actix_rt::{net::TcpStream, System}; use actix_rt::{net::TcpStream, System};
use actix_server::{Server, ServerBuilder, ServiceFactory};
use socket2::{Domain, Protocol, Socket, Type};
#[cfg(not(test))] // Work around for rust-lang/rust#62127 use crate::{Server, ServerBuilder, ServiceFactory};
pub use actix_macros::test;
/// The `TestServer` type. /// The `TestServer` type.
/// ///
@@ -24,7 +14,7 @@ pub use actix_macros::test;
/// ///
/// ```rust /// ```rust
/// use actix_service::fn_service; /// use actix_service::fn_service;
/// use actix_testing::TestServer; /// use actix_server::TestServer;
/// ///
/// #[actix_rt::main] /// #[actix_rt::main]
/// async fn main() { /// async fn main() {
@@ -83,7 +73,7 @@ impl TestServer {
// run server in separate thread // run server in separate thread
thread::spawn(move || { thread::spawn(move || {
let mut sys = System::new("actix-test-server"); let sys = System::new("actix-test-server");
let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap();
let local_addr = tcp.local_addr().unwrap(); let local_addr = tcp.local_addr().unwrap();
@@ -94,9 +84,8 @@ impl TestServer {
.workers(1) .workers(1)
.disable_signals() .disable_signals()
.start(); .start();
tx.send((System::current(), local_addr)).unwrap();
}); });
tx.send((System::current(), local_addr)).unwrap();
sys.run() sys.run()
}); });
@@ -116,11 +105,10 @@ 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 {
let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
let socket = let socket = mio::net::TcpSocket::new_v4().unwrap();
Socket::new(Domain::ipv4(), Type::stream(), Some(Protocol::tcp())).unwrap(); socket.bind(addr).unwrap();
socket.bind(&addr.into()).unwrap(); socket.set_reuseaddr(true).unwrap();
socket.set_reuse_address(true).unwrap(); let tcp = socket.listen(1024).unwrap();
let tcp = socket.into_tcp_listener();
tcp.local_addr().unwrap() tcp.local_addr().unwrap()
} }
} }

View File

@@ -0,0 +1,89 @@
use std::{
collections::VecDeque,
ops::Deref,
sync::{Arc, Mutex, MutexGuard},
};
use mio::{Registry, Token as MioToken, Waker};
use crate::worker::WorkerHandle;
/// waker token for `mio::Poll` instance
pub(crate) const WAKER_TOKEN: MioToken = MioToken(usize::MAX);
/// `mio::Waker` with a queue for waking up the `Accept`'s `Poll` and contains the `WakerInterest`
/// the `Poll` would want to look into.
pub(crate) struct WakerQueue(Arc<(Waker, Mutex<VecDeque<WakerInterest>>)>);
impl Clone for WakerQueue {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl Deref for WakerQueue {
type Target = (Waker, Mutex<VecDeque<WakerInterest>>);
fn deref(&self) -> &Self::Target {
self.0.deref()
}
}
impl WakerQueue {
/// construct a waker queue with given `Poll`'s `Registry` and capacity.
///
/// A fixed `WAKER_TOKEN` is used to identify the wake interest and the `Poll` needs to match
/// event's token for it to properly handle `WakerInterest`.
pub(crate) fn new(registry: &Registry) -> std::io::Result<Self> {
let waker = Waker::new(registry, WAKER_TOKEN)?;
let queue = Mutex::new(VecDeque::with_capacity(16));
Ok(Self(Arc::new((waker, queue))))
}
/// push a new interest to the queue and wake up the accept poll afterwards.
pub(crate) fn wake(&self, interest: WakerInterest) {
let (waker, queue) = self.deref();
queue
.lock()
.expect("Failed to lock WakerQueue")
.push_back(interest);
waker
.wake()
.unwrap_or_else(|e| panic!("can not wake up Accept Poll: {}", e));
}
/// get a MutexGuard of the waker queue.
pub(crate) fn guard(&self) -> MutexGuard<'_, VecDeque<WakerInterest>> {
self.deref().1.lock().expect("Failed to lock WakerQueue")
}
/// reset the waker queue so it does not grow infinitely.
pub(crate) fn reset(queue: &mut VecDeque<WakerInterest>) {
std::mem::swap(&mut VecDeque::<WakerInterest>::with_capacity(16), queue);
}
}
/// types of interests we would look into when `Accept`'s `Poll` is waked up by waker.
///
/// *. These interests should not be confused with `mio::Interest` and mostly not I/O related
pub(crate) enum WakerInterest {
/// `WorkerAvailable` is an interest from `Worker` notifying `Accept` there is a worker
/// available and can accept new tasks.
WorkerAvailable,
/// `Pause`, `Resume`, `Stop` Interest are from `ServerBuilder` future. It listens to
/// `ServerCommand` and notify `Accept` to do exactly these tasks.
Pause,
Resume,
Stop,
/// `Timer` is an interest sent as a delayed future. When an error happens on accepting
/// connection `Accept` would deregister socket listener temporary and wake up the poll and
/// register them again after the delayed future resolve.
Timer,
/// `Worker` is an interest happen after a worker runs into faulted state(This is determined
/// by if work can be sent to it successfully).`Accept` would be waked up and add the new
/// `WorkerHandle`.
Worker(WorkerHandle),
}

View File

@@ -1,22 +1,22 @@
use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc; use std::sync::Arc;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use std::time; use std::time::Duration;
use actix_rt::time::{delay_until, Delay, Instant}; use actix_rt::time::{sleep_until, Instant, Sleep};
use actix_rt::{spawn, Arbiter}; use actix_rt::{spawn, Arbiter};
use actix_utils::counter::Counter; use actix_utils::counter::Counter;
use futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}; use futures_core::future::LocalBoxFuture;
use futures_channel::oneshot;
use futures_util::future::{join_all, LocalBoxFuture, MapOk};
use futures_util::{future::Future, stream::Stream, FutureExt, TryFutureExt};
use log::{error, info, trace}; use log::{error, info, trace};
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
use tokio::sync::oneshot;
use crate::accept::AcceptNotify;
use crate::service::{BoxedServerService, InternalServiceFactory}; use crate::service::{BoxedServerService, InternalServiceFactory};
use crate::socket::{SocketAddr, StdStream}; use crate::socket::{MioStream, SocketAddr};
use crate::Token; use crate::waker_queue::{WakerInterest, WakerQueue};
use crate::{join_all, Token};
pub(crate) struct WorkerCommand(Conn); pub(crate) struct WorkerCommand(Conn);
@@ -29,7 +29,7 @@ pub(crate) struct StopCommand {
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct Conn { pub(crate) struct Conn {
pub io: StdStream, pub io: MioStream,
pub token: Token, pub token: Token,
pub peer: Option<SocketAddr>, pub peer: Option<SocketAddr>,
} }
@@ -46,31 +46,33 @@ pub fn max_concurrent_connections(num: usize) {
MAX_CONNS.store(num, Ordering::Relaxed); MAX_CONNS.store(num, Ordering::Relaxed);
} }
pub(crate) fn num_connections() -> usize {
MAX_CONNS_COUNTER.with(|conns| conns.total())
}
thread_local! { thread_local! {
static MAX_CONNS_COUNTER: Counter = static MAX_CONNS_COUNTER: Counter =
Counter::new(MAX_CONNS.load(Ordering::Relaxed)); Counter::new(MAX_CONNS.load(Ordering::Relaxed));
} }
pub(crate) fn num_connections() -> usize {
MAX_CONNS_COUNTER.with(|conns| conns.total())
}
// a handle to worker that can send message to worker and share the availability of worker to other
// thread.
#[derive(Clone)] #[derive(Clone)]
pub(crate) struct WorkerClient { pub(crate) struct WorkerHandle {
pub idx: usize, pub idx: usize,
tx1: UnboundedSender<WorkerCommand>, tx1: UnboundedSender<WorkerCommand>,
tx2: UnboundedSender<StopCommand>, tx2: UnboundedSender<StopCommand>,
avail: WorkerAvailability, avail: WorkerAvailability,
} }
impl WorkerClient { impl WorkerHandle {
pub fn new( pub fn new(
idx: usize, idx: usize,
tx1: UnboundedSender<WorkerCommand>, tx1: UnboundedSender<WorkerCommand>,
tx2: UnboundedSender<StopCommand>, tx2: UnboundedSender<StopCommand>,
avail: WorkerAvailability, avail: WorkerAvailability,
) -> Self { ) -> Self {
WorkerClient { WorkerHandle {
idx, idx,
tx1, tx1,
tx2, tx2,
@@ -79,9 +81,7 @@ impl WorkerClient {
} }
pub fn send(&self, msg: Conn) -> Result<(), Conn> { pub fn send(&self, msg: Conn) -> Result<(), Conn> {
self.tx1 self.tx1.send(WorkerCommand(msg)).map_err(|msg| msg.0 .0)
.unbounded_send(WorkerCommand(msg))
.map_err(|msg| msg.into_inner().0)
} }
pub fn available(&self) -> bool { pub fn available(&self) -> bool {
@@ -90,21 +90,21 @@ impl WorkerClient {
pub fn stop(&self, graceful: bool) -> oneshot::Receiver<bool> { pub fn stop(&self, graceful: bool) -> oneshot::Receiver<bool> {
let (result, rx) = oneshot::channel(); let (result, rx) = oneshot::channel();
let _ = self.tx2.unbounded_send(StopCommand { graceful, result }); let _ = self.tx2.send(StopCommand { graceful, result });
rx rx
} }
} }
#[derive(Clone)] #[derive(Clone)]
pub(crate) struct WorkerAvailability { pub(crate) struct WorkerAvailability {
notify: AcceptNotify, waker: WakerQueue,
available: Arc<AtomicBool>, available: Arc<AtomicBool>,
} }
impl WorkerAvailability { impl WorkerAvailability {
pub fn new(notify: AcceptNotify) -> Self { pub fn new(waker: WakerQueue) -> Self {
WorkerAvailability { WorkerAvailability {
notify, waker,
available: Arc::new(AtomicBool::new(false)), available: Arc::new(AtomicBool::new(false)),
} }
} }
@@ -115,8 +115,9 @@ impl WorkerAvailability {
pub fn set(&self, val: bool) { pub fn set(&self, val: bool) {
let old = self.available.swap(val, Ordering::Release); let old = self.available.swap(val, Ordering::Release);
// notify the accept on switched to available.
if !old && val { if !old && val {
self.notify.notify() self.waker.wake(WakerInterest::WorkerAvailable);
} }
} }
} }
@@ -133,7 +134,7 @@ pub(crate) struct Worker {
conns: Counter, conns: Counter,
factories: Vec<Box<dyn InternalServiceFactory>>, factories: Vec<Box<dyn InternalServiceFactory>>,
state: WorkerState, state: WorkerState,
shutdown_timeout: time::Duration, shutdown_timeout: Duration,
} }
struct WorkerService { struct WorkerService {
@@ -164,63 +165,65 @@ impl Worker {
idx: usize, idx: usize,
factories: Vec<Box<dyn InternalServiceFactory>>, factories: Vec<Box<dyn InternalServiceFactory>>,
availability: WorkerAvailability, availability: WorkerAvailability,
shutdown_timeout: time::Duration, shutdown_timeout: Duration,
) -> WorkerClient { ) -> WorkerHandle {
let (tx1, rx) = unbounded(); let (tx1, rx) = unbounded_channel();
let (tx2, rx2) = unbounded(); let (tx2, rx2) = unbounded_channel();
let avail = availability.clone(); let avail = availability.clone();
Arbiter::new().send( // every worker runs in it's own arbiter.
async move { Arbiter::new().send(Box::pin(async move {
availability.set(false); availability.set(false);
let mut wrk = MAX_CONNS_COUNTER.with(move |conns| Worker { let mut wrk = MAX_CONNS_COUNTER.with(move |conns| Worker {
rx, rx,
rx2, rx2,
availability, availability,
factories, factories,
shutdown_timeout, shutdown_timeout,
services: Vec::new(), services: Vec::new(),
conns: conns.clone(), conns: conns.clone(),
state: WorkerState::Unavailable(Vec::new()), state: WorkerState::Unavailable,
}); });
let mut fut: Vec<MapOk<LocalBoxFuture<'static, _>, _>> = Vec::new(); let fut = wrk
for (idx, factory) in wrk.factories.iter().enumerate() { .factories
fut.push(factory.create().map_ok(move |r| { .iter()
r.into_iter() .enumerate()
.map(|(t, s): (Token, _)| (idx, t, s)) .map(|(idx, factory)| {
.collect::<Vec<_>>() let fut = factory.create();
})); async move {
} fut.await.map(|r| {
r.into_iter().map(|(t, s)| (idx, t, s)).collect::<Vec<_>>()
})
}
})
.collect::<Vec<_>>();
spawn(async move { spawn(async move {
let res = join_all(fut).await; let res: Result<Vec<_>, _> = join_all(fut).await.into_iter().collect();
let res: Result<Vec<_>, _> = res.into_iter().collect(); match res {
match res { Ok(services) => {
Ok(services) => { for item in services {
for item in services { for (factory, token, service) in item {
for (factory, token, service) in item { assert_eq!(token.0, wrk.services.len());
assert_eq!(token.0, wrk.services.len()); wrk.services.push(WorkerService {
wrk.services.push(WorkerService { factory,
factory, service,
service, status: WorkerServiceStatus::Unavailable,
status: WorkerServiceStatus::Unavailable, });
});
}
} }
} }
Err(e) => {
error!("Can not start worker: {:?}", e);
Arbiter::current().stop();
}
} }
wrk.await Err(e) => {
}); error!("Can not start worker: {:?}", e);
} Arbiter::current().stop();
.boxed(), }
); }
wrk.await
});
}));
WorkerClient::new(idx, tx1, tx2, avail) WorkerHandle::new(idx, tx1, tx2, avail)
} }
fn shutdown(&mut self, force: bool) { fn shutdown(&mut self, force: bool) {
@@ -242,7 +245,7 @@ impl Worker {
fn check_readiness(&mut self, cx: &mut Context<'_>) -> Result<bool, (Token, usize)> { fn check_readiness(&mut self, cx: &mut Context<'_>) -> Result<bool, (Token, usize)> {
let mut ready = self.conns.available(cx); let mut ready = self.conns.available(cx);
let mut failed = None; let mut failed = None;
for (idx, srv) in &mut self.services.iter_mut().enumerate() { for (idx, srv) in self.services.iter_mut().enumerate() {
if srv.status == WorkerServiceStatus::Available if srv.status == WorkerServiceStatus::Available
|| srv.status == WorkerServiceStatus::Unavailable || srv.status == WorkerServiceStatus::Unavailable
{ {
@@ -288,16 +291,15 @@ impl Worker {
enum WorkerState { enum WorkerState {
Available, Available,
Unavailable(Vec<Conn>), Unavailable,
Restarting( Restarting(
usize, usize,
Token, Token,
#[allow(clippy::type_complexity)] LocalBoxFuture<'static, Result<Vec<(Token, BoxedServerService)>, ()>>,
Pin<Box<dyn Future<Output = Result<Vec<(Token, BoxedServerService)>, ()>>>>,
), ),
Shutdown( Shutdown(
Pin<Box<Delay>>, Pin<Box<Sleep>>,
Pin<Box<Delay>>, Pin<Box<Sleep>>,
Option<oneshot::Sender<bool>>, Option<oneshot::Sender<bool>>,
), ),
} }
@@ -305,12 +307,10 @@ enum WorkerState {
impl Future for Worker { impl Future for Worker {
type Output = (); type Output = ();
// FIXME: remove this attribute
#[allow(clippy::never_loop)]
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> {
// `StopWorker` message handler // `StopWorker` message handler
if let Poll::Ready(Some(StopCommand { graceful, result })) = if let Poll::Ready(Some(StopCommand { graceful, result })) =
Pin::new(&mut self.rx2).poll_next(cx) Pin::new(&mut self.rx2).poll_recv(cx)
{ {
self.availability.set(false); self.availability.set(false);
let num = num_connections(); let num = num_connections();
@@ -324,8 +324,8 @@ impl Future for Worker {
if num != 0 { if num != 0 {
info!("Graceful worker shutdown, {} connections", num); info!("Graceful worker shutdown, {} connections", num);
self.state = WorkerState::Shutdown( self.state = WorkerState::Shutdown(
Box::pin(delay_until(Instant::now() + time::Duration::from_secs(1))), Box::pin(sleep_until(Instant::now() + Duration::from_secs(1))),
Box::pin(delay_until(Instant::now() + self.shutdown_timeout)), Box::pin(sleep_until(Instant::now() + self.shutdown_timeout)),
Some(result), Some(result),
); );
} else { } else {
@@ -341,53 +341,35 @@ impl Future for Worker {
} }
match self.state { match self.state {
WorkerState::Unavailable(ref mut conns) => { WorkerState::Unavailable => match self.check_readiness(cx) {
let conn = conns.pop(); Ok(true) => {
match self.check_readiness(cx) { self.state = WorkerState::Available;
Ok(true) => { self.availability.set(true);
// process requests from wait queue self.poll(cx)
if let Some(conn) = conn {
let guard = self.conns.get();
let _ = self.services[conn.token.0]
.service
.call((Some(guard), conn.io));
} else {
self.state = WorkerState::Available;
self.availability.set(true);
}
self.poll(cx)
}
Ok(false) => {
// push connection back to queue
if let Some(conn) = conn {
if let WorkerState::Unavailable(ref mut conns) = self.state {
conns.push(conn);
}
}
Poll::Pending
}
Err((token, idx)) => {
trace!(
"Service {:?} failed, restarting",
self.factories[idx].name(token)
);
self.services[token.0].status = WorkerServiceStatus::Restarting;
self.state =
WorkerState::Restarting(idx, token, self.factories[idx].create());
self.poll(cx)
}
} }
} Ok(false) => Poll::Pending,
Err((token, idx)) => {
trace!(
"Service {:?} failed, restarting",
self.factories[idx].name(token)
);
self.services[token.0].status = WorkerServiceStatus::Restarting;
self.state =
WorkerState::Restarting(idx, token, self.factories[idx].create());
self.poll(cx)
}
},
WorkerState::Restarting(idx, token, ref mut fut) => { WorkerState::Restarting(idx, token, ref mut fut) => {
match Pin::new(fut).poll(cx) { match fut.as_mut().poll(cx) {
Poll::Ready(Ok(item)) => { Poll::Ready(Ok(item)) => {
for (token, service) in item { // only interest in the first item?
if let Some((token, service)) = item.into_iter().next() {
trace!( trace!(
"Service {:?} has been restarted", "Service {:?} has been restarted",
self.factories[idx].name(token) self.factories[idx].name(token)
); );
self.services[token.0].created(service); self.services[token.0].created(service);
self.state = WorkerState::Unavailable(Vec::new()); self.state = WorkerState::Unavailable;
return self.poll(cx); return self.poll(cx);
} }
} }
@@ -397,9 +379,7 @@ impl Future for Worker {
self.factories[idx].name(token) self.factories[idx].name(token)
); );
} }
Poll::Pending => { Poll::Pending => return Poll::Pending,
return Poll::Pending;
}
} }
self.poll(cx) self.poll(cx)
} }
@@ -412,71 +392,56 @@ impl Future for Worker {
} }
// check graceful timeout // check graceful timeout
match t2.as_mut().poll(cx) { if Pin::new(t2).poll(cx).is_ready() {
Poll::Pending => (), let _ = tx.take().unwrap().send(false);
Poll::Ready(_) => { self.shutdown(true);
let _ = tx.take().unwrap().send(false); Arbiter::current().stop();
self.shutdown(true); return Poll::Ready(());
Arbiter::current().stop();
return Poll::Ready(());
}
} }
// sleep for 1 second and then check again // sleep for 1 second and then check again
match t1.as_mut().poll(cx) { if t1.as_mut().poll(cx).is_ready() {
Poll::Pending => (), *t1 = Box::pin(sleep_until(Instant::now() + Duration::from_secs(1)));
Poll::Ready(_) => { let _ = t1.as_mut().poll(cx);
*t1 = Box::pin(delay_until(
Instant::now() + time::Duration::from_secs(1),
));
let _ = t1.as_mut().poll(cx);
}
} }
Poll::Pending Poll::Pending
} }
WorkerState::Available => { // actively poll stream and handle worker command
loop { WorkerState::Available => loop {
match Pin::new(&mut self.rx).poll_next(cx) { match self.check_readiness(cx) {
// handle incoming io stream Ok(true) => (),
Poll::Ready(Some(WorkerCommand(msg))) => { Ok(false) => {
match self.check_readiness(cx) { trace!("Worker is unavailable");
Ok(true) => { self.availability.set(false);
let guard = self.conns.get(); self.state = WorkerState::Unavailable;
let _ = self.services[msg.token.0] return self.poll(cx);
.service }
.call((Some(guard), msg.io)); Err((token, idx)) => {
continue; trace!(
} "Service {:?} failed, restarting",
Ok(false) => { self.factories[idx].name(token)
trace!("Worker is unavailable"); );
self.availability.set(false); self.availability.set(false);
self.state = WorkerState::Unavailable(vec![msg]); self.services[token.0].status = WorkerServiceStatus::Restarting;
} self.state =
Err((token, idx)) => { WorkerState::Restarting(idx, token, self.factories[idx].create());
trace!( return self.poll(cx);
"Service {:?} failed, restarting",
self.factories[idx].name(token)
);
self.availability.set(false);
self.services[token.0].status =
WorkerServiceStatus::Restarting;
self.state = WorkerState::Restarting(
idx,
token,
self.factories[idx].create(),
);
}
}
return self.poll(cx);
}
Poll::Pending => {
self.state = WorkerState::Available;
return Poll::Pending;
}
Poll::Ready(None) => return Poll::Ready(()),
} }
} }
}
match Pin::new(&mut self.rx).poll_recv(cx) {
// handle incoming io stream
Poll::Ready(Some(WorkerCommand(msg))) => {
let guard = self.conns.get();
let _ = self.services[msg.token.0]
.service
.call((Some(guard), msg.io));
}
Poll::Pending => return Poll::Pending,
Poll::Ready(None) => return Poll::Ready(()),
};
},
} }
} }
} }

View File

@@ -5,14 +5,13 @@ use std::{net, thread, time};
use actix_server::Server; use actix_server::Server;
use actix_service::fn_service; use actix_service::fn_service;
use futures_util::future::{lazy, ok}; use futures_util::future::{lazy, ok};
use socket2::{Domain, Protocol, Socket, Type};
fn unused_addr() -> net::SocketAddr { fn unused_addr() -> net::SocketAddr {
let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
let socket = Socket::new(Domain::ipv4(), Type::stream(), Some(Protocol::tcp())).unwrap(); let socket = mio::net::TcpSocket::new_v4().unwrap();
socket.bind(&addr.into()).unwrap(); socket.bind(addr).unwrap();
socket.set_reuse_address(true).unwrap(); socket.set_reuseaddr(true).unwrap();
let tcp = socket.into_tcp_listener(); let tcp = socket.listen(32).unwrap();
tcp.local_addr().unwrap() tcp.local_addr().unwrap()
} }
@@ -22,8 +21,7 @@ fn test_bind() {
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
let h = thread::spawn(move || { let h = thread::spawn(move || {
let mut sys = actix_rt::System::new("test"); let sys = actix_rt::System::new("test");
let srv = sys.block_on(lazy(|_| { let srv = sys.block_on(lazy(|_| {
Server::build() Server::build()
.workers(1) .workers(1)
@@ -49,17 +47,17 @@ fn test_listen() {
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
let h = thread::spawn(move || { let h = thread::spawn(move || {
let mut sys = actix_rt::System::new("test"); let sys = actix_rt::System::new("test");
let lst = net::TcpListener::bind(addr).unwrap(); let lst = net::TcpListener::bind(addr).unwrap();
sys.block_on(lazy(|_| { sys.block_on(async {
Server::build() Server::build()
.disable_signals() .disable_signals()
.workers(1) .workers(1)
.listen("test", lst, move || fn_service(|_| ok::<_, ()>(()))) .listen("test", lst, move || fn_service(|_| ok::<_, ()>(())))
.unwrap() .unwrap()
.start() .start();
})); let _ = tx.send(actix_rt::System::current());
let _ = tx.send(actix_rt::System::current()); });
let _ = sys.run(); let _ = sys.run();
}); });
let sys = rx.recv().unwrap(); let sys = rx.recv().unwrap();
@@ -83,7 +81,7 @@ fn test_start() {
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
let h = thread::spawn(move || { let h = thread::spawn(move || {
let mut sys = actix_rt::System::new("test"); let sys = actix_rt::System::new("test");
let srv = sys.block_on(lazy(|_| { let srv = sys.block_on(lazy(|_| {
Server::build() Server::build()
.backlog(100) .backlog(100)
@@ -102,6 +100,7 @@ fn test_start() {
let _ = tx.send((srv, actix_rt::System::current())); let _ = tx.send((srv, actix_rt::System::current()));
let _ = sys.run(); let _ = sys.run();
}); });
let (srv, sys) = rx.recv().unwrap(); let (srv, sys) = rx.recv().unwrap();
let mut buf = [1u8; 4]; let mut buf = [1u8; 4];
@@ -151,7 +150,7 @@ fn test_configure() {
let h = thread::spawn(move || { let h = thread::spawn(move || {
let num = num2.clone(); let num = num2.clone();
let mut sys = actix_rt::System::new("test"); let sys = actix_rt::System::new("test");
let srv = sys.block_on(lazy(|_| { let srv = sys.block_on(lazy(|_| {
Server::build() Server::build()
.disable_signals() .disable_signals()

View File

@@ -1,6 +1,10 @@
# Changes # Changes
## Unreleased - 2020-xx-xx ## Unreleased - 2021-xx-xx
## 2.0.0-beta.2 - 2021-01-03
* Remove redundant type parameter from `map_config`.
## 2.0.0-beta.1 - 2020-12-28 ## 2.0.0-beta.1 - 2020-12-28

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-service" name = "actix-service"
version = "2.0.0-beta.1" version = "2.0.0-beta.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

@@ -6,7 +6,7 @@ use super::{IntoServiceFactory, ServiceFactory};
/// ///
/// Note that this function consumes the receiving service factory and returns /// Note that this function consumes the receiving service factory and returns
/// a wrapped version of it. /// a wrapped version of it.
pub fn map_config<I, SF, S, Req, F, Cfg>(factory: I, f: F) -> MapConfig<SF, Req, F, Cfg> pub fn map_config<I, SF, Req, F, Cfg>(factory: I, f: F) -> MapConfig<SF, Req, F, Cfg>
where where
I: IntoServiceFactory<SF, Req>, I: IntoServiceFactory<SF, Req>,
SF: ServiceFactory<Req>, SF: ServiceFactory<Req>,

View File

@@ -1,33 +0,0 @@
# Changes
## [1.0.1] - 2020-05-19
* Replace deprecated `net2` crate with `socket2`
* Remove unused `futures` dependency
## [1.0.0] - 2019-12-11
* Update actix-server to 1.0.0
## [1.0.0-alpha.3] - 2019-12-07
* Migrate to tokio 0.2
## [1.0.0-alpha.2] - 2019-12-02
* Re-export `test` attribute macros
## [0.3.0-alpha.1] - 2019-11-22
* Migrate to std::future
## [0.2.0] - 2019-10-14
* Upgrade actix-server and actix-server-config deps
## [0.1.0] - 2019-09-25
* Initial impl

View File

@@ -1,27 +0,0 @@
[package]
name = "actix-testing"
version = "1.0.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix testing utils"
keywords = ["network", "framework", "async", "futures"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-testing/"
categories = ["network-programming", "asynchronous"]
license = "MIT OR Apache-2.0"
edition = "2018"
workspace = ".."
readme = "README.md"
[lib]
name = "actix_testing"
path = "src/lib.rs"
[dependencies]
actix-rt = "1.0.0"
actix-macros = "0.1.0"
actix-server = "1.0.0"
actix-service = "1.0.0"
log = "0.4"
socket2 = "0.3"

View File

@@ -1,9 +0,0 @@
# Actix test utilities [![crates.io](https://meritbadge.herokuapp.com/actix-testing)](https://crates.io/crates/actix-testint) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
## Documentation & community resources
* [User Guide](https://actix.rs/docs/)
* [API Documentation](https://docs.rs/actix-testing/)
* [Chat on gitter](https://gitter.im/actix/actix)
* Cargo package: [actix-http-test](https://crates.io/crates/actix-testing)
* Minimum supported Rust version: 1.37 or later

View File

@@ -19,7 +19,7 @@ path = "src/lib.rs"
[dependencies] [dependencies]
derive_more = "0.99.2" derive_more = "0.99.2"
futures-channel = "0.3.1" futures-channel = "0.3.7"
parking_lot = "0.11" parking_lot = "0.11"
lazy_static = "1.3" lazy_static = "1.3"
log = "0.4" log = "0.4"

View File

@@ -1,6 +1,20 @@
# Changes # Changes
## Unreleased - 2020-xx-xx ## Unreleased - 2021-xx-xx
## 3.0.0-beta.2 - 2021-xx-xx
* Depend on stable trust-dns packages. [#204]
[#204]: https://github.com/actix/actix-net/pull/204
## 3.0.0-beta.1 - 2020-12-29
* Move acceptors under `accept` module. [#238]
* Merge `actix-connect` crate under `connect` module. [#238]
* Add feature flags to enable acceptors and/or connectors individually. [#238]
[#238]: https://github.com/actix/actix-net/pull/238
## 2.0.0 - 2020-09-03 ## 2.0.0 - 2020-09-03

View File

@@ -1,9 +1,9 @@
[package] [package]
name = "actix-tls" name = "actix-tls"
version = "2.0.0" version = "3.0.0-beta.2"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "TLS acceptor services for Actix ecosystem." description = "TLS acceptor and connector services for Actix ecosystem"
keywords = ["network", "framework", "async", "tls", "ssl"] keywords = ["network", "tls", "ssl", "async", "transport"]
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/actix-tls/" documentation = "https://docs.rs/actix-tls/"
@@ -12,7 +12,7 @@ license = "MIT OR Apache-2.0"
edition = "2018" edition = "2018"
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["openssl", "rustls", "nativetls"] features = ["openssl", "rustls", "native-tls", "accept", "connect", "uri"]
[lib] [lib]
name = "actix_tls" name = "actix_tls"
@@ -20,45 +20,64 @@ path = "src/lib.rs"
[[example]] [[example]]
name = "basic" name = "basic"
required-features = ["rustls"] required-features = ["accept", "rustls"]
[features] [features]
default = [] default = ["accept", "connect", "uri"]
# openssl # enable acceptor services
openssl = ["open-ssl", "tokio-openssl"] accept = []
# rustls # enable connector services
rustls = ["rust-tls", "webpki", "webpki-roots", "tokio-rustls"] connect = ["trust-dns-proto/tokio-runtime", "trust-dns-resolver/tokio-runtime", "trust-dns-resolver/system-config"]
# nativetls # use openssl impls
nativetls = ["native-tls", "tokio-tls"] openssl = ["tls-openssl", "tokio-openssl"]
# use rustls impls
rustls = ["tls-rustls", "webpki", "webpki-roots", "tokio-rustls"]
# use native-tls impls
native-tls = ["tls-native-tls", "tokio-native-tls"]
# support http::Uri as connect address
uri = ["http"]
[dependencies] [dependencies]
actix-service = "1.0.0" actix-codec = "0.4.0-beta.1"
actix-codec = "0.3.0" actix-rt = "2.0.0-beta.1"
actix-utils = "2.0.0" actix-service = "2.0.0-beta.2"
actix-utils = "3.0.0-beta.1"
futures-util = { version = "0.3.4", default-features = false } derive_more = "0.99.5"
either = "1.6"
futures-util = { version = "0.3.7", default-features = false }
http = { version = "0.2.2", optional = true }
log = "0.4"
# resolver
trust-dns-proto = { version = "0.20.0", default-features = false, optional = true }
trust-dns-resolver = { version = "0.20.0", default-features = false, optional = true }
# openssl # openssl
open-ssl = { package = "openssl", version = "0.10", optional = true } tls-openssl = { package = "openssl", version = "0.10", optional = true }
tokio-openssl = { version = "0.4.0", optional = true } tokio-openssl = { version = "0.6", optional = true }
# TODO: Reduce dependencies where tokio wrappers re-export base crate.
# rustls # rustls
rust-tls = { package = "rustls", version = "0.18.0", optional = true } tls-rustls = { package = "rustls", version = "0.19", optional = true }
tokio-rustls = { version = "0.22", optional = true }
webpki = { version = "0.21", optional = true } webpki = { version = "0.21", optional = true }
webpki-roots = { version = "0.20", optional = true } webpki-roots = { version = "0.21", optional = true }
tokio-rustls = { version = "0.14.0", optional = true }
# native-tls # native-tls
native-tls = { version = "0.2", optional = true } tls-native-tls = { package = "native-tls", version = "0.2", optional = true }
tokio-tls = { version = "0.3", optional = true } tokio-native-tls = { version = "0.3", optional = true }
[dev-dependencies] [dev-dependencies]
bytes = "0.5" actix-server = "2.0.0-beta.2"
bytes = "1"
env_logger = "0.8"
futures-util = { version = "0.3.7", default-features = false, features = ["sink"] }
log = "0.4" log = "0.4"
env_logger = "0.7"
actix-testing = "1.0.0"
actix-server = "1"
actix-rt = "1"

View File

@@ -15,6 +15,10 @@
//! http --verify=false https://127.0.0.1:8443 //! http --verify=false https://127.0.0.1:8443
//! ``` //! ```
// this rename only exists because of how we have organised the crate's feature flags
// it is not necessary for your actual code
extern crate tls_rustls as rustls;
use std::{ use std::{
env, env,
fs::File, fs::File,
@@ -27,10 +31,10 @@ use std::{
use actix_server::Server; use actix_server::Server;
use actix_service::pipeline_factory; use actix_service::pipeline_factory;
use actix_tls::rustls::Acceptor as RustlsAcceptor; use actix_tls::accept::rustls::Acceptor as RustlsAcceptor;
use futures_util::future::ok; use futures_util::future::ok;
use log::info; use log::info;
use rust_tls::{ use rustls::{
internal::pemfile::certs, internal::pemfile::rsa_private_keys, NoClientAuth, ServerConfig, internal::pemfile::certs, internal::pemfile::rsa_private_keys, NoClientAuth, ServerConfig,
}; };

View File

@@ -0,0 +1,42 @@
//! TLS acceptor services for Actix ecosystem.
//!
//! ## 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 actix_utils::counter::Counter;
#[cfg(feature = "openssl")]
pub mod openssl;
#[cfg(feature = "rustls")]
pub mod rustls;
#[cfg(feature = "native-tls")]
pub mod nativetls;
pub(crate) static MAX_CONN: AtomicUsize = AtomicUsize::new(256);
thread_local! {
static MAX_CONN_COUNTER: Counter = Counter::new(MAX_CONN.load(Ordering::Relaxed));
}
/// Sets the maximum per-worker concurrent TLS connection limit.
///
/// All listeners will stop accepting connections when this limit is reached.
/// It can be used to regulate the global TLS CPU usage.
///
/// By default, the connection limit is 256.
pub fn max_concurrent_tls_connect(num: usize) {
MAX_CONN.store(num, Ordering::Relaxed);
}
/// TLS error combined with service error.
#[derive(Debug)]
pub enum TlsError<E1, E2> {
Tls(E1),
Service(E2),
}

View File

@@ -1,93 +1,79 @@
use std::marker::PhantomData;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use actix_codec::{AsyncRead, AsyncWrite}; use actix_codec::{AsyncRead, AsyncWrite};
use actix_service::{Service, ServiceFactory}; use actix_service::{Service, ServiceFactory};
use actix_utils::counter::Counter; use actix_utils::counter::Counter;
use futures_util::future::{self, FutureExt, LocalBoxFuture, TryFutureExt}; use futures_util::future::{ready, LocalBoxFuture, Ready};
pub use native_tls::Error; pub use native_tls::Error;
pub use tokio_tls::{TlsAcceptor, TlsStream}; pub use tokio_native_tls::{TlsAcceptor, TlsStream};
use crate::MAX_CONN_COUNTER; use super::MAX_CONN_COUNTER;
/// Accept TLS connections via `native-tls` package. /// Accept TLS connections via `native-tls` package.
/// ///
/// `nativetls` feature enables this `Acceptor` type. /// `native-tls` feature enables this `Acceptor` type.
pub struct Acceptor<T> { pub struct Acceptor {
acceptor: TlsAcceptor, acceptor: TlsAcceptor,
io: PhantomData<T>,
} }
impl<T> Acceptor<T> impl Acceptor {
where
T: AsyncRead + AsyncWrite + Unpin,
{
/// Create `native-tls` based `Acceptor` service factory. /// Create `native-tls` based `Acceptor` service factory.
#[inline] #[inline]
pub fn new(acceptor: TlsAcceptor) -> Self { pub fn new(acceptor: TlsAcceptor) -> Self {
Acceptor { Acceptor { acceptor }
acceptor,
io: PhantomData,
}
} }
} }
impl<T> Clone for Acceptor<T> { impl Clone for Acceptor {
#[inline] #[inline]
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
acceptor: self.acceptor.clone(), acceptor: self.acceptor.clone(),
io: PhantomData,
} }
} }
} }
impl<T> ServiceFactory for Acceptor<T> impl<T> ServiceFactory<T> for Acceptor
where where
T: AsyncRead + AsyncWrite + Unpin + 'static, T: AsyncRead + AsyncWrite + Unpin + 'static,
{ {
type Request = T;
type Response = TlsStream<T>; type Response = TlsStream<T>;
type Error = Error; type Error = Error;
type Service = NativeTlsAcceptorService<T>;
type Config = (); type Config = ();
type Service = NativeTlsAcceptorService;
type InitError = (); type InitError = ();
type Future = future::Ready<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 {
MAX_CONN_COUNTER.with(|conns| { MAX_CONN_COUNTER.with(|conns| {
future::ok(NativeTlsAcceptorService { ready(Ok(NativeTlsAcceptorService {
acceptor: self.acceptor.clone(), acceptor: self.acceptor.clone(),
conns: conns.clone(), conns: conns.clone(),
io: PhantomData, }))
})
}) })
} }
} }
pub struct NativeTlsAcceptorService<T> { pub struct NativeTlsAcceptorService {
acceptor: TlsAcceptor, acceptor: TlsAcceptor,
io: PhantomData<T>,
conns: Counter, conns: Counter,
} }
impl<T> Clone for NativeTlsAcceptorService<T> { impl Clone for NativeTlsAcceptorService {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
acceptor: self.acceptor.clone(), acceptor: self.acceptor.clone(),
io: PhantomData,
conns: self.conns.clone(), conns: self.conns.clone(),
} }
} }
} }
impl<T> Service for NativeTlsAcceptorService<T> impl<T> Service<T> for NativeTlsAcceptorService
where where
T: AsyncRead + AsyncWrite + Unpin + 'static, T: AsyncRead + AsyncWrite + Unpin + 'static,
{ {
type Request = T;
type Response = TlsStream<T>; type Response = TlsStream<T>;
type Error = Error; type Error = Error;
type Future = LocalBoxFuture<'static, Result<TlsStream<T>, Error>>; type Future = LocalBoxFuture<'static, Result<TlsStream<T>, Error>>;
@@ -100,15 +86,13 @@ where
} }
} }
fn call(&mut self, req: Self::Request) -> Self::Future { fn call(&mut self, io: T) -> Self::Future {
let guard = self.conns.get(); let guard = self.conns.get();
let this = self.clone(); let this = self.clone();
async move { this.acceptor.accept(req).await } Box::pin(async move {
.map_ok(move |io| { let io = this.acceptor.accept(io).await;
// Required to preserve `CounterGuard` until `Self::Future` is completely resolved. drop(guard);
let _ = guard; io
io })
})
.boxed_local()
} }
} }

View File

@@ -1,77 +1,78 @@
use std::future::Future; use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use actix_codec::{AsyncRead, AsyncWrite}; use actix_codec::{AsyncRead, AsyncWrite};
use actix_service::{Service, ServiceFactory}; use actix_service::{Service, ServiceFactory};
use actix_utils::counter::{Counter, CounterGuard}; use actix_utils::counter::{Counter, CounterGuard};
use futures_util::future::{ok, FutureExt, LocalBoxFuture, Ready}; use futures_util::{
future::{ready, Ready},
ready,
};
pub use open_ssl::ssl::{AlpnError, SslAcceptor, SslAcceptorBuilder}; pub use openssl::ssl::{
pub use tokio_openssl::{HandshakeError, SslStream}; AlpnError, Error as SslError, HandshakeError, Ssl, SslAcceptor, SslAcceptorBuilder,
};
pub use tokio_openssl::SslStream;
use crate::MAX_CONN_COUNTER; use super::MAX_CONN_COUNTER;
/// Accept TLS connections via `openssl` package. /// Accept TLS connections via `openssl` package.
/// ///
/// `openssl` feature enables this `Acceptor` type. /// `openssl` feature enables this `Acceptor` type.
pub struct Acceptor<T: AsyncRead + AsyncWrite> { pub struct Acceptor {
acceptor: SslAcceptor, acceptor: SslAcceptor,
io: PhantomData<T>,
} }
impl<T: AsyncRead + AsyncWrite> Acceptor<T> { 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,
io: PhantomData,
}
} }
} }
impl<T: AsyncRead + AsyncWrite> Clone for Acceptor<T> { impl Clone for Acceptor {
#[inline] #[inline]
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
acceptor: self.acceptor.clone(), acceptor: self.acceptor.clone(),
io: PhantomData,
} }
} }
} }
impl<T: AsyncRead + AsyncWrite + Unpin + 'static> ServiceFactory for Acceptor<T> { impl<T> ServiceFactory<T> for Acceptor
type Request = T; where
T: AsyncRead + AsyncWrite + Unpin + 'static,
{
type Response = SslStream<T>; type Response = SslStream<T>;
type Error = HandshakeError<T>; type Error = SslError;
type Config = (); type Config = ();
type Service = AcceptorService<T>; type Service = AcceptorService;
type InitError = (); type InitError = ();
type Future = Ready<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 {
MAX_CONN_COUNTER.with(|conns| { MAX_CONN_COUNTER.with(|conns| {
ok(AcceptorService { ready(Ok(AcceptorService {
acceptor: self.acceptor.clone(), acceptor: self.acceptor.clone(),
conns: conns.clone(), conns: conns.clone(),
io: PhantomData, }))
})
}) })
} }
} }
pub struct AcceptorService<T> { pub struct AcceptorService {
acceptor: SslAcceptor, acceptor: SslAcceptor,
conns: Counter, conns: Counter,
io: PhantomData<T>,
} }
impl<T: AsyncRead + AsyncWrite + Unpin + 'static> Service for AcceptorService<T> { impl<T> Service<T> for AcceptorService
type Request = T; where
T: AsyncRead + AsyncWrite + Unpin + 'static,
{
type Response = SslStream<T>; type Response = SslStream<T>;
type Error = HandshakeError<T>; type Error = SslError;
type Future = AcceptorServiceResponse<T>; type Future = AcceptorServiceResponse<T>;
fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
@@ -82,15 +83,12 @@ impl<T: AsyncRead + AsyncWrite + Unpin + 'static> Service for AcceptorService<T>
} }
} }
fn call(&mut self, req: Self::Request) -> Self::Future { fn call(&mut self, io: T) -> Self::Future {
let acc = self.acceptor.clone(); let ssl_ctx = self.acceptor.context();
let ssl = Ssl::new(ssl_ctx).expect("Provided SSL acceptor was invalid.");
AcceptorServiceResponse { AcceptorServiceResponse {
_guard: self.conns.get(), _guard: self.conns.get(),
fut: async move { stream: Some(SslStream::new(ssl, io).unwrap()),
let acc = acc;
tokio_openssl::accept(&acc, req).await
}
.boxed_local(),
} }
} }
} }
@@ -99,15 +97,15 @@ pub struct AcceptorServiceResponse<T>
where where
T: AsyncRead + AsyncWrite, T: AsyncRead + AsyncWrite,
{ {
fut: LocalBoxFuture<'static, Result<SslStream<T>, HandshakeError<T>>>, stream: Option<SslStream<T>>,
_guard: CounterGuard, _guard: CounterGuard,
} }
impl<T: AsyncRead + AsyncWrite + Unpin> Future for AcceptorServiceResponse<T> { impl<T: AsyncRead + AsyncWrite + Unpin> Future for AcceptorServiceResponse<T> {
type Output = Result<SslStream<T>, HandshakeError<T>>; type Output = Result<SslStream<T>, SslError>;
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> {
let io = futures_util::ready!(Pin::new(&mut self.fut).poll(cx))?; ready!(Pin::new(self.stream.as_mut().unwrap()).poll_accept(cx))?;
Poll::Ready(Ok(io)) Poll::Ready(Ok(self.stream.take().expect("SSL connect has resolved.")))
} }
} }

View File

@@ -1,6 +1,5 @@
use std::future::Future; use std::future::Future;
use std::io; use std::io;
use std::marker::PhantomData;
use std::pin::Pin; use std::pin::Pin;
use std::sync::Arc; use std::sync::Arc;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
@@ -8,74 +7,73 @@ use std::task::{Context, Poll};
use actix_codec::{AsyncRead, AsyncWrite}; use actix_codec::{AsyncRead, AsyncWrite};
use actix_service::{Service, ServiceFactory}; use actix_service::{Service, ServiceFactory};
use actix_utils::counter::{Counter, CounterGuard}; use actix_utils::counter::{Counter, CounterGuard};
use futures_util::future::{ok, Ready}; use futures_util::future::{ready, Ready};
use tokio_rustls::{Accept, TlsAcceptor}; use tokio_rustls::{Accept, TlsAcceptor};
pub use rust_tls::{ServerConfig, Session}; pub use rustls::{ServerConfig, Session};
pub use tokio_rustls::server::TlsStream; pub use tokio_rustls::server::TlsStream;
pub use webpki_roots::TLS_SERVER_ROOTS; pub use webpki_roots::TLS_SERVER_ROOTS;
use crate::MAX_CONN_COUNTER; use super::MAX_CONN_COUNTER;
/// Accept TLS connections via `rustls` package. /// Accept TLS connections via `rustls` package.
/// ///
/// `rustls` feature enables this `Acceptor` type. /// `rustls` feature enables this `Acceptor` type.
pub struct Acceptor<T> { pub struct Acceptor {
config: Arc<ServerConfig>, config: Arc<ServerConfig>,
io: PhantomData<T>,
} }
impl<T: AsyncRead + AsyncWrite> Acceptor<T> { impl Acceptor {
/// Create Rustls based `Acceptor` service factory. /// Create Rustls based `Acceptor` service factory.
#[inline] #[inline]
pub fn new(config: ServerConfig) -> Self { pub fn new(config: ServerConfig) -> Self {
Acceptor { Acceptor {
config: Arc::new(config), config: Arc::new(config),
io: PhantomData,
} }
} }
} }
impl<T> Clone for Acceptor<T> { impl Clone for Acceptor {
#[inline] #[inline]
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
config: self.config.clone(), config: self.config.clone(),
io: PhantomData,
} }
} }
} }
impl<T: AsyncRead + AsyncWrite + Unpin> ServiceFactory for Acceptor<T> { impl<T> ServiceFactory<T> for Acceptor
type Request = T; where
T: AsyncRead + AsyncWrite + Unpin,
{
type Response = TlsStream<T>; type Response = TlsStream<T>;
type Error = io::Error; type Error = io::Error;
type Service = AcceptorService<T>;
type Config = (); type Config = ();
type Service = AcceptorService;
type InitError = (); type InitError = ();
type Future = Ready<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 {
MAX_CONN_COUNTER.with(|conns| { MAX_CONN_COUNTER.with(|conns| {
ok(AcceptorService { ready(Ok(AcceptorService {
acceptor: self.config.clone().into(), acceptor: self.config.clone().into(),
conns: conns.clone(), conns: conns.clone(),
io: PhantomData, }))
})
}) })
} }
} }
/// Rustls based `Acceptor` service /// Rustls based `Acceptor` service
pub struct AcceptorService<T> { pub struct AcceptorService {
acceptor: TlsAcceptor, acceptor: TlsAcceptor,
io: PhantomData<T>,
conns: Counter, conns: Counter,
} }
impl<T: AsyncRead + AsyncWrite + Unpin> Service for AcceptorService<T> { impl<T> Service<T> for AcceptorService
type Request = T; where
T: AsyncRead + AsyncWrite + Unpin,
{
type Response = TlsStream<T>; type Response = TlsStream<T>;
type Error = io::Error; type Error = io::Error;
type Future = AcceptorServiceFut<T>; type Future = AcceptorServiceFut<T>;
@@ -88,7 +86,7 @@ impl<T: AsyncRead + AsyncWrite + Unpin> Service for AcceptorService<T> {
} }
} }
fn call(&mut self, req: Self::Request) -> Self::Future { fn call(&mut self, req: T) -> Self::Future {
AcceptorServiceFut { AcceptorServiceFut {
_guard: self.conns.get(), _guard: self.conns.get(),
fut: self.acceptor.accept(req), fut: self.acceptor.accept(req),
@@ -104,16 +102,14 @@ where
_guard: CounterGuard, _guard: CounterGuard,
} }
impl<T: AsyncRead + AsyncWrite + Unpin> Future for AcceptorServiceFut<T> { impl<T> Future for AcceptorServiceFut<T>
where
T: AsyncRead + AsyncWrite + Unpin,
{
type Output = Result<TlsStream<T>, io::Error>; type Output = Result<TlsStream<T>, io::Error>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut(); let this = self.get_mut();
Pin::new(&mut this.fut).poll(cx)
let res = futures_util::ready!(Pin::new(&mut this.fut).poll(cx));
match res {
Ok(io) => Poll::Ready(Ok(io)),
Err(e) => Poll::Ready(Err(e)),
}
} }
} }

View File

@@ -8,7 +8,8 @@ use std::task::{Context, Poll};
use actix_rt::net::TcpStream; use actix_rt::net::TcpStream;
use actix_service::{Service, ServiceFactory}; use actix_service::{Service, ServiceFactory};
use futures_util::future::{err, ok, BoxFuture, Either, FutureExt, Ready}; use futures_util::future::{ready, Ready};
use log::{error, trace};
use super::connect::{Address, Connect, Connection}; use super::connect::{Address, Connect, Connection};
use super::error::ConnectError; use super::error::ConnectError;
@@ -49,7 +50,7 @@ impl<T: Address> ServiceFactory<Connect<T>> for TcpConnectorFactory<T> {
type Future = Ready<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 {
ok(self.service()) ready(Ok(self.service()))
} }
} }
@@ -72,8 +73,7 @@ impl<T> Clone for TcpConnector<T> {
impl<T: Address> Service<Connect<T>> for TcpConnector<T> { impl<T: Address> Service<Connect<T>> for TcpConnector<T> {
type Response = Connection<T, TcpStream>; type Response = Connection<T, TcpStream>;
type Error = ConnectError; type Error = ConnectError;
#[allow(clippy::type_complexity)] type Future = TcpConnectorResponse<T>;
type Future = Either<TcpConnectorResponse<T>, Ready<Result<Self::Response, Self::Error>>>;
actix_service::always_ready!(); actix_service::always_ready!();
@@ -82,21 +82,26 @@ impl<T: Address> Service<Connect<T>> for TcpConnector<T> {
let Connect { req, addr, .. } = req; let Connect { req, addr, .. } = req;
if let Some(addr) = addr { if let Some(addr) = addr {
Either::Left(TcpConnectorResponse::new(req, port, addr)) TcpConnectorResponse::new(req, port, addr)
} else { } else {
error!("TCP connector: got unresolved address"); error!("TCP connector: got unresolved address");
Either::Right(err(ConnectError::Unresolved)) TcpConnectorResponse::Error(Some(ConnectError::Unresolved))
} }
} }
} }
type LocalBoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
#[doc(hidden)] #[doc(hidden)]
/// TCP stream connector response future /// TCP stream connector response future
pub struct TcpConnectorResponse<T> { pub enum TcpConnectorResponse<T> {
req: Option<T>, Response {
port: u16, req: Option<T>,
addrs: Option<VecDeque<SocketAddr>>, port: u16,
stream: Option<BoxFuture<'static, Result<TcpStream, io::Error>>>, addrs: Option<VecDeque<SocketAddr>>,
stream: Option<LocalBoxFuture<'static, Result<TcpStream, io::Error>>>,
},
Error(Option<ConnectError>),
} }
impl<T: Address> TcpConnectorResponse<T> { impl<T: Address> TcpConnectorResponse<T> {
@@ -112,13 +117,13 @@ impl<T: Address> TcpConnectorResponse<T> {
); );
match addr { match addr {
either::Either::Left(addr) => TcpConnectorResponse { either::Either::Left(addr) => TcpConnectorResponse::Response {
req: Some(req), req: Some(req),
port, port,
addrs: None, addrs: None,
stream: Some(TcpStream::connect(addr).boxed()), stream: Some(Box::pin(TcpStream::connect(addr))),
}, },
either::Either::Right(addrs) => TcpConnectorResponse { either::Either::Right(addrs) => TcpConnectorResponse::Response {
req: Some(req), req: Some(req),
port, port,
addrs: Some(addrs), addrs: Some(addrs),
@@ -133,36 +138,43 @@ impl<T: Address> Future for TcpConnectorResponse<T> {
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut(); let this = self.get_mut();
match this {
// connect TcpConnectorResponse::Error(e) => Poll::Ready(Err(e.take().unwrap())),
loop { // connect
if let Some(new) = this.stream.as_mut() { TcpConnectorResponse::Response {
match new.as_mut().poll(cx) { req,
Poll::Ready(Ok(sock)) => { port,
let req = this.req.take().unwrap(); addrs,
trace!( stream,
"TCP connector - successfully connected to connecting to {:?} - {:?}", } => loop {
req.host(), sock.peer_addr() if let Some(new) = stream.as_mut() {
); match new.as_mut().poll(cx) {
return Poll::Ready(Ok(Connection::new(sock, req))); Poll::Ready(Ok(sock)) => {
} let req = req.take().unwrap();
Poll::Pending => return Poll::Pending, trace!(
Poll::Ready(Err(err)) => { "TCP connector - successfully connected to connecting to {:?} - {:?}",
trace!( req.host(), sock.peer_addr()
"TCP connector - failed to connect to connecting to {:?} port: {}", );
this.req.as_ref().unwrap().host(), return Poll::Ready(Ok(Connection::new(sock, req)));
this.port, }
); Poll::Pending => return Poll::Pending,
if this.addrs.is_none() || this.addrs.as_ref().unwrap().is_empty() { Poll::Ready(Err(err)) => {
return Poll::Ready(Err(err.into())); trace!(
"TCP connector - failed to connect to connecting to {:?} port: {}",
req.as_ref().unwrap().host(),
port,
);
if addrs.is_none() || addrs.as_ref().unwrap().is_empty() {
return Poll::Ready(Err(err.into()));
}
} }
} }
} }
}
// try to connect // try to connect
let addr = this.addrs.as_mut().unwrap().pop_front().unwrap(); let addr = addrs.as_mut().unwrap().pop_front().unwrap();
this.stream = Some(TcpStream::connect(addr).boxed()); *stream = Some(Box::pin(TcpStream::connect(addr)));
},
} }
} }
} }

View File

@@ -5,21 +5,12 @@
//! * `openssl` - enables TLS support via `openssl` crate //! * `openssl` - enables TLS support via `openssl` crate
//! * `rustls` - enables TLS support via `rustls` crate //! * `rustls` - enables TLS support via `rustls` crate
#![deny(rust_2018_idioms, nonstandard_style)]
#![recursion_limit = "128"]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#[macro_use]
extern crate log;
mod connect; mod connect;
mod connector; mod connector;
mod error; mod error;
mod resolve; mod resolve;
mod service; mod service;
pub mod ssl; pub mod ssl;
#[cfg(feature = "uri")] #[cfg(feature = "uri")]
mod uri; mod uri;
@@ -45,7 +36,7 @@ pub async fn start_resolver(
cfg: ResolverConfig, cfg: ResolverConfig,
opts: ResolverOpts, opts: ResolverOpts,
) -> Result<AsyncResolver, ConnectError> { ) -> Result<AsyncResolver, ConnectError> {
Ok(AsyncResolver::tokio(cfg, opts).await?) Ok(AsyncResolver::tokio(cfg, opts)?)
} }
struct DefaultResolver(AsyncResolver); struct DefaultResolver(AsyncResolver);
@@ -62,7 +53,7 @@ pub(crate) async fn get_default_resolver() -> Result<AsyncResolver, ConnectError
} }
}; };
let resolver = AsyncResolver::tokio(cfg, opts).await?; let resolver = AsyncResolver::tokio(cfg, opts)?;
Arbiter::set_item(DefaultResolver(resolver.clone())); Arbiter::set_item(DefaultResolver(resolver.clone()));
Ok(resolver) Ok(resolver)

View File

@@ -6,12 +6,13 @@ use std::task::{Context, Poll};
use actix_service::{Service, ServiceFactory}; use actix_service::{Service, ServiceFactory};
use futures_util::future::{ok, Either, Ready}; use futures_util::future::{ok, Either, Ready};
use log::trace;
use trust_dns_resolver::TokioAsyncResolver as AsyncResolver; use trust_dns_resolver::TokioAsyncResolver as AsyncResolver;
use trust_dns_resolver::{error::ResolveError, lookup_ip::LookupIp}; use trust_dns_resolver::{error::ResolveError, lookup_ip::LookupIp};
use crate::connect::{Address, Connect}; use super::connect::{Address, Connect};
use crate::error::ConnectError; use super::error::ConnectError;
use crate::get_default_resolver; use super::get_default_resolver;
/// DNS Resolver Service factory /// DNS Resolver Service factory
pub struct ResolverFactory<T> { pub struct ResolverFactory<T> {

View File

@@ -8,10 +8,10 @@ use either::Either;
use futures_util::future::{ok, Ready}; use futures_util::future::{ok, Ready};
use trust_dns_resolver::TokioAsyncResolver as AsyncResolver; use trust_dns_resolver::TokioAsyncResolver as AsyncResolver;
use crate::connect::{Address, Connect, Connection}; use super::connect::{Address, Connect, Connection};
use crate::connector::{TcpConnector, TcpConnectorFactory}; use super::connector::{TcpConnector, TcpConnectorFactory};
use crate::error::ConnectError; use super::error::ConnectError;
use crate::resolve::{Resolver, ResolverFactory}; use super::resolve::{Resolver, ResolverFactory};
pub struct ConnectServiceFactory<T> { pub struct ConnectServiceFactory<T> {
tcp: TcpConnectorFactory<T>, tcp: TcpConnectorFactory<T>,

View File

@@ -4,97 +4,83 @@ use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use std::{fmt, io}; use std::{fmt, io};
pub use open_ssl::ssl::{Error as SslError, SslConnector, SslMethod};
pub use tokio_openssl::{HandshakeError, SslStream};
use actix_codec::{AsyncRead, AsyncWrite}; use actix_codec::{AsyncRead, AsyncWrite};
use actix_rt::net::TcpStream; use actix_rt::net::TcpStream;
use actix_service::{Service, ServiceFactory}; use actix_service::{Service, ServiceFactory};
use futures_util::future::{err, ok, Either, FutureExt, LocalBoxFuture, Ready}; use futures_util::{
future::{ready, Either, Ready},
ready,
};
use log::trace;
pub use openssl::ssl::{Error as SslError, HandshakeError, SslConnector, SslMethod};
pub use tokio_openssl::SslStream;
use trust_dns_resolver::TokioAsyncResolver as AsyncResolver; use trust_dns_resolver::TokioAsyncResolver as AsyncResolver;
use crate::{ use crate::connect::{
Address, Connect, ConnectError, ConnectService, ConnectServiceFactory, Connection, Address, Connect, ConnectError, ConnectService, ConnectServiceFactory, Connection,
}; };
/// OpenSSL connector factory /// OpenSSL connector factory
pub struct OpensslConnector<T, U> { pub struct OpensslConnector {
connector: SslConnector, connector: SslConnector,
_t: PhantomData<(T, U)>,
} }
impl<T, U> OpensslConnector<T, U> { impl OpensslConnector {
pub fn new(connector: SslConnector) -> Self { pub fn new(connector: SslConnector) -> Self {
OpensslConnector { OpensslConnector { connector }
connector,
_t: PhantomData,
}
} }
} }
impl<T, U> OpensslConnector<T, U> impl OpensslConnector {
where pub fn service(connector: SslConnector) -> OpensslConnectorService {
T: Address + 'static, OpensslConnectorService { connector }
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static,
{
pub fn service(connector: SslConnector) -> OpensslConnectorService<T, U> {
OpensslConnectorService {
connector,
_t: PhantomData,
}
} }
} }
impl<T, U> Clone for OpensslConnector<T, U> { impl Clone for OpensslConnector {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
connector: self.connector.clone(), connector: self.connector.clone(),
_t: PhantomData,
} }
} }
} }
impl<T, U> ServiceFactory for OpensslConnector<T, U> impl<T, U> ServiceFactory<Connection<T, U>> for OpensslConnector
where where
T: Address + 'static, T: Address + 'static,
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static, U: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static,
{ {
type Request = Connection<T, U>;
type Response = Connection<T, SslStream<U>>; type Response = Connection<T, SslStream<U>>;
type Error = io::Error; type Error = io::Error;
type Config = (); type Config = ();
type Service = OpensslConnectorService<T, U>; type Service = OpensslConnectorService;
type InitError = (); type InitError = ();
type Future = Ready<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 {
ok(OpensslConnectorService { ready(Ok(OpensslConnectorService {
connector: self.connector.clone(), connector: self.connector.clone(),
_t: PhantomData, }))
})
} }
} }
pub struct OpensslConnectorService<T, U> { pub struct OpensslConnectorService {
connector: SslConnector, connector: SslConnector,
_t: PhantomData<(T, U)>,
} }
impl<T, U> Clone for OpensslConnectorService<T, U> { impl Clone for OpensslConnectorService {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
connector: self.connector.clone(), connector: self.connector.clone(),
_t: PhantomData,
} }
} }
} }
impl<T, U> Service for OpensslConnectorService<T, U> impl<T, U> Service<Connection<T, U>> for OpensslConnectorService
where where
T: Address + 'static, T: Address + 'static,
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static, U: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static,
{ {
type Request = Connection<T, U>;
type Response = Connection<T, SslStream<U>>; type Response = Connection<T, SslStream<U>>;
type Error = io::Error; type Error = io::Error;
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
@@ -108,19 +94,24 @@ where
let host = stream.host().to_string(); let host = stream.host().to_string();
match self.connector.configure() { match self.connector.configure() {
Err(e) => Either::Right(err(io::Error::new(io::ErrorKind::Other, e))), Err(e) => Either::Right(ready(Err(io::Error::new(io::ErrorKind::Other, e)))),
Ok(config) => Either::Left(ConnectAsyncExt { Ok(config) => {
fut: async move { tokio_openssl::connect(config, &host, io).await } let ssl = config
.boxed_local(), .into_ssl(&host)
stream: Some(stream), .expect("SSL connect configuration was invalid.");
_t: PhantomData,
}), Either::Left(ConnectAsyncExt {
io: Some(SslStream::new(ssl, io).unwrap()),
stream: Some(stream),
_t: PhantomData,
})
}
} }
} }
} }
pub struct ConnectAsyncExt<T, U> { pub struct ConnectAsyncExt<T, U> {
fut: LocalBoxFuture<'static, Result<SslStream<U>, HandshakeError<U>>>, io: Option<SslStream<U>>,
stream: Option<Connection<T, ()>>, stream: Option<Connection<T, ()>>,
_t: PhantomData<U>, _t: PhantomData<U>,
} }
@@ -134,24 +125,23 @@ where
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut(); let this = self.get_mut();
match Pin::new(&mut this.fut).poll(cx) { match ready!(Pin::new(this.io.as_mut().unwrap()).poll_connect(cx)) {
Poll::Ready(Ok(stream)) => { Ok(_) => {
let s = this.stream.take().unwrap(); let stream = this.stream.take().unwrap();
trace!("SSL Handshake success: {:?}", s.host()); trace!("SSL Handshake success: {:?}", stream.host());
Poll::Ready(Ok(s.replace(stream).1)) Poll::Ready(Ok(stream.replace(this.io.take().unwrap()).1))
} }
Poll::Ready(Err(e)) => { Err(e) => {
trace!("SSL Handshake error: {:?}", e); trace!("SSL Handshake error: {:?}", e);
Poll::Ready(Err(io::Error::new(io::ErrorKind::Other, format!("{}", e)))) Poll::Ready(Err(io::Error::new(io::ErrorKind::Other, format!("{}", e))))
} }
Poll::Pending => Poll::Pending,
} }
} }
} }
pub struct OpensslConnectServiceFactory<T> { pub struct OpensslConnectServiceFactory<T> {
tcp: ConnectServiceFactory<T>, tcp: ConnectServiceFactory<T>,
openssl: OpensslConnector<T, TcpStream>, openssl: OpensslConnector,
} }
impl<T> OpensslConnectServiceFactory<T> { impl<T> OpensslConnectServiceFactory<T> {
@@ -177,7 +167,6 @@ impl<T> OpensslConnectServiceFactory<T> {
tcp: self.tcp.service(), tcp: self.tcp.service(),
openssl: OpensslConnectorService { openssl: OpensslConnectorService {
connector: self.openssl.connector.clone(), connector: self.openssl.connector.clone(),
_t: PhantomData,
}, },
} }
} }
@@ -192,8 +181,7 @@ impl<T> Clone for OpensslConnectServiceFactory<T> {
} }
} }
impl<T: Address + 'static> ServiceFactory for OpensslConnectServiceFactory<T> { impl<T: Address + 'static> ServiceFactory<Connect<T>> for OpensslConnectServiceFactory<T> {
type Request = Connect<T>;
type Response = SslStream<TcpStream>; type Response = SslStream<TcpStream>;
type Error = ConnectError; type Error = ConnectError;
type Config = (); type Config = ();
@@ -202,18 +190,17 @@ impl<T: Address + 'static> ServiceFactory for OpensslConnectServiceFactory<T> {
type Future = Ready<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 {
ok(self.service()) ready(Ok(self.service()))
} }
} }
#[derive(Clone)] #[derive(Clone)]
pub struct OpensslConnectService<T> { pub struct OpensslConnectService<T> {
tcp: ConnectService<T>, tcp: ConnectService<T>,
openssl: OpensslConnectorService<T, TcpStream>, openssl: OpensslConnectorService,
} }
impl<T: Address + 'static> Service for OpensslConnectService<T> { impl<T: Address + 'static> Service<Connect<T>> for OpensslConnectService<T> {
type Request = Connect<T>;
type Response = SslStream<TcpStream>; type Response = SslStream<TcpStream>;
type Error = ConnectError; type Error = ConnectError;
type Future = OpensslConnectServiceResponse<T>; type Future = OpensslConnectServiceResponse<T>;
@@ -230,9 +217,9 @@ impl<T: Address + 'static> Service for OpensslConnectService<T> {
} }
pub struct OpensslConnectServiceResponse<T: Address + 'static> { pub struct OpensslConnectServiceResponse<T: Address + 'static> {
fut1: Option<<ConnectService<T> as Service>::Future>, fut1: Option<<ConnectService<T> as Service<Connect<T>>>::Future>,
fut2: Option<<OpensslConnectorService<T, TcpStream> as Service>::Future>, fut2: Option<<OpensslConnectorService as Service<Connection<T, TcpStream>>>::Future>,
openssl: OpensslConnectorService<T, TcpStream>, openssl: OpensslConnectorService,
} }
impl<T: Address> Future for OpensslConnectServiceResponse<T> { impl<T: Address> Future for OpensslConnectServiceResponse<T> {
@@ -240,7 +227,7 @@ impl<T: Address> Future for OpensslConnectServiceResponse<T> {
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> {
if let Some(ref mut fut) = self.fut1 { if let Some(ref mut fut) = self.fut1 {
match futures_util::ready!(Pin::new(fut).poll(cx)) { match ready!(Pin::new(fut).poll(cx)) {
Ok(res) => { Ok(res) => {
let _ = self.fut1.take(); let _ = self.fut1.take();
self.fut2 = Some(self.openssl.call(res)); self.fut2 = Some(self.openssl.call(res));
@@ -250,7 +237,7 @@ impl<T: Address> Future for OpensslConnectServiceResponse<T> {
} }
if let Some(ref mut fut) = self.fut2 { if let Some(ref mut fut) = self.fut2 {
match futures_util::ready!(Pin::new(fut).poll(cx)) { match ready!(Pin::new(fut).poll(cx)) {
Ok(connect) => Poll::Ready(Ok(connect.into_parts().0)), Ok(connect) => Poll::Ready(Ok(connect.into_parts().0)),
Err(e) => Poll::Ready(Err(ConnectError::Io(io::Error::new( Err(e) => Poll::Ready(Err(ConnectError::Io(io::Error::new(
io::ErrorKind::Other, io::ErrorKind::Other,

View File

@@ -1,97 +1,84 @@
use std::fmt; use std::fmt;
use std::future::Future; use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin; use std::pin::Pin;
use std::sync::Arc; use std::sync::Arc;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
pub use rust_tls::Session; pub use rustls::Session;
pub use tokio_rustls::{client::TlsStream, rustls::ClientConfig}; pub use tokio_rustls::{client::TlsStream, rustls::ClientConfig};
use actix_codec::{AsyncRead, AsyncWrite}; use actix_codec::{AsyncRead, AsyncWrite};
use actix_service::{Service, ServiceFactory}; use actix_service::{Service, ServiceFactory};
use futures_util::future::{ok, Ready}; use futures_util::{
future::{ready, Ready},
ready,
};
use log::trace;
use tokio_rustls::{Connect, TlsConnector}; use tokio_rustls::{Connect, TlsConnector};
use webpki::DNSNameRef; use webpki::DNSNameRef;
use crate::{Address, Connection}; use crate::connect::{Address, Connection};
/// Rustls connector factory /// Rustls connector factory
pub struct RustlsConnector<T, U> { pub struct RustlsConnector {
connector: Arc<ClientConfig>, connector: Arc<ClientConfig>,
_t: PhantomData<(T, U)>,
} }
impl<T, U> RustlsConnector<T, U> { impl RustlsConnector {
pub fn new(connector: Arc<ClientConfig>) -> Self { pub fn new(connector: Arc<ClientConfig>) -> Self {
RustlsConnector { RustlsConnector { connector }
connector,
_t: PhantomData,
}
} }
} }
impl<T, U> RustlsConnector<T, U> impl RustlsConnector {
where pub fn service(connector: Arc<ClientConfig>) -> RustlsConnectorService {
T: Address, RustlsConnectorService { connector }
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug,
{
pub fn service(connector: Arc<ClientConfig>) -> RustlsConnectorService<T, U> {
RustlsConnectorService {
connector,
_t: PhantomData,
}
} }
} }
impl<T, U> Clone for RustlsConnector<T, U> { impl Clone for RustlsConnector {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
connector: self.connector.clone(), connector: self.connector.clone(),
_t: PhantomData,
} }
} }
} }
impl<T: Address, U> ServiceFactory for RustlsConnector<T, U> impl<T: Address, U> ServiceFactory<Connection<T, U>> for RustlsConnector
where where
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug, U: AsyncRead + AsyncWrite + Unpin + fmt::Debug,
{ {
type Request = Connection<T, U>;
type Response = Connection<T, TlsStream<U>>; type Response = Connection<T, TlsStream<U>>;
type Error = std::io::Error; type Error = std::io::Error;
type Config = (); type Config = ();
type Service = RustlsConnectorService<T, U>; type Service = RustlsConnectorService;
type InitError = (); type InitError = ();
type Future = Ready<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 {
ok(RustlsConnectorService { ready(Ok(RustlsConnectorService {
connector: self.connector.clone(), connector: self.connector.clone(),
_t: PhantomData, }))
})
} }
} }
pub struct RustlsConnectorService<T, U> { pub struct RustlsConnectorService {
connector: Arc<ClientConfig>, connector: Arc<ClientConfig>,
_t: PhantomData<(T, U)>,
} }
impl<T, U> Clone for RustlsConnectorService<T, U> { impl Clone for RustlsConnectorService {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
connector: self.connector.clone(), connector: self.connector.clone(),
_t: PhantomData,
} }
} }
} }
impl<T: Address, U> Service for RustlsConnectorService<T, U> impl<T, U> Service<Connection<T, U>> for RustlsConnectorService
where where
T: Address,
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug, U: AsyncRead + AsyncWrite + Unpin + fmt::Debug,
{ {
type Request = Connection<T, U>;
type Response = Connection<T, TlsStream<U>>; type Response = Connection<T, TlsStream<U>>;
type Error = std::io::Error; type Error = std::io::Error;
type Future = ConnectAsyncExt<T, U>; type Future = ConnectAsyncExt<T, U>;
@@ -115,20 +102,18 @@ pub struct ConnectAsyncExt<T, U> {
stream: Option<Connection<T, ()>>, stream: Option<Connection<T, ()>>,
} }
impl<T: Address, U> Future for ConnectAsyncExt<T, U> impl<T, U> Future for ConnectAsyncExt<T, U>
where where
T: Address,
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug, U: AsyncRead + AsyncWrite + Unpin + fmt::Debug,
{ {
type Output = Result<Connection<T, TlsStream<U>>, std::io::Error>; type Output = Result<Connection<T, TlsStream<U>>, std::io::Error>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut(); let this = self.get_mut();
Poll::Ready( let stream = ready!(Pin::new(&mut this.fut).poll(cx))?;
futures_util::ready!(Pin::new(&mut this.fut).poll(cx)).map(|stream| { let s = this.stream.take().unwrap();
let s = this.stream.take().unwrap(); trace!("SSL Handshake success: {:?}", s.host());
trace!("SSL Handshake success: {:?}", s.host()); Poll::Ready(Ok(s.replace(stream).1))
s.replace(stream).1
}),
)
} }
} }

View File

@@ -1,6 +1,6 @@
use http::Uri; use http::Uri;
use crate::Address; use super::Address;
impl Address for Uri { impl Address for Uri {
fn host(&self) -> &str { fn host(&self) -> &str {

View File

@@ -1,46 +1,17 @@
//! TLS acceptor services for Actix ecosystem. //! TLS acceptor and connector services for Actix ecosystem
//!
//! ## Crate Features
//! * `openssl` - TLS acceptor using the `openssl` crate.
//! * `rustls` - TLS acceptor using the `rustls` crate.
//! * `nativetls` - TLS acceptor using the `native-tls` crate.
#![deny(rust_2018_idioms, nonstandard_style)] #![deny(rust_2018_idioms, nonstandard_style)]
#![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")]
use std::sync::atomic::{AtomicUsize, Ordering}; #[cfg(feature = "native-tls")]
extern crate tls_native_tls as native_tls;
use actix_utils::counter::Counter;
#[cfg(feature = "openssl")] #[cfg(feature = "openssl")]
pub mod openssl; extern crate tls_openssl as openssl;
#[cfg(feature = "rustls")] #[cfg(feature = "rustls")]
pub mod rustls; extern crate tls_rustls as rustls;
#[cfg(feature = "nativetls")] #[cfg(feature = "accept")]
pub mod nativetls; pub mod accept;
#[cfg(feature = "connect")]
pub(crate) static MAX_CONN: AtomicUsize = AtomicUsize::new(256); pub mod connect;
thread_local! {
static MAX_CONN_COUNTER: Counter = Counter::new(MAX_CONN.load(Ordering::Relaxed));
}
/// Sets the maximum per-worker concurrent TLS connection limit.
///
/// All listeners will stop accepting connections when this limit is reached.
/// It can be used to regulate the global TLS CPU usage.
///
/// By default, the connection limit is 256.
pub fn max_concurrent_tls_connect(num: usize) {
MAX_CONN.store(num, Ordering::Relaxed);
}
/// TLS error combined with service error.
#[derive(Debug)]
pub enum TlsError<E1, E2> {
Tls(E1),
Service(E2),
}

View File

@@ -2,15 +2,18 @@ use std::io;
use actix_codec::{BytesCodec, Framed}; use actix_codec::{BytesCodec, Framed};
use actix_rt::net::TcpStream; use actix_rt::net::TcpStream;
use actix_server::TestServer;
use actix_service::{fn_service, Service, ServiceFactory}; use actix_service::{fn_service, Service, ServiceFactory};
use actix_testing::TestServer;
use bytes::Bytes; use bytes::Bytes;
use futures_util::sink::SinkExt; use futures_util::sink::SinkExt;
use actix_connect::resolver::{ResolverConfig, ResolverOpts}; use actix_tls::connect::{
use actix_connect::Connect; self as actix_connect,
resolver::{ResolverConfig, ResolverOpts},
Connect,
};
#[cfg(feature = "openssl")] #[cfg(all(feature = "connect", feature = "openssl"))]
#[actix_rt::test] #[actix_rt::test]
async fn test_string() { async fn test_string() {
let srv = TestServer::with(|| { let srv = TestServer::with(|| {

View File

@@ -16,11 +16,12 @@ name = "actix_tracing"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
actix-service = "1.0.4" actix-service = "2.0.0-beta.2"
futures-util = { version = "0.3.4", default-features = false } futures-util = { version = "0.3.4", default-features = false }
tracing = "0.1" tracing = "0.1"
tracing-futures = "0.2" tracing-futures = "0.2"
[dev_dependencies] [dev_dependencies]
actix-rt = "1.0" actix-rt = "1.0"
slab = "0.4" slab = "0.4"

View File

@@ -1,226 +1,156 @@
# Changes # Changes
## Unreleased - 2020-xx-xx ## Unreleased - 2021-xx-xx
## 3.0.0-beta.1 - 2020-12-28
* 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
## 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
* Add `Clone` impl for `condition::Waiter` ## 1.0.6 - 2020-01-08
* 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
* Allow to create `framed::Dispatcher` with custom `mpsc::Receiver` ## 1.0.2 - 2019-12-11
* 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.
* Fix oneshot.
* Migrate to tokio 0.2
* Fix oneshot ## 1.0.0-alpha.2 - 2019-12-02
* Migrate to `std::future`.
## [1.0.0-alpha.2] - 2019-12-02
* 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.
### Removed
* 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.
### Changed
* 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.
### Deprecated * Deprecate `FramedTransport` in favor of `actix-ioframe`.
* Deprecate `CloneableService` as it is not safe and in general not very useful
* 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.
### Fixed
* Do not block on sink drop for FramedTransport
## [0.4.1] - 2019-05-15 ## 0.4.1 - 2019-05-15
* Change `Either` constructor.
### Changed
* Change `Either` constructor
## [0.4.0] - 2019-05-11 ## 0.4.0 - 2019-05-11
* Change `Either` to handle two nexted services.
* Upgrade actix-service 0.4.
* Removed framed related services.
* Removed stream related services.
### Changed
* Change `Either` to handle two nexted services
* Upgrade actix-service 0.4
### Deleted
* Framed related services
* Stream related services
## [0.3.5] - 2019-04-04
### Added
## 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.
### Changed
* Remove 'static constraint from Clonable service
## [0.3.4] - 2019-03-12 ## 0.3.4 - 2019-03-12
### Changed
* `TimeoutService`, `InOrderService`, `InFlightService` accepts generic IntoService services. * `TimeoutService`, `InOrderService`, `InFlightService` accepts generic IntoService services.
* Fix `InFlightService::poll_ready()` nested service readiness check.
### Fixed * Fix `InOrderService::poll_ready()` nested service readiness check.
* Fix `InFlightService::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.
### Changed * Add generic config param for IntoFramed and TakeOne new services.
* Revert IntoFuture change
* 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.
### Changed ## 0.3.1 - 2019-03-04
* Use new type of transform trait.
* Use IntoFuture for new services
## [0.3.1] - 2019-03-04 ## 0.3.0 - 2019-03-02
### Changed
* Use new type of transform trait
## [0.3.0] - 2019-03-02
### Changed
* 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
### Changed
* Custom `BoxedNewService` implementation. * Custom `BoxedNewService` implementation.
## [0.2.3] - 2019-02-21 ## 0.2.3 - 2019-02-21
* Add `BoxedNewService` and `BoxedService`.
### Added
* Add `BoxedNewService` and `BoxedService`
## [0.2.2] - 2019-02-11 ## 0.2.2 - 2019-02-11
* Add `Display` impl for `TimeoutError`.
### Added * Add `Display` impl for `InOrderError`.
* Add `Display` impl for `TimeoutError`
* Add `Display` impl for `InOrderError`
## [0.2.1] - 2019-02-06 ## 0.2.1 - 2019-02-06
### Added
* 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.
### Changed
* 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 Timeout service factory.
* Added Clone impl for Either service * Added Service and NewService for Stream dispatcher.
* Switch to actix-service 0.2.
* Added Clone impl for Timeout service factory
* Added Service and NewService for Stream dispatcher
* 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

@@ -1,8 +1,8 @@
[package] [package]
name = "actix-utils" name = "actix-utils"
version = "2.0.0" version = "3.0.0-beta.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Various network related services and utilities for the Actix ecosystem." description = "Various network related services and utilities for the Actix ecosystem"
keywords = ["network", "framework", "async", "futures"] keywords = ["network", "framework", "async", "futures"]
homepage = "https://actix.rs" homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git" repository = "https://github.com/actix/actix-net.git"
@@ -16,9 +16,9 @@ name = "actix_utils"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
actix-codec = "0.3.0" actix-codec = "0.4.0-beta.1"
actix-rt = "1.1.1" actix-rt = "2.0.0-beta.1"
actix-service = "1.0.6" actix-service = "2.0.0-beta.2"
futures-core = { version = "0.3.7", default-features = false } futures-core = { version = "0.3.7", default-features = false }
futures-sink = { version = "0.3.7", default-features = false } futures-sink = { version = "0.3.7", default-features = false }

View File

@@ -1,4 +1,4 @@
//! Actix utils - various helper services //! Various network related services and utilities for the Actix ecosystem.
#![deny(rust_2018_idioms, nonstandard_style)] #![deny(rust_2018_idioms, nonstandard_style)]
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]

View File

@@ -1,4 +1,5 @@
//! A multi-producer, single-consumer, futures-aware, FIFO queue. //! A multi-producer, single-consumer, futures-aware, FIFO queue.
use core::any::Any; use core::any::Any;
use core::cell::RefCell; use core::cell::RefCell;
use core::fmt; use core::fmt;

View File

@@ -1,7 +1,6 @@
//! Service that applies a timeout to requests. //! Service that applies a timeout to requests.
//! //!
//! If the response does not complete within the specified timeout, the response //! If the response does not complete within the specified timeout, the response will be aborted.
//! will be aborted.
use core::future::Future; use core::future::Future;
use core::marker::PhantomData; use core::marker::PhantomData;
@@ -9,7 +8,7 @@ use core::pin::Pin;
use core::task::{Context, Poll}; use core::task::{Context, Poll};
use core::{fmt, time}; use core::{fmt, time};
use actix_rt::time::{delay_for, Delay}; use actix_rt::time::{sleep, Sleep};
use actix_service::{IntoService, Service, Transform}; use actix_service::{IntoService, Service, Transform};
use pin_project_lite::pin_project; use pin_project_lite::pin_project;
@@ -85,8 +84,8 @@ where
{ {
type Response = S::Response; type Response = S::Response;
type Error = TimeoutError<S::Error>; type Error = TimeoutError<S::Error>;
type InitError = E;
type Transform = TimeoutService<S, Req>; type Transform = TimeoutService<S, Req>;
type InitError = E;
type Future = TimeoutFuture<Self::Transform, Self::InitError>; type Future = TimeoutFuture<Self::Transform, Self::InitError>;
fn new_transform(&self, service: S) -> Self::Future { fn new_transform(&self, service: S) -> Self::Future {
@@ -157,7 +156,7 @@ where
fn call(&mut self, request: Req) -> Self::Future { fn call(&mut self, request: Req) -> Self::Future {
TimeoutServiceResponse { TimeoutServiceResponse {
fut: self.service.call(request), fut: self.service.call(request),
sleep: delay_for(self.timeout), sleep: sleep(self.timeout),
} }
} }
} }
@@ -171,7 +170,8 @@ pin_project! {
{ {
#[pin] #[pin]
fut: S::Future, fut: S::Future,
sleep: Delay, #[pin]
sleep: Sleep,
} }
} }
@@ -193,20 +193,18 @@ where
} }
// Now check the sleep // Now check the sleep
Pin::new(this.sleep) this.sleep.poll(cx).map(|_| Err(TimeoutError::Timeout))
.poll(cx)
.map(|_| Err(TimeoutError::Timeout))
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::task::Poll; use core::task::Poll;
use std::time::Duration; use core::time::Duration;
use super::*; use super::*;
use actix_service::{apply, fn_factory, Service, ServiceFactory}; use actix_service::{apply, fn_factory, Service, ServiceFactory};
use futures_util::future::{ok, FutureExt, LocalBoxFuture}; use futures_core::future::LocalBoxFuture;
struct SleepService(Duration); struct SleepService(Duration);
@@ -218,9 +216,11 @@ mod tests {
actix_service::always_ready!(); actix_service::always_ready!();
fn call(&mut self, _: ()) -> Self::Future { fn call(&mut self, _: ()) -> Self::Future {
actix_rt::time::delay_for(self.0) let sleep = actix_rt::time::sleep(self.0);
.then(|_| ok::<_, ()>(())) Box::pin(async move {
.boxed_local() sleep.await;
Ok(())
})
} }
} }
@@ -249,7 +249,7 @@ mod tests {
let timeout = apply( let timeout = apply(
Timeout::new(resolution), Timeout::new(resolution),
fn_factory(|| ok::<_, ()>(SleepService(wait_time))), fn_factory(|| async { Ok::<_, ()>(SleepService(wait_time)) }),
); );
let mut srv = timeout.new_service(&()).await.unwrap(); let mut srv = timeout.new_service(&()).await.unwrap();

36
bytestring/CHANGES.md Normal file
View File

@@ -0,0 +1,36 @@
# Changes
## Unreleased - 2021-xx-xx
## 1.0.0 - 2020-12-31
* Update `bytes` dependency to `1`.
* 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`.
* `ByteString::new` is now a `const fn`.
* Crate is now `#[no_std]` compatible.
## 0.1.5 - 2020-03-30
* Serde support
## 0.1.4 - 2020-01-14
* Fix `AsRef<str>` impl
## 0.1.3 - 2020-01-13
* Add `PartialEq<T: AsRef<str>>`, `AsRef<[u8]>` impls
## 0.1.2 - 2019-12-22
* Fix `new()` method
* Make `ByteString::from_static()` and `ByteString::from_bytes_unchecked()` methods const.
## 0.1.1 - 2019-12-07
* Fix hash impl
## 0.1.0 - 2019-12-07
* Initial release

View File

@@ -1,9 +1,13 @@
[package] [package]
name = "bytestring" name = "bytestring"
version = "0.1.5" version = "1.0.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = [
description = "A UTF-8 encoded string with Bytes as a storage" "Nikolay Kim <fafhrd91@gmail.com>",
keywords = ["actix"] "Rob Ede <robjtede@icloud.com>",
]
description = "An immutable UTF-8 encoded string using Bytes as storage"
keywords = ["string", "bytes", "utf8", "web", "actix"]
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/" documentation = "https://docs.rs/bytestring/"
@@ -15,8 +19,9 @@ name = "bytestring"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
bytes = "0.5.3" bytes = "1"
serde = { version = "1.0", optional = true } serde = { version = "1.0", optional = true }
[dev-dependencies] [dev-dependencies]
serde_json = "1.0" serde_json = "1.0"
siphasher = "0.3"

View File

@@ -1,37 +1,38 @@
//! A UTF-8 encoded read-only string using Bytes as storage. //! A UTF-8 encoded read-only string using Bytes as storage.
#![no_std]
#![deny(rust_2018_idioms, nonstandard_style)] #![deny(rust_2018_idioms, nonstandard_style)]
#![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")]
use std::convert::TryFrom; extern crate alloc;
use std::{borrow, fmt, hash, ops, str};
use alloc::{string::String, vec::Vec};
use core::{borrow, convert::TryFrom, fmt, hash, ops, str};
use bytes::Bytes; use bytes::Bytes;
/// A UTF-8 encoded string with [`Bytes`] as a storage. /// An immutable UTF-8 encoded string with [`Bytes`] as a storage.
/// #[derive(Clone, Default, Eq, PartialOrd, Ord)]
/// [`Bytes`]: bytes::Bytes
#[derive(Clone, Eq, Ord, PartialOrd, Default)]
pub struct ByteString(Bytes); pub struct ByteString(Bytes);
impl ByteString { impl ByteString {
/// Creates a new `ByteString`. /// Creates a new empty `ByteString`.
pub fn new() -> Self { pub const fn new() -> Self {
ByteString(Bytes::new()) ByteString(Bytes::new())
} }
/// Get a reference to the underlying bytes object. /// Get a reference to the underlying `Bytes` object.
pub fn get_ref(&self) -> &Bytes { pub fn as_bytes(&self) -> &Bytes {
&self.0 &self.0
} }
/// Unwraps this `ByteString`, returning the underlying bytes object. /// Unwraps this `ByteString` into the underlying `Bytes` object.
pub fn into_inner(self) -> Bytes { pub fn into_bytes(self) -> Bytes {
self.0 self.0
} }
/// Creates a new `ByteString` from a static str. /// Creates a new `ByteString` from a `&'static str`.
pub const fn from_static(src: &'static str) -> ByteString { pub const fn from_static(src: &'static str) -> ByteString {
Self(Bytes::from_static(src.as_bytes())) Self(Bytes::from_static(src.as_bytes()))
} }
@@ -39,11 +40,10 @@ impl ByteString {
/// Creates a new `ByteString` from a Bytes. /// Creates a new `ByteString` from a Bytes.
/// ///
/// # Safety /// # Safety
/// This function is unsafe because it does not check the bytes passed to it /// This function is unsafe because it does not check the bytes passed to it are valid UTF-8.
/// are valid UTF-8. If this constraint is violated, /// If this constraint is violated, it may cause memory unsafety issues with future users of
/// it may cause memory unsafety issues with future users of the `ByteString`, /// the `ByteString`, as we assume that `ByteString`s are valid UTF-8. However, the most likely
/// as we assume that `ByteString`s are valid UTF-8. /// issue is that the data gets corrupted.
/// However, the most likely issue is that the data gets corrupted.
pub const unsafe fn from_bytes_unchecked(src: Bytes) -> ByteString { pub const unsafe fn from_bytes_unchecked(src: Bytes) -> ByteString {
Self(src) Self(src)
} }
@@ -84,8 +84,10 @@ impl ops::Deref for ByteString {
#[inline] #[inline]
fn deref(&self) -> &str { fn deref(&self) -> &str {
let b = self.0.as_ref(); let bytes = self.0.as_ref();
unsafe { str::from_utf8_unchecked(b) } // SAFETY:
// UTF-8 validity is guaranteed at during construction.
unsafe { str::from_utf8_unchecked(bytes) }
} }
} }
@@ -96,21 +98,24 @@ impl borrow::Borrow<str> for ByteString {
} }
impl From<String> for ByteString { impl From<String> for ByteString {
#[inline]
fn from(value: String) -> Self { fn from(value: String) -> Self {
Self(Bytes::from(value)) Self(Bytes::from(value))
} }
} }
impl<'a> From<&'a str> for ByteString { impl From<&str> for ByteString {
fn from(value: &'a str) -> Self { #[inline]
fn from(value: &str) -> Self {
Self(Bytes::copy_from_slice(value.as_ref())) Self(Bytes::copy_from_slice(value.as_ref()))
} }
} }
impl<'a> TryFrom<&'a [u8]> for ByteString { impl TryFrom<&[u8]> for ByteString {
type Error = str::Utf8Error; type Error = str::Utf8Error;
fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> { #[inline]
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
let _ = str::from_utf8(value)?; let _ = str::from_utf8(value)?;
Ok(ByteString(Bytes::copy_from_slice(value))) Ok(ByteString(Bytes::copy_from_slice(value)))
} }
@@ -119,15 +124,17 @@ impl<'a> TryFrom<&'a [u8]> for ByteString {
impl TryFrom<Vec<u8>> for ByteString { impl TryFrom<Vec<u8>> for ByteString {
type Error = str::Utf8Error; type Error = str::Utf8Error;
#[inline]
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> { fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
let _ = str::from_utf8(value.as_ref())?; let buf = String::from_utf8(value).map_err(|err| err.utf8_error())?;
Ok(ByteString(Bytes::from(value))) Ok(ByteString(Bytes::from(buf)))
} }
} }
impl TryFrom<Bytes> for ByteString { impl TryFrom<Bytes> for ByteString {
type Error = str::Utf8Error; type Error = str::Utf8Error;
#[inline]
fn try_from(value: Bytes) -> Result<Self, Self::Error> { fn try_from(value: Bytes) -> Result<Self, Self::Error> {
let _ = str::from_utf8(value.as_ref())?; let _ = str::from_utf8(value.as_ref())?;
Ok(ByteString(value)) Ok(ByteString(value))
@@ -137,8 +144,9 @@ impl TryFrom<Bytes> for ByteString {
impl TryFrom<bytes::BytesMut> for ByteString { impl TryFrom<bytes::BytesMut> for ByteString {
type Error = str::Utf8Error; type Error = str::Utf8Error;
#[inline]
fn try_from(value: bytes::BytesMut) -> Result<Self, Self::Error> { fn try_from(value: bytes::BytesMut) -> Result<Self, Self::Error> {
let _ = str::from_utf8(value.as_ref())?; let _ = str::from_utf8(&value)?;
Ok(ByteString(value.freeze())) Ok(ByteString(value.freeze()))
} }
} }
@@ -146,10 +154,20 @@ impl TryFrom<bytes::BytesMut> for ByteString {
macro_rules! array_impls { macro_rules! array_impls {
($($len:expr)+) => { ($($len:expr)+) => {
$( $(
impl<'a> TryFrom<&'a [u8; $len]> for ByteString { impl TryFrom<[u8; $len]> for ByteString {
type Error = str::Utf8Error; type Error = str::Utf8Error;
fn try_from(value: &'a [u8; $len]) -> Result<Self, Self::Error> { #[inline]
fn try_from(value: [u8; $len]) -> Result<Self, Self::Error> {
ByteString::try_from(&value[..])
}
}
impl TryFrom<&[u8; $len]> for ByteString {
type Error = str::Utf8Error;
#[inline]
fn try_from(value: &[u8; $len]) -> Result<Self, Self::Error> {
ByteString::try_from(&value[..]) ByteString::try_from(&value[..])
} }
} }
@@ -157,7 +175,7 @@ macro_rules! array_impls {
} }
} }
array_impls!(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16); array_impls!(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32);
impl fmt::Debug for ByteString { impl fmt::Debug for ByteString {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -173,6 +191,8 @@ impl fmt::Display for ByteString {
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
mod serde { mod serde {
use alloc::string::String;
use serde::de::{Deserialize, Deserializer}; use serde::de::{Deserialize, Deserializer};
use serde::ser::{Serialize, Serializer}; use serde::ser::{Serialize, Serializer};
@@ -201,16 +221,19 @@ mod serde {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use alloc::borrow::ToOwned;
use core::hash::{Hash, Hasher};
use siphasher::sip::SipHasher;
use super::*; use super::*;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
#[test] #[test]
fn test_partial_eq() { fn test_partial_eq() {
let s: ByteString = ByteString::from_static("test"); let s: ByteString = ByteString::from_static("test");
assert_eq!(s, "test"); assert_eq!(s, "test");
assert_eq!(s, *"test"); assert_eq!(s, *"test");
assert_eq!(s, "test".to_string()); assert_eq!(s, "test".to_owned());
} }
#[test] #[test]
@@ -220,10 +243,10 @@ mod test {
#[test] #[test]
fn test_hash() { fn test_hash() {
let mut hasher1 = DefaultHasher::default(); let mut hasher1 = SipHasher::default();
"str".hash(&mut hasher1); "str".hash(&mut hasher1);
let mut hasher2 = DefaultHasher::default(); let mut hasher2 = SipHasher::default();
let s = ByteString::from_static("str"); let s = ByteString::from_static("str");
s.hash(&mut hasher2); s.hash(&mut hasher2);
assert_eq!(hasher1.finish(), hasher2.finish()); assert_eq!(hasher1.finish(), hasher2.finish());
@@ -231,7 +254,7 @@ mod test {
#[test] #[test]
fn test_from_string() { fn test_from_string() {
let s: ByteString = "hello".to_string().into(); let s: ByteString = "hello".to_owned().into();
assert_eq!(&s, "hello"); assert_eq!(&s, "hello");
let t: &str = s.as_ref(); let t: &str = s.as_ref();
assert_eq!(t, "hello"); assert_eq!(t, "hello");
@@ -249,17 +272,25 @@ mod test {
} }
#[test] #[test]
fn test_try_from_rbytes() { fn test_try_from_slice() {
let _ = ByteString::try_from(b"nice bytes").unwrap(); let _ = ByteString::try_from(b"nice bytes").unwrap();
} }
#[test]
fn test_try_from_array() {
assert_eq!(
ByteString::try_from([b'h', b'i']).unwrap(),
ByteString::from_static("hi")
);
}
#[test] #[test]
fn test_try_from_bytes() { fn test_try_from_bytes() {
let _ = ByteString::try_from(Bytes::from_static(b"nice bytes")).unwrap(); let _ = ByteString::try_from(Bytes::from_static(b"nice bytes")).unwrap();
} }
#[test] #[test]
fn test_try_from_bytesmut() { fn test_try_from_bytes_mut() {
let _ = ByteString::try_from(bytes::BytesMut::from(&b"nice bytes"[..])).unwrap(); let _ = ByteString::try_from(bytes::BytesMut::from(&b"nice bytes"[..])).unwrap();
} }

View File

@@ -1 +0,0 @@
../LICENSE-APACHE

View File

@@ -1 +0,0 @@
../LICENSE-MIT

View File

@@ -1,27 +0,0 @@
# Changes
## [0.1.5] - 2020-03-30
* Serde support
## [0.1.4] - 2020-01-14
* Fix `AsRef<str>` impl
## [0.1.3] - 2020-01-13
* Add `PartialEq<T: AsRef<str>>`, `AsRef<[u8]>` impls
## [0.1.2] - 2019-12-22
* Fix `new()` method
* Make `ByteString::from_static()` and `ByteString::from_bytes_unchecked()` methods const.
## [0.1.1] - 2019-12-07
* Fix hash impl
## [0.1.0] - 2019-12-07
* Initial release

View File

@@ -1 +0,0 @@
../LICENSE-APACHE

View File

@@ -1 +0,0 @@
../LICENSE-MIT