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

Compare commits

..

4 Commits

Author SHA1 Message Date
jwdeitch
bfa98627b4 let's encrypt - wip 2019-08-07 08:25:16 -04:00
jwdeitch
2a26c87c36 let's encrypt - wip 2019-08-07 08:05:16 -04:00
jwdeitch
e976758d92 let's encrypt - wip 2019-08-07 07:55:09 -04:00
jwdeitch
e1ee3a1c32 let's encrypt - wip 2019-08-06 23:12:48 -04:00
174 changed files with 8434 additions and 8664 deletions

41
.appveyor.yml Normal file
View File

@@ -0,0 +1,41 @@
environment:
global:
PROJECT_NAME: actix-net
matrix:
# Stable channel
- TARGET: i686-pc-windows-msvc
CHANNEL: stable
- TARGET: x86_64-pc-windows-gnu
CHANNEL: stable
- TARGET: x86_64-pc-windows-msvc
CHANNEL: stable
# Nightly channel
- TARGET: i686-pc-windows-msvc
CHANNEL: nightly
- TARGET: x86_64-pc-windows-gnu
CHANNEL: nightly
- TARGET: x86_64-pc-windows-msvc
CHANNEL: nightly
# Install Rust and Cargo
# (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml)
install:
- ps: >-
If ($Env:TARGET -eq 'x86_64-pc-windows-gnu') {
$Env:PATH += ';C:\msys64\mingw64\bin'
} ElseIf ($Env:TARGET -eq 'i686-pc-windows-gnu') {
$Env:PATH += ';C:\MinGW\bin'
}
- curl -sSf -o rustup-init.exe https://win.rustup.rs
- rustup-init.exe --default-host %TARGET% --default-toolchain %CHANNEL% -y
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
- rustc -Vv
- cargo -V
# 'cargo test' takes care of building for us, so disable Appveyor's build stage.
build: false
# Equivalent to Travis' `script` phase
test_script:
- cargo clean
- cargo test

View File

@@ -1,24 +0,0 @@
## PR Type
<!-- What kind of change does this PR make? -->
<!-- Bug Fix / Feature / Refactor / Code Style / Other -->
INSERT_PR_TYPE
## PR Checklist
Check your PR fulfills the following:
<!-- For draft PRs check the boxes as you complete them. -->
- [ ] Tests for the changes have been added / updated.
- [ ] Documentation comments have been added / updated.
- [ ] A changelog entry has been made for the appropriate packages.
- [ ] Format code with the latest stable rustfmt
## Overview
<!-- Describe the current and new behavior. -->
<!-- Emphasize any breaking changes. -->
<!-- If this PR fixes or closes an issue, reference it here. -->
<!-- Closes #000 -->

View File

@@ -1,29 +0,0 @@
name: Benchmark (Linux)
on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
- '1.0'
jobs:
check_benchmark:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
profile: minimal
override: true
- name: Check benchmark
uses: actions-rs/cargo@v1
with:
command: bench
args: --package=actix-service

View File

@@ -1,34 +0,0 @@
on:
pull_request:
types: [opened, synchronize, reopened]
name: Clippy and rustfmt Check
jobs:
clippy_check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
components: rustfmt
profile: minimal
override: true
- name: Check with rustfmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly
components: clippy
profile: minimal
override: true
- name: Check with Clippy
uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features --all --tests

View File

@@ -1,82 +0,0 @@
name: CI (Linux)
on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
- '1.0'
jobs:
build_and_test:
strategy:
fail-fast: false
matrix:
version:
- 1.42.0
- stable
- nightly
name: ${{ matrix.version }} - x86_64-unknown-linux-gnu
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.version }}-x86_64-unknown-linux-gnu
profile: minimal
override: true
- name: Generate Cargo.lock
uses: actions-rs/cargo@v1
with:
command: generate-lockfile
- name: Cache cargo dirs
uses: actions/cache@v2
with:
path:
~/.cargo/registry
~/.cargo/git
~/.cargo/bin
key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-cargo-trimmed-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v2
with:
path: target
key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-cargo-build-trimmed-${{ hashFiles('**/Cargo.lock') }}
- name: check build
uses: actions-rs/cargo@v1
with:
command: check
args: --all --bins --examples --tests
- name: tests
uses: actions-rs/cargo@v1
timeout-minutes: 40
with:
command: test
args: --all --all-features --no-fail-fast -- --nocapture
- name: Generate coverage file
if: matrix.version == 'stable' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request')
run: |
cargo install cargo-tarpaulin
cargo tarpaulin --out Xml --workspace --all-features
- name: Upload to Codecov
if: matrix.version == 'stable' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request')
uses: codecov/codecov-action@v1
with:
file: cobertura.xml
- name: Clear the cargo caches
run: |
rustup update stable
rustup override set stable
cargo install cargo-cache --no-default-features --features ci-autoclean
cargo-cache

View File

@@ -1,43 +0,0 @@
name: CI (macOS)
on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
- '1.0'
jobs:
build_and_test:
strategy:
fail-fast: false
matrix:
version:
- stable
- nightly
name: ${{ matrix.version }} - x86_64-apple-darwin
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.version }}-x86_64-apple-darwin
profile: minimal
override: true
- name: check build
uses: actions-rs/cargo@v1
with:
command: check
args: --all --bins --examples --tests
- name: tests
uses: actions-rs/cargo@v1
with:
command: test
args: --all --all-features --no-fail-fast -- --nocapture

View File

@@ -1,45 +0,0 @@
name: CI (Windows-mingw)
on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
- '1.0'
jobs:
build_and_test:
strategy:
fail-fast: false
matrix:
version:
- stable
- nightly
name: ${{ matrix.version }} - x86_64-pc-windows-gnu
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.version }}-x86_64-pc-windows-gnu
profile: minimal
override: true
- name: Install MSYS2
uses: msys2/setup-msys2@v2
- name: Install packages
run: |
msys2 -c 'pacman -Sy --noconfirm pacman'
msys2 -c 'pacman --noconfirm -S base-devel pkg-config'
- name: check build
uses: actions-rs/cargo@v1
with:
command: check
args: --all --bins --examples --tests

View File

@@ -1,69 +0,0 @@
name: CI (Windows)
on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
- '1.0'
env:
VCPKGRS_DYNAMIC: 1
jobs:
build_and_test:
strategy:
fail-fast: false
matrix:
version:
- stable
- nightly
target:
- x86_64-pc-windows-msvc
- i686-pc-windows-msvc
name: ${{ matrix.version }} - ${{ matrix.target }}
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.version }}-${{ matrix.target }}
profile: minimal
override: true
- name: Install OpenSSL (x64)
if: matrix.target == 'x86_64-pc-windows-msvc'
run: |
vcpkg integrate install
vcpkg install openssl:x64-windows
Get-ChildItem C:\vcpkg\installed\x64-windows\bin
Get-ChildItem C:\vcpkg\installed\x64-windows\lib
Copy-Item C:\vcpkg\installed\x64-windows\bin\libcrypto-1_1-x64.dll C:\vcpkg\installed\x64-windows\bin\libcrypto.dll
Copy-Item C:\vcpkg\installed\x64-windows\bin\libssl-1_1-x64.dll C:\vcpkg\installed\x64-windows\bin\libssl.dll
- name: Install OpenSSL (x86)
if: matrix.target == 'i686-pc-windows-msvc'
run: |
vcpkg integrate install
vcpkg install openssl:x86-windows
Get-ChildItem C:\vcpkg\installed\x86-windows\bin
Get-ChildItem C:\vcpkg\installed\x86-windows\lib
Copy-Item C:\vcpkg\installed\x86-windows\bin\libcrypto-1_1.dll C:\vcpkg\installed\x86-windows\bin\libcrypto.dll
Copy-Item C:\vcpkg\installed\x86-windows\bin\libssl-1_1.dll C:\vcpkg\installed\x86-windows\bin\libssl.dll
- name: check build
uses: actions-rs/cargo@v1
with:
command: check
args: --all --bins --examples --tests
- name: tests
uses: actions-rs/cargo@v1
with:
command: test
args: --all --all-features --no-fail-fast -- --nocapture

2
.gitignore vendored
View File

@@ -12,5 +12,3 @@ guide/build/
# These are backup files generated by rustfmt # These are backup files generated by rustfmt
**/*.rs.bk **/*.rs.bk
.idea

49
.travis.yml Normal file
View File

@@ -0,0 +1,49 @@
language: rust
sudo: required
dist: trusty
cache:
cargo: true
apt: true
matrix:
include:
- rust: stable
- rust: beta
- rust: nightly-2019-06-15
allow_failures:
- rust: nightly-2019-06-15
env:
global:
- RUSTFLAGS="-C link-dead-code"
- OPENSSL_VERSION=openssl-1.0.2
before_install:
- sudo add-apt-repository -y ppa:0k53d-karl-f830m/openssl
- sudo apt-get update -qq
- sudo apt-get install -y openssl libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev
before_cache: |
if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-06-15" ]]; then
RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install --version 0.6.11 cargo-tarpaulin
fi
# Add clippy
before_script:
- export PATH=$PATH:~/.cargo/bin
script:
- |
if [[ "$TRAVIS_RUST_VERSION" != "nightly-2019-06-15" ]]; then
cargo clean
cargo test --all --all-features -- --nocapture
fi
after_success:
- |
if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-06-15" ]]; then
taskset -c 0 cargo tarpaulin --all --all-features --out Xml
echo "Uploaded code coverage"
bash <(curl -s https://codecov.io/bash)
fi

68
CHANGES.md Normal file
View File

@@ -0,0 +1,68 @@
# Changes
## [0.3.0] - xxx
* Split `Service` trait to separate crate
* Use new `Service<Request>` trait
## [0.2.4] - 2018-11-21
### Added
* Allow to skip name resolution stage in Connector
## [0.2.3] - 2018-11-17
### Added
* Framed::is_write_buf_empty() checks if write buffer is flushed
## [0.2.2] - 2018-11-14
### Added
* Add low/high caps to Framed
### Changed
* Refactor Connector and Resolver services
### Fixed
* Fix wrong service to socket binding
## [0.2.0] - 2018-11-08
### Added
* Timeout service
* Added ServiceConfig and ServiceRuntime for server service configuration
### Changed
* Connector has been refactored
* timer and LowResTimer renamed to time and LowResTime
* Refactored `Server::configure()` method
## [0.1.1] - 2018-10-10
### Changed
- Set actix min version - 0.7.5
- Set trust-dns min version
## [0.1.0] - 2018-10-08
* Initial impl

View File

@@ -1,31 +1,42 @@
[package]
name = "actix-net"
version = "0.3.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix net - framework for the composable network services for Rust"
readme = "README.md"
keywords = ["network", "framework", "async", "futures"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-net/"
categories = ["network-programming", "asynchronous"]
license = "MIT/Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
edition = "2018"
[workspace] [workspace]
members = [ members = [
"actix-codec", "actix-codec",
"actix-connect", "actix-connect",
"actix-lets-encrypt",
"actix-rt", "actix-rt",
"actix-macros",
"actix-service", "actix-service",
"actix-server", "actix-server",
"actix-testing", "actix-server-config",
"actix-test-server",
"actix-threadpool", "actix-threadpool",
"actix-tls", "actix-tower",
"actix-tracing", "actix-ioframe",
"actix-utils", "actix-utils",
"router", "router",
"string",
] ]
[patch.crates-io] [dev-dependencies]
actix-codec = { path = "actix-codec" } actix-service = "0.4.0"
actix-connect = { path = "actix-connect" } actix-codec = "0.1.1"
actix-rt = { path = "actix-rt" } actix-rt = "0.2.0"
actix-macros = { path = "actix-macros" } actix-server = { version="0.5.0", features=["ssl"] }
actix-server = { path = "actix-server" } env_logger = "0.6"
actix-service = { path = "actix-service" } futures = "0.1.25"
actix-testing = { path = "actix-testing" } openssl = "0.10"
actix-threadpool = { path = "actix-threadpool" } tokio-tcp = "0.1"
actix-tls = { path = "actix-tls" } tokio-openssl = "0.3"
actix-tracing = { path = "actix-tracing" }
actix-utils = { path = "actix-utils" }
actix-router = { path = "router" }
bytestring = { path = "string" }

View File

@@ -1,20 +1,13 @@
# 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 [![Build Status](https://travis-ci.org/actix/actix-net.svg?branch=master)](https://travis-ci.org/actix/actix-net) [![codecov](https://codecov.io/gh/actix/actix-net/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-net) [![crates.io](https://meritbadge.herokuapp.com/actix-net)](https://crates.io/crates/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 - framework for composable network services Actix net - framework for composable network services
## Build statuses
| 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)") |
| macOS | [![build status](https://github.com/actix/actix-net/workflows/CI%20%28macOS%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-net/actions?query=workflow%3A"CI+(macOS)") |
| 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)") |
## Documentation & community resources ## Documentation & community resources
* [Chat on Gitter](https://gitter.im/actix/actix) * [API Documentation (Development)](https://actix.rs/actix-net/actix_net/)
* Minimum supported Rust version: 1.42 or later * [Chat on gitter](https://gitter.im/actix/actix)
* Cargo package: [actix-net](https://crates.io/crates/actix-net)
* Minimum supported Rust version: 1.32 or later
## Example ## Example
@@ -39,16 +32,14 @@ fn main() -> io::Result<()> {
let num = num.clone(); let num = num.clone();
let acceptor = acceptor.clone(); let acceptor = acceptor.clone();
// construct transformation pipeline // service for converting incoming TcpStream to a SslStream<TcpStream>
pipeline( fn_service(move |stream: Io<tokio_tcp::TcpStream>| {
// service for converting incoming TcpStream to a SslStream<TcpStream> SslAcceptorExt::accept_async(&acceptor, stream.into_parts().0)
fn_service(move |stream: actix_rt::net::TcpStream| async move { .map_err(|e| println!("Openssl error: {}", e))
SslAcceptorExt::accept_async(&acceptor, stream.into_parts().0).await })
.map_err(|e| println!("Openssl error: {}", e)) // .and_then() combinator uses other service to convert incoming `Request` to a
})) // `Response` and then uses that response as an input for next
// .and_then() combinator chains result of previos service call to argument // service. in this case, on success we use `logger` service
/// for next service calll. in this case, on success we chain
/// ssl stream to the `logger` service.
.and_then(fn_service(logger)) .and_then(fn_service(logger))
// Next service counts number of connections // Next service counts number of connections
.and_then(move |_| { .and_then(move |_| {

View File

@@ -1,48 +1,15 @@
# Changes # Changes
## Unreleased - 2020-xx-xx
## 0.3.0 - 2020-08-23
* No changes from beta 2.
## 0.3.0-beta.2 - 2020-08-19
* Remove unused type parameter from `Framed::replace_codec`.
## 0.3.0-beta.1 - 2020-08-19
* Use `.advance()` instead of `.split_to()`.
* Upgrade `tokio-util` to `0.3`.
* Improve `BytesCodec` `.encode()` performance
* Simplify `BytesCodec` `.decode()`
* Rename methods on `Framed` to better describe their use.
* Add method on `Framed` to get a pinned reference to the underlying I/O.
* Add method on `Framed` check emptiness of read buffer.
## [0.2.0] - 2019-12-10
* Use specific futures dependencies
## [0.2.0-alpha.4]
* Fix buffer remaining capacity calculation
## [0.2.0-alpha.3]
* Use tokio 0.2
* Fix low/high watermark for write/read buffers
## [0.2.0-alpha.2]
* 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,26 +1,25 @@
[package] [package]
name = "actix-codec" name = "actix-codec"
version = "0.3.0" version = "0.1.2"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Codec utilities for working with framed protocols." description = "Utilities for encoding and decoding frames"
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"
documentation = "https://docs.rs/actix-codec/" documentation = "https://docs.rs/actix-codec/"
categories = ["network-programming", "asynchronous"] categories = ["network-programming", "asynchronous"]
license = "MIT OR Apache-2.0" license = "MIT/Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
edition = "2018" edition = "2018"
workspace = ".."
[lib] [lib]
name = "actix_codec" name = "actix_codec"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
bitflags = "1.2.1" bytes = "0.4.12"
bytes = "0.5.2" futures = "0.1.24"
futures-core = { version = "0.3.4", default-features = false } tokio-io = "0.1.12"
futures-sink = { version = "0.3.4", default-features = false } tokio-codec = "0.1.1"
log = "0.4" log = "0.4"
pin-project = "0.4.17"
tokio = { version = "0.2.5", default-features = false }
tokio-util = { version = "0.3.1", default-features = false, features = ["codec"] }

View File

@@ -1,7 +1,7 @@
use bytes::{Buf, Bytes, BytesMut};
use std::io; use std::io;
use super::{Decoder, Encoder}; use bytes::{Bytes, BytesMut};
use tokio_codec::{Decoder, Encoder};
/// Bytes codec. /// Bytes codec.
/// ///
@@ -9,12 +9,12 @@ use super::{Decoder, Encoder};
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct BytesCodec; pub struct BytesCodec;
impl Encoder<Bytes> for BytesCodec { impl Encoder for BytesCodec {
type Item = Bytes;
type Error = io::Error; type Error = io::Error;
#[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[..]);
Ok(()) Ok(())
} }
} }
@@ -27,7 +27,7 @@ impl Decoder for BytesCodec {
if src.is_empty() { if src.is_empty() {
Ok(None) Ok(None)
} else { } else {
Ok(Some(src.split())) Ok(Some(src.take()))
} }
} }
} }

View File

@@ -1,73 +1,118 @@
use std::pin::Pin; #![allow(deprecated)]
use std::task::{Context, Poll};
use std::{fmt, io};
use bytes::{Buf, BytesMut}; use std::fmt;
use futures_core::{ready, Stream}; use std::io::{self, Read, Write};
use futures_sink::Sink;
use pin_project::pin_project;
use crate::{AsyncRead, AsyncWrite, Decoder, Encoder}; use bytes::BytesMut;
use futures::{Poll, Sink, StartSend, Stream};
use tokio_codec::{Decoder, Encoder};
use tokio_io::{AsyncRead, AsyncWrite};
use super::framed_read::{framed_read2, framed_read2_with_buffer, FramedRead2};
use super::framed_write::{framed_write2, framed_write2_with_buffer, FramedWrite2};
/// Low-water mark
const LW: usize = 1024; const LW: usize = 1024;
/// High-water mark
const HW: usize = 8 * 1024; const HW: usize = 8 * 1024;
bitflags::bitflags! {
struct Flags: u8 {
const EOF = 0b0001;
const READABLE = 0b0010;
}
}
/// A unified `Stream` and `Sink` interface to an underlying I/O object, using /// A unified `Stream` and `Sink` interface to an underlying I/O object, using
/// the `Encoder` and `Decoder` traits to encode and decode frames. /// the `Encoder` and `Decoder` traits to encode and decode frames.
/// ///
/// Raw I/O objects work with byte sequences, but higher-level code usually /// You can create a `Framed` instance by using the `AsyncRead::framed` adapter.
/// wants to batch these into meaningful chunks, called "frames". This
/// method layers framing on top of an I/O object, by using the `Encoder`/`Decoder`
/// traits to handle encoding and decoding of message frames. Note that
/// the incoming and outgoing frame types may be distinct.
#[pin_project]
pub struct Framed<T, U> { pub struct Framed<T, U> {
#[pin] inner: FramedRead2<FramedWrite2<Fuse<T, U>>>,
io: T,
codec: U,
flags: Flags,
read_buf: BytesMut,
write_buf: BytesMut,
} }
pub struct Fuse<T, U>(pub T, pub U);
impl<T, U> Framed<T, U> impl<T, U> Framed<T, U>
where where
T: AsyncRead + AsyncWrite, T: AsyncRead + AsyncWrite,
U: Decoder, U: Decoder + Encoder,
{ {
/// Provides a `Stream` and `Sink` interface for reading and writing to this
/// `Io` object, using `Decode` and `Encode` to read and write the raw data.
///
/// Raw I/O objects work with byte sequences, but higher-level code usually
/// wants to batch these into meaningful chunks, called "frames". This
/// method layers framing on top of an I/O object, by using the `Codec`
/// traits to handle encoding and decoding of messages frames. Note that
/// the incoming and outgoing frame types may be distinct.
///
/// This function returns a *single* object that is both `Stream` and /// This function returns a *single* object that is both `Stream` and
/// `Sink`; grouping this into a single object is often useful for layering /// `Sink`; grouping this into a single object is often useful for layering
/// things like gzip or TLS, which require both read and write access to the /// things like gzip or TLS, which require both read and write access to the
/// underlying object. /// underlying object.
pub fn new(io: T, codec: U) -> Framed<T, U> { ///
/// If you want to work more directly with the streams and sink, consider
/// calling `split` on the `Framed` returned by this method, which will
/// break them into separate objects, allowing them to interact more easily.
pub fn new(inner: T, codec: U) -> Framed<T, U> {
Framed { Framed {
io, inner: framed_read2(framed_write2(Fuse(inner, codec), LW, HW)),
codec,
flags: Flags::empty(),
read_buf: BytesMut::with_capacity(HW),
write_buf: BytesMut::with_capacity(HW),
} }
} }
/// Same as `Framed::new()` with ability to specify write buffer low/high capacity watermarks.
pub fn new_with_caps(inner: T, codec: U, lw: usize, hw: usize) -> Framed<T, U> {
debug_assert!((lw < hw) && hw != 0);
Framed {
inner: framed_read2(framed_write2(Fuse(inner, codec), lw, hw)),
}
}
/// Force send item
pub fn force_send(
&mut self,
item: <U as Encoder>::Item,
) -> Result<(), <U as Encoder>::Error> {
self.inner.get_mut().force_send(item)
}
} }
impl<T, U> Framed<T, U> { impl<T, U> Framed<T, U> {
/// Provides a `Stream` and `Sink` interface for reading and writing to this
/// `Io` object, using `Decode` and `Encode` to read and write the raw data.
///
/// Raw I/O objects work with byte sequences, but higher-level code usually
/// wants to batch these into meaningful chunks, called "frames". This
/// method layers framing on top of an I/O object, by using the `Codec`
/// traits to handle encoding and decoding of messages frames. Note that
/// the incoming and outgoing frame types may be distinct.
///
/// This function returns a *single* object that is both `Stream` and
/// `Sink`; grouping this into a single object is often useful for layering
/// things like gzip or TLS, which require both read and write access to the
/// underlying object.
///
/// This objects takes a stream and a readbuffer and a writebuffer. These
/// field can be obtained from an existing `Framed` with the
/// `into_parts` method.
///
/// If you want to work more directly with the streams and sink, consider
/// calling `split` on the `Framed` returned by this method, which will
/// break them into separate objects, allowing them to interact more easily.
pub fn from_parts(parts: FramedParts<T, U>) -> Framed<T, U> {
Framed {
inner: framed_read2_with_buffer(
framed_write2_with_buffer(
Fuse(parts.io, parts.codec),
parts.write_buf,
parts.write_buf_lw,
parts.write_buf_hw,
),
parts.read_buf,
),
}
}
/// Returns a reference to the underlying codec. /// Returns a reference to the underlying codec.
pub fn codec_ref(&self) -> &U { pub fn get_codec(&self) -> &U {
&self.codec &self.inner.get_ref().get_ref().1
} }
/// Returns a mutable reference to the underlying codec. /// Returns a mutable reference to the underlying codec.
pub fn codec_mut(&mut self) -> &mut U { pub fn get_codec_mut(&mut self) -> &mut U {
&mut self.codec &mut self.inner.get_mut().get_mut().1
} }
/// Returns a reference to the underlying I/O stream wrapped by /// Returns a reference to the underlying I/O stream wrapped by
@@ -76,284 +121,81 @@ impl<T, U> Framed<T, U> {
/// Note that care should be taken to not tamper with the underlying stream /// Note that care should be taken to not tamper with the underlying stream
/// of data coming in as it may corrupt the stream of frames otherwise /// of data coming in as it may corrupt the stream of frames otherwise
/// being worked with. /// being worked with.
pub fn io_ref(&self) -> &T { pub fn get_ref(&self) -> &T {
&self.io &self.inner.get_ref().get_ref().0
} }
/// Returns a mutable reference to the underlying I/O stream. /// Returns a mutable reference to the underlying I/O stream wrapped by
/// `Frame`.
/// ///
/// Note that care should be taken to not tamper with the underlying stream /// Note that care should be taken to not tamper with the underlying stream
/// of data coming in as it may corrupt the stream of frames otherwise /// of data coming in as it may corrupt the stream of frames otherwise
/// being worked with. /// being worked with.
pub fn io_mut(&mut self) -> &mut T { pub fn get_mut(&mut self) -> &mut T {
&mut self.io &mut self.inner.get_mut().get_mut().0
}
/// Returns a `Pin` of a mutable reference to the underlying I/O stream.
pub fn io_pin(self: Pin<&mut Self>) -> Pin<&mut T> {
self.project().io
}
/// Check if read buffer is empty.
pub fn is_read_buf_empty(&self) -> bool {
self.read_buf.is_empty()
} }
/// Check if write buffer is empty. /// Check if write buffer is empty.
pub fn is_write_buf_empty(&self) -> bool { pub fn is_write_buf_empty(&self) -> bool {
self.write_buf.is_empty() self.inner.get_ref().is_empty()
} }
/// Check if write buffer is full. /// Check if write buffer is full.
pub fn is_write_buf_full(&self) -> bool { pub fn is_write_buf_full(&self) -> bool {
self.write_buf.len() >= HW self.inner.get_ref().is_full()
} }
/// Check if framed is able to write more data. /// Consumes the `Frame`, returning its underlying I/O stream.
/// ///
/// `Framed` object considers ready if there is free space in write buffer. /// Note that care should be taken to not tamper with the underlying stream
pub fn is_write_ready(&self) -> bool { /// of data coming in as it may corrupt the stream of frames otherwise
self.write_buf.len() < HW /// being worked with.
pub fn into_inner(self) -> T {
self.inner.into_inner().into_inner().0
} }
/// Consume the `Frame`, returning `Frame` with different codec. /// Consume the `Frame`, returning `Frame` with different codec.
pub fn replace_codec<U2>(self, codec: U2) -> Framed<T, U2> { pub fn into_framed<U2>(self, codec: U2) -> Framed<T, U2> {
let (inner, read_buf) = self.inner.into_parts();
let (inner, write_buf, lw, hw) = inner.into_parts();
Framed { Framed {
codec, inner: framed_read2_with_buffer(
io: self.io, framed_write2_with_buffer(Fuse(inner.0, codec), write_buf, lw, hw),
flags: self.flags, read_buf,
read_buf: self.read_buf, ),
write_buf: self.write_buf,
} }
} }
/// Consume the `Frame`, returning `Frame` with different io. /// Consume the `Frame`, returning `Frame` with different io.
pub fn into_map_io<F, T2>(self, f: F) -> Framed<T2, U> pub fn map_io<F, T2>(self, f: F) -> Framed<T2, U>
where where
F: Fn(T) -> T2, F: Fn(T) -> T2,
{ {
let (inner, read_buf) = self.inner.into_parts();
let (inner, write_buf, lw, hw) = inner.into_parts();
Framed { Framed {
io: f(self.io), inner: framed_read2_with_buffer(
codec: self.codec, framed_write2_with_buffer(Fuse(f(inner.0), inner.1), write_buf, lw, hw),
flags: self.flags, read_buf,
read_buf: self.read_buf, ),
write_buf: self.write_buf,
} }
} }
/// Consume the `Frame`, returning `Frame` with different codec. /// Consume the `Frame`, returning `Frame` with different codec.
pub fn into_map_codec<F, U2>(self, f: F) -> Framed<T, U2> pub fn map_codec<F, U2>(self, f: F) -> Framed<T, U2>
where where
F: Fn(U) -> U2, F: Fn(U) -> U2,
{ {
let (inner, read_buf) = self.inner.into_parts();
let (inner, write_buf, lw, hw) = inner.into_parts();
Framed { Framed {
io: self.io, inner: framed_read2_with_buffer(
codec: f(self.codec), framed_write2_with_buffer(Fuse(inner.0, f(inner.1)), write_buf, lw, hw),
flags: self.flags, read_buf,
read_buf: self.read_buf, ),
write_buf: self.write_buf,
}
}
}
impl<T, U> Framed<T, U> {
/// Serialize item and Write to the inner buffer
pub fn write<I>(mut self: Pin<&mut Self>, item: I) -> Result<(), <U as Encoder<I>>::Error>
where
T: AsyncWrite,
U: Encoder<I>,
{
let this = self.as_mut().project();
let remaining = this.write_buf.capacity() - this.write_buf.len();
if remaining < LW {
this.write_buf.reserve(HW - remaining);
}
this.codec.encode(item, this.write_buf)?;
Ok(())
}
/// Try to read underlying I/O stream and decode item.
pub fn next_item(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<<U as Decoder>::Item, U::Error>>>
where
T: AsyncRead,
U: Decoder,
{
loop {
let mut this = self.as_mut().project();
// Repeatedly call `decode` or `decode_eof` as long as it is
// "readable". Readable is defined as not having returned `None`. If
// the upstream has returned EOF, and the decoder is no longer
// readable, it can be assumed that the decoder will never become
// readable again, at which point the stream is terminated.
if this.flags.contains(Flags::READABLE) {
if this.flags.contains(Flags::EOF) {
match this.codec.decode_eof(&mut this.read_buf) {
Ok(Some(frame)) => return Poll::Ready(Some(Ok(frame))),
Ok(None) => return Poll::Ready(None),
Err(e) => return Poll::Ready(Some(Err(e))),
}
}
log::trace!("attempting to decode a frame");
match this.codec.decode(&mut this.read_buf) {
Ok(Some(frame)) => {
log::trace!("frame decoded from buffer");
return Poll::Ready(Some(Ok(frame)));
}
Err(e) => return Poll::Ready(Some(Err(e))),
_ => (), // Need more data
}
this.flags.remove(Flags::READABLE);
}
debug_assert!(!this.flags.contains(Flags::EOF));
// Otherwise, try to read more data and try again. Make sure we've got room
let remaining = this.read_buf.capacity() - this.read_buf.len();
if remaining < LW {
this.read_buf.reserve(HW - remaining)
}
let cnt = match this.io.poll_read_buf(cx, &mut this.read_buf) {
Poll::Pending => return Poll::Pending,
Poll::Ready(Err(e)) => return Poll::Ready(Some(Err(e.into()))),
Poll::Ready(Ok(cnt)) => cnt,
};
if cnt == 0 {
this.flags.insert(Flags::EOF);
}
this.flags.insert(Flags::READABLE);
}
}
/// Flush write buffer to underlying I/O stream.
pub fn flush<I>(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<(), U::Error>>
where
T: AsyncWrite,
U: Encoder<I>,
{
let mut this = self.as_mut().project();
log::trace!("flushing framed transport");
while !this.write_buf.is_empty() {
log::trace!("writing; remaining={}", this.write_buf.len());
let n = ready!(this.io.as_mut().poll_write(cx, this.write_buf))?;
if n == 0 {
return Poll::Ready(Err(io::Error::new(
io::ErrorKind::WriteZero,
"failed to write frame to transport",
)
.into()));
}
// remove written data
this.write_buf.advance(n);
}
// Try flushing the underlying IO
ready!(this.io.poll_flush(cx))?;
log::trace!("framed transport flushed");
Poll::Ready(Ok(()))
}
/// Flush write buffer and shutdown underlying I/O stream.
pub fn close<I>(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<(), U::Error>>
where
T: AsyncWrite,
U: Encoder<I>,
{
let mut this = self.as_mut().project();
ready!(this.io.as_mut().poll_flush(cx))?;
ready!(this.io.as_mut().poll_shutdown(cx))?;
Poll::Ready(Ok(()))
}
}
impl<T, U> Stream for Framed<T, U>
where
T: AsyncRead,
U: Decoder,
{
type Item = Result<U::Item, U::Error>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.next_item(cx)
}
}
impl<T, U, I> Sink<I> for Framed<T, U>
where
T: AsyncWrite,
U: Encoder<I>,
U::Error: From<io::Error>,
{
type Error = U::Error;
fn poll_ready(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
if self.is_write_ready() {
Poll::Ready(Ok(()))
} else {
Poll::Pending
}
}
fn start_send(self: Pin<&mut Self>, item: I) -> Result<(), Self::Error> {
self.write(item)
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.flush(cx)
}
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.close(cx)
}
}
impl<T, U> fmt::Debug for Framed<T, U>
where
T: fmt::Debug,
U: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Framed")
.field("io", &self.io)
.field("codec", &self.codec)
.finish()
}
}
impl<T, U> Framed<T, U> {
/// This function returns a *single* object that is both `Stream` and
/// `Sink`; grouping this into a single object is often useful for layering
/// things like gzip or TLS, which require both read and write access to the
/// underlying object.
///
/// These objects take a stream, a read buffer and a write buffer. These
/// fields can be obtained from an existing `Framed` with the `into_parts` method.
pub fn from_parts(parts: FramedParts<T, U>) -> Framed<T, U> {
Framed {
io: parts.io,
codec: parts.codec,
flags: parts.flags,
write_buf: parts.write_buf,
read_buf: parts.read_buf,
} }
} }
@@ -364,16 +206,124 @@ impl<T, U> Framed<T, U> {
/// of data coming in as it may corrupt the stream of frames otherwise /// of data coming in as it may corrupt the stream of frames otherwise
/// being worked with. /// being worked with.
pub fn into_parts(self) -> FramedParts<T, U> { pub fn into_parts(self) -> FramedParts<T, U> {
let (inner, read_buf) = self.inner.into_parts();
let (inner, write_buf, write_buf_lw, write_buf_hw) = inner.into_parts();
FramedParts { FramedParts {
io: self.io, io: inner.0,
codec: self.codec, codec: inner.1,
flags: self.flags, read_buf,
read_buf: self.read_buf, write_buf,
write_buf: self.write_buf, write_buf_lw,
write_buf_hw,
_priv: (),
} }
} }
} }
impl<T, U> Stream for Framed<T, U>
where
T: AsyncRead,
U: Decoder,
{
type Item = U::Item;
type Error = U::Error;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
self.inner.poll()
}
}
impl<T, U> Sink for Framed<T, U>
where
T: AsyncWrite,
U: Encoder,
U::Error: From<io::Error>,
{
type SinkItem = U::Item;
type SinkError = U::Error;
fn start_send(
&mut self,
item: Self::SinkItem,
) -> StartSend<Self::SinkItem, Self::SinkError> {
self.inner.get_mut().start_send(item)
}
fn poll_complete(&mut self) -> Poll<(), Self::SinkError> {
self.inner.get_mut().poll_complete()
}
fn close(&mut self) -> Poll<(), Self::SinkError> {
self.inner.get_mut().close()
}
}
impl<T, U> fmt::Debug for Framed<T, U>
where
T: fmt::Debug,
U: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Framed")
.field("io", &self.inner.get_ref().get_ref().0)
.field("codec", &self.inner.get_ref().get_ref().1)
.finish()
}
}
// ===== impl Fuse =====
impl<T: Read, U> Read for Fuse<T, U> {
fn read(&mut self, dst: &mut [u8]) -> io::Result<usize> {
self.0.read(dst)
}
}
impl<T: AsyncRead, U> AsyncRead for Fuse<T, U> {
unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool {
self.0.prepare_uninitialized_buffer(buf)
}
}
impl<T: Write, U> Write for Fuse<T, U> {
fn write(&mut self, src: &[u8]) -> io::Result<usize> {
self.0.write(src)
}
fn flush(&mut self) -> io::Result<()> {
self.0.flush()
}
}
impl<T: AsyncWrite, U> AsyncWrite for Fuse<T, U> {
fn shutdown(&mut self) -> Poll<(), io::Error> {
self.0.shutdown()
}
}
impl<T, U: Decoder> Decoder for Fuse<T, U> {
type Item = U::Item;
type Error = U::Error;
fn decode(&mut self, buffer: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
self.1.decode(buffer)
}
fn decode_eof(&mut self, buffer: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
self.1.decode_eof(buffer)
}
}
impl<T, U: Encoder> Encoder for Fuse<T, U> {
type Item = U::Item;
type Error = U::Error;
fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> {
self.1.encode(item, dst)
}
}
/// `FramedParts` contains an export of the data of a Framed transport. /// `FramedParts` contains an export of the data of a Framed transport.
/// It can be used to construct a new `Framed` with a different codec. /// It can be used to construct a new `Framed` with a different codec.
/// It contains all current buffers and the inner transport. /// It contains all current buffers and the inner transport.
@@ -391,7 +341,15 @@ pub struct FramedParts<T, U> {
/// A buffer with unprocessed data which are not written yet. /// A buffer with unprocessed data which are not written yet.
pub write_buf: BytesMut, pub write_buf: BytesMut,
flags: Flags, /// A buffer low watermark capacity
pub write_buf_lw: usize,
/// A buffer high watermark capacity
pub write_buf_hw: usize,
/// This private field allows us to add additional fields in the future in a
/// backwards compatible way.
_priv: (),
} }
impl<T, U> FramedParts<T, U> { impl<T, U> FramedParts<T, U> {
@@ -400,9 +358,11 @@ impl<T, U> FramedParts<T, U> {
FramedParts { FramedParts {
io, io,
codec, codec,
flags: Flags::empty(),
read_buf: BytesMut::new(), read_buf: BytesMut::new(),
write_buf: BytesMut::new(), write_buf: BytesMut::new(),
write_buf_lw: LW,
write_buf_hw: HW,
_priv: (),
} }
} }
@@ -412,8 +372,10 @@ impl<T, U> FramedParts<T, U> {
io, io,
codec, codec,
read_buf, read_buf,
flags: Flags::empty(),
write_buf: BytesMut::new(), write_buf: BytesMut::new(),
write_buf_lw: LW,
write_buf_hw: HW,
_priv: (),
} }
} }
} }

View File

@@ -0,0 +1,218 @@
use std::fmt;
use bytes::BytesMut;
use futures::{try_ready, Async, Poll, Sink, StartSend, Stream};
use log::trace;
use tokio_codec::Decoder;
use tokio_io::AsyncRead;
use super::framed::Fuse;
/// A `Stream` of messages decoded from an `AsyncRead`.
pub struct FramedRead<T, D> {
inner: FramedRead2<Fuse<T, D>>,
}
pub struct FramedRead2<T> {
inner: T,
eof: bool,
is_readable: bool,
buffer: BytesMut,
}
const INITIAL_CAPACITY: usize = 8 * 1024;
// ===== impl FramedRead =====
impl<T, D> FramedRead<T, D>
where
T: AsyncRead,
D: Decoder,
{
/// Creates a new `FramedRead` with the given `decoder`.
pub fn new(inner: T, decoder: D) -> FramedRead<T, D> {
FramedRead {
inner: framed_read2(Fuse(inner, decoder)),
}
}
}
impl<T, D> FramedRead<T, D> {
/// Returns a reference to the underlying I/O stream wrapped by
/// `FramedRead`.
///
/// Note that care should be taken to not tamper with the underlying stream
/// of data coming in as it may corrupt the stream of frames otherwise
/// being worked with.
pub fn get_ref(&self) -> &T {
&self.inner.inner.0
}
/// Returns a mutable reference to the underlying I/O stream wrapped by
/// `FramedRead`.
///
/// Note that care should be taken to not tamper with the underlying stream
/// of data coming in as it may corrupt the stream of frames otherwise
/// being worked with.
pub fn get_mut(&mut self) -> &mut T {
&mut self.inner.inner.0
}
/// Consumes the `FramedRead`, returning its underlying I/O stream.
///
/// Note that care should be taken to not tamper with the underlying stream
/// of data coming in as it may corrupt the stream of frames otherwise
/// being worked with.
pub fn into_inner(self) -> T {
self.inner.inner.0
}
/// Returns a reference to the underlying decoder.
pub fn decoder(&self) -> &D {
&self.inner.inner.1
}
/// Returns a mutable reference to the underlying decoder.
pub fn decoder_mut(&mut self) -> &mut D {
&mut self.inner.inner.1
}
}
impl<T, D> Stream for FramedRead<T, D>
where
T: AsyncRead,
D: Decoder,
{
type Item = D::Item;
type Error = D::Error;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
self.inner.poll()
}
}
impl<T, D> Sink for FramedRead<T, D>
where
T: Sink,
{
type SinkItem = T::SinkItem;
type SinkError = T::SinkError;
fn start_send(
&mut self,
item: Self::SinkItem,
) -> StartSend<Self::SinkItem, Self::SinkError> {
self.inner.inner.0.start_send(item)
}
fn poll_complete(&mut self) -> Poll<(), Self::SinkError> {
self.inner.inner.0.poll_complete()
}
fn close(&mut self) -> Poll<(), Self::SinkError> {
self.inner.inner.0.close()
}
}
impl<T, D> fmt::Debug for FramedRead<T, D>
where
T: fmt::Debug,
D: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("FramedRead")
.field("inner", &self.inner.inner.0)
.field("decoder", &self.inner.inner.1)
.field("eof", &self.inner.eof)
.field("is_readable", &self.inner.is_readable)
.field("buffer", &self.inner.buffer)
.finish()
}
}
// ===== impl FramedRead2 =====
pub fn framed_read2<T>(inner: T) -> FramedRead2<T> {
FramedRead2 {
inner,
eof: false,
is_readable: false,
buffer: BytesMut::with_capacity(INITIAL_CAPACITY),
}
}
pub fn framed_read2_with_buffer<T>(inner: T, mut buf: BytesMut) -> FramedRead2<T> {
if buf.capacity() < INITIAL_CAPACITY {
let bytes_to_reserve = INITIAL_CAPACITY - buf.capacity();
buf.reserve(bytes_to_reserve);
}
FramedRead2 {
inner,
eof: false,
is_readable: !buf.is_empty(),
buffer: buf,
}
}
impl<T> FramedRead2<T> {
pub fn get_ref(&self) -> &T {
&self.inner
}
pub fn into_inner(self) -> T {
self.inner
}
pub fn into_parts(self) -> (T, BytesMut) {
(self.inner, self.buffer)
}
pub fn get_mut(&mut self) -> &mut T {
&mut self.inner
}
}
impl<T> Stream for FramedRead2<T>
where
T: AsyncRead + Decoder,
{
type Item = T::Item;
type Error = T::Error;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
loop {
// Repeatedly call `decode` or `decode_eof` as long as it is
// "readable". Readable is defined as not having returned `None`. If
// the upstream has returned EOF, and the decoder is no longer
// readable, it can be assumed that the decoder will never become
// readable again, at which point the stream is terminated.
if self.is_readable {
if self.eof {
let frame = self.inner.decode_eof(&mut self.buffer)?;
return Ok(Async::Ready(frame));
}
trace!("attempting to decode a frame");
if let Some(frame) = self.inner.decode(&mut self.buffer)? {
trace!("frame decoded from buffer");
return Ok(Async::Ready(Some(frame)));
}
self.is_readable = false;
}
assert!(!self.eof);
// Otherwise, try to read more data and try again. Make sure we've
// got room for at least one byte to read to ensure that we don't
// get a spurious 0 that looks like EOF
self.buffer.reserve(1);
if 0 == try_ready!(self.inner.read_buf(&mut self.buffer)) {
self.eof = true;
}
self.is_readable = true;
}
}
}

View File

@@ -0,0 +1,303 @@
use std::fmt;
use std::io::{self, Read};
use bytes::BytesMut;
use futures::{try_ready, Async, AsyncSink, Poll, Sink, StartSend, Stream};
use log::trace;
use tokio_codec::{Decoder, Encoder};
use tokio_io::{AsyncRead, AsyncWrite};
use super::framed::Fuse;
/// A `Sink` of frames encoded to an `AsyncWrite`.
pub struct FramedWrite<T, E> {
inner: FramedWrite2<Fuse<T, E>>,
}
pub struct FramedWrite2<T> {
inner: T,
buffer: BytesMut,
low_watermark: usize,
high_watermark: usize,
}
impl<T, E> FramedWrite<T, E>
where
T: AsyncWrite,
E: Encoder,
{
/// Creates a new `FramedWrite` with the given `encoder`.
pub fn new(inner: T, encoder: E, lw: usize, hw: usize) -> FramedWrite<T, E> {
FramedWrite {
inner: framed_write2(Fuse(inner, encoder), lw, hw),
}
}
}
impl<T, E> FramedWrite<T, E> {
/// Returns a reference to the underlying I/O stream wrapped by
/// `FramedWrite`.
///
/// Note that care should be taken to not tamper with the underlying stream
/// of data coming in as it may corrupt the stream of frames otherwise
/// being worked with.
pub fn get_ref(&self) -> &T {
&self.inner.inner.0
}
/// Returns a mutable reference to the underlying I/O stream wrapped by
/// `FramedWrite`.
///
/// Note that care should be taken to not tamper with the underlying stream
/// of data coming in as it may corrupt the stream of frames otherwise
/// being worked with.
pub fn get_mut(&mut self) -> &mut T {
&mut self.inner.inner.0
}
/// Consumes the `FramedWrite`, returning its underlying I/O stream.
///
/// Note that care should be taken to not tamper with the underlying stream
/// of data coming in as it may corrupt the stream of frames otherwise
/// being worked with.
pub fn into_inner(self) -> T {
self.inner.inner.0
}
/// Returns a reference to the underlying decoder.
pub fn encoder(&self) -> &E {
&self.inner.inner.1
}
/// Returns a mutable reference to the underlying decoder.
pub fn encoder_mut(&mut self) -> &mut E {
&mut self.inner.inner.1
}
/// Check if write buffer is full
pub fn is_full(&self) -> bool {
self.inner.is_full()
}
/// Check if write buffer is empty.
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
}
impl<T, E> FramedWrite<T, E>
where
E: Encoder,
{
/// Force send item
pub fn force_send(&mut self, item: E::Item) -> Result<(), E::Error> {
self.inner.force_send(item)
}
}
impl<T, E> Sink for FramedWrite<T, E>
where
T: AsyncWrite,
E: Encoder,
{
type SinkItem = E::Item;
type SinkError = E::Error;
fn start_send(&mut self, item: E::Item) -> StartSend<E::Item, E::Error> {
self.inner.start_send(item)
}
fn poll_complete(&mut self) -> Poll<(), Self::SinkError> {
self.inner.poll_complete()
}
fn close(&mut self) -> Poll<(), Self::SinkError> {
Ok(self.inner.close()?)
}
}
impl<T, D> Stream for FramedWrite<T, D>
where
T: Stream,
{
type Item = T::Item;
type Error = T::Error;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
self.inner.inner.0.poll()
}
}
impl<T, U> fmt::Debug for FramedWrite<T, U>
where
T: fmt::Debug,
U: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("FramedWrite")
.field("inner", &self.inner.get_ref().0)
.field("encoder", &self.inner.get_ref().1)
.field("buffer", &self.inner.buffer)
.finish()
}
}
// ===== impl FramedWrite2 =====
pub fn framed_write2<T>(
inner: T,
low_watermark: usize,
high_watermark: usize,
) -> FramedWrite2<T> {
FramedWrite2 {
inner,
low_watermark,
high_watermark,
buffer: BytesMut::with_capacity(high_watermark),
}
}
pub fn framed_write2_with_buffer<T>(
inner: T,
mut buffer: BytesMut,
low_watermark: usize,
high_watermark: usize,
) -> FramedWrite2<T> {
if buffer.capacity() < high_watermark {
let bytes_to_reserve = high_watermark - buffer.capacity();
buffer.reserve(bytes_to_reserve);
}
FramedWrite2 {
inner,
buffer,
low_watermark,
high_watermark,
}
}
impl<T> FramedWrite2<T> {
pub fn get_ref(&self) -> &T {
&self.inner
}
pub fn into_inner(self) -> T {
self.inner
}
pub fn into_parts(self) -> (T, BytesMut, usize, usize) {
(
self.inner,
self.buffer,
self.low_watermark,
self.high_watermark,
)
}
pub fn get_mut(&mut self) -> &mut T {
&mut self.inner
}
pub fn is_full(&self) -> bool {
self.buffer.len() >= self.high_watermark
}
pub fn is_empty(&self) -> bool {
self.buffer.is_empty()
}
}
impl<T> FramedWrite2<T>
where
T: Encoder,
{
pub fn force_send(&mut self, item: T::Item) -> Result<(), T::Error> {
let len = self.buffer.len();
if len < self.low_watermark {
self.buffer.reserve(self.high_watermark - len)
}
self.inner.encode(item, &mut self.buffer)?;
Ok(())
}
}
impl<T> Sink for FramedWrite2<T>
where
T: AsyncWrite + Encoder,
{
type SinkItem = T::Item;
type SinkError = T::Error;
fn start_send(&mut self, item: T::Item) -> StartSend<T::Item, T::Error> {
// Check the buffer capacity
let len = self.buffer.len();
if len >= self.high_watermark {
return Ok(AsyncSink::NotReady(item));
}
if len < self.low_watermark {
self.buffer.reserve(self.high_watermark - len)
}
self.inner.encode(item, &mut self.buffer)?;
Ok(AsyncSink::Ready)
}
fn poll_complete(&mut self) -> Poll<(), Self::SinkError> {
trace!("flushing framed transport");
while !self.buffer.is_empty() {
trace!("writing; remaining={}", self.buffer.len());
let n = try_ready!(self.inner.poll_write(&self.buffer));
if n == 0 {
return Err(io::Error::new(
io::ErrorKind::WriteZero,
"failed to \
write frame to transport",
)
.into());
}
// TODO: Add a way to `bytes` to do this w/o returning the drained
// data.
let _ = self.buffer.split_to(n);
}
// Try flushing the underlying IO
try_ready!(self.inner.poll_flush());
trace!("framed transport flushed");
Ok(Async::Ready(()))
}
fn close(&mut self) -> Poll<(), Self::SinkError> {
try_ready!(self.poll_complete());
Ok(self.inner.shutdown()?)
}
}
impl<T: Decoder> Decoder for FramedWrite2<T> {
type Item = T::Item;
type Error = T::Error;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<T::Item>, T::Error> {
self.inner.decode(src)
}
fn decode_eof(&mut self, src: &mut BytesMut) -> Result<Option<T::Item>, T::Error> {
self.inner.decode_eof(src)
}
}
impl<T: Read> Read for FramedWrite2<T> {
fn read(&mut self, dst: &mut [u8]) -> io::Result<usize> {
self.inner.read(dst)
}
}
impl<T: AsyncRead> AsyncRead for FramedWrite2<T> {
unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool {
self.inner.prepare_uninitialized_buffer(buf)
}
}

View File

@@ -2,21 +2,23 @@
//! //!
//! 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`].
//! Framed streams are also known as `transports`. //! Framed streams are also known as [transports].
//! //!
//! [`AsyncRead`]: AsyncRead //! [`AsyncRead`]: #
//! [`AsyncWrite`]: AsyncWrite //! [`AsyncWrite`]: #
//! [`Sink`]: futures_sink::Sink //! [`Sink`]: #
//! [`Stream`]: futures_core::Stream //! [`Stream`]: #
//! [transports]: #
#![deny(rust_2018_idioms)]
#![warn(missing_docs)]
mod bcodec; mod bcodec;
mod framed; mod framed;
mod framed_read;
mod framed_write;
pub use self::bcodec::BytesCodec; pub use self::bcodec::BytesCodec;
pub use self::framed::{Framed, FramedParts}; pub use self::framed::{Framed, FramedParts};
pub use self::framed_read::FramedRead;
pub use self::framed_write::FramedWrite;
pub use tokio::io::{AsyncRead, AsyncWrite}; pub use tokio_codec::{Decoder, Encoder};
pub use tokio_util::codec::{Decoder, Encoder}; pub use tokio_io::{AsyncRead, AsyncWrite};

View File

@@ -1,81 +1,5 @@
# Changes # Changes
## Unreleased
## 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 ## [0.2.3] - 2019-08-05
* Add `ConnectService` and `OpensslConnectService` * Add `ConnectService` and `OpensslConnectService`

View File

@@ -1,18 +1,20 @@
[package] [package]
name = "actix-connect" name = "actix-connect"
version = "2.0.0" version = "0.2.3"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "TCP connector service for Actix ecosystem." description = "Actix Connector - tcp connector service"
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"
documentation = "https://docs.rs/actix-connect/" documentation = "https://docs.rs/actix-connect/"
categories = ["network-programming", "asynchronous"] categories = ["network-programming", "asynchronous"]
license = "MIT OR Apache-2.0" license = "MIT/Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
edition = "2018" edition = "2018"
workspace = ".."
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["openssl", "rustls", "uri"] features = ["ssl", "uri"]
[lib] [lib]
name = "actix_connect" name = "actix_connect"
@@ -22,37 +24,37 @@ path = "src/lib.rs"
default = ["uri"] default = ["uri"]
# openssl # openssl
openssl = ["open-ssl", "tokio-openssl"] ssl = ["openssl", "tokio-openssl"]
# rustls #rustls
rustls = ["rust-tls", "tokio-rustls", "webpki"] rust-tls = ["rustls", "tokio-rustls", "webpki"]
# support http::Uri as connect address # support http::Uri as connect address
uri = ["http"] uri = ["http"]
[dependencies] [dependencies]
actix-service = "1.0.6" actix-service = "0.4.0"
actix-codec = "0.3.0" actix-codec = "0.1.2"
actix-utils = "2.0.0" actix-utils = "0.4.0"
actix-rt = "1.1.1" derive_more = "0.15"
either = "1.5.2"
derive_more = "0.99.2" futures = "0.1.25"
either = "1.5.3" http = { version = "0.1.17", optional = true }
futures-util = { version = "0.3.4", default-features = false }
http = { version = "0.2.0", optional = true }
log = "0.4" log = "0.4"
trust-dns-proto = { version = "0.19", default-features = false, features = ["tokio-runtime"] } tokio-tcp = "0.1.3"
trust-dns-resolver = { version = "0.19", default-features = false, features = ["tokio-runtime", "system-config"] } tokio-current-thread = "0.1.5"
trust-dns-resolver = { version="0.11.0", default-features = false }
# openssl # openssl
open-ssl = { package = "openssl", version = "0.10", optional = true } openssl = { version="0.10", optional = true }
tokio-openssl = { version = "0.4.0", optional = true } tokio-openssl = { version="0.3", optional = true }
# rustls #rustls
rust-tls = { package = "rustls", version = "0.18.0", optional = true } rustls = { version = "0.15.2", optional = true }
tokio-rustls = { version = "0.14.0", optional = true } tokio-rustls = { version = "0.9.1", optional = true }
webpki = { version = "0.21", optional = true } webpki = { version = "0.19", optional = true }
[dev-dependencies] [dev-dependencies]
bytes = "0.5.3" bytes = "0.4"
actix-testing = "1.0.0" actix-test-server = { version="0.2.2", features=["ssl"] }
actix-server-config = "0.1.0"

View File

@@ -6,7 +6,7 @@ use std::net::SocketAddr;
use either::Either; use either::Either;
/// Connect request /// Connect request
pub trait Address: Unpin + 'static { pub trait Address {
/// Host name of the request /// Host name of the request
fn host(&self) -> &str; fn host(&self) -> &str;
@@ -43,7 +43,7 @@ pub struct Connect<T> {
} }
impl<T: Address> Connect<T> { impl<T: Address> Connect<T> {
/// Create `Connect` instance by splitting the string by ':' and convert the second part to u16 /// Create `Connect` instance by spliting the string by ':' and convert the second part to u16
pub fn new(req: T) -> Connect<T> { pub fn new(req: T) -> Connect<T> {
let (_, port) = parse(req.host()); let (_, port) = parse(req.host());
Connect { Connect {
@@ -53,8 +53,7 @@ impl<T: Address> Connect<T> {
} }
} }
/// Create new `Connect` instance from host and address. Connector skips name resolution stage /// Create new `Connect` instance from host and address. Connector skips name resolution stage for such connect messages.
/// for such connect messages.
pub fn with(req: T, addr: SocketAddr) -> Connect<T> { pub fn with(req: T, addr: SocketAddr) -> Connect<T> {
Connect { Connect {
req, req,
@@ -103,7 +102,7 @@ impl<T: Address> Connect<T> {
self.req.port().unwrap_or(self.port) self.req.port().unwrap_or(self.port)
} }
/// Pre-resolved addresses of the request. /// Preresolved addresses of the request.
pub fn addrs(&self) -> ConnectAddrsIter<'_> { pub fn addrs(&self) -> ConnectAddrsIter<'_> {
let inner = match self.addr { let inner = match self.addr {
None => Either::Left(None), None => Either::Left(None),
@@ -114,7 +113,7 @@ impl<T: Address> Connect<T> {
ConnectAddrsIter { inner } ConnectAddrsIter { inner }
} }
/// Takes pre-resolved addresses of the request. /// Takes preresolved addresses of the request.
pub fn take_addrs(&mut self) -> ConnectTakeAddrsIter { pub fn take_addrs(&mut self) -> ConnectTakeAddrsIter {
let inner = match self.addr.take() { let inner = match self.addr.take() {
None => Either::Left(None), None => Either::Left(None),
@@ -133,7 +132,7 @@ impl<T: Address> From<T> for Connect<T> {
} }
impl<T: Address> fmt::Display for Connect<T> { impl<T: Address> fmt::Display for Connect<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}:{}", self.host(), self.port()) write!(f, "{}:{}", self.host(), self.port())
} }
} }
@@ -164,7 +163,7 @@ impl Iterator for ConnectAddrsIter<'_> {
} }
impl fmt::Debug for ConnectAddrsIter<'_> { impl fmt::Debug for ConnectAddrsIter<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_list().entries(self.clone()).finish() f.debug_list().entries(self.clone()).finish()
} }
} }
@@ -276,7 +275,7 @@ impl<T, U> std::ops::DerefMut for Connection<T, U> {
} }
impl<T, U: fmt::Debug> fmt::Debug for Connection<T, U> { impl<T, U: fmt::Debug> fmt::Debug for Connection<T, U> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Stream {{{:?}}}", self.io) write!(f, "Stream {{{:?}}}", self.io)
} }
} }

View File

@@ -1,19 +1,16 @@
use std::collections::VecDeque; use std::collections::VecDeque;
use std::future::Future;
use std::io;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::pin::Pin;
use std::task::{Context, Poll};
use actix_rt::net::TcpStream; use actix_service::{NewService, Service};
use actix_service::{Service, ServiceFactory}; use futures::future::{err, ok, Either, FutureResult};
use futures_util::future::{err, ok, BoxFuture, Either, FutureExt, Ready}; use futures::{Async, Future, Poll};
use tokio_tcp::{ConnectFuture, TcpStream};
use super::connect::{Address, Connect, Connection}; use super::connect::{Address, Connect, Connection};
use super::error::ConnectError; use super::error::ConnectError;
/// TCP connector service factory /// Tcp connector service factory
#[derive(Debug)] #[derive(Debug)]
pub struct TcpConnectorFactory<T>(PhantomData<T>); pub struct TcpConnectorFactory<T>(PhantomData<T>);
@@ -22,7 +19,7 @@ impl<T> TcpConnectorFactory<T> {
TcpConnectorFactory(PhantomData) TcpConnectorFactory(PhantomData)
} }
/// Create TCP connector service /// Create tcp connector service
pub fn service(&self) -> TcpConnector<T> { pub fn service(&self) -> TcpConnector<T> {
TcpConnector(PhantomData) TcpConnector(PhantomData)
} }
@@ -40,22 +37,22 @@ impl<T> Clone for TcpConnectorFactory<T> {
} }
} }
impl<T: Address> ServiceFactory for TcpConnectorFactory<T> { impl<T: Address> NewService for TcpConnectorFactory<T> {
type Request = Connect<T>; type Request = Connect<T>;
type Response = Connection<T, TcpStream>; type Response = Connection<T, TcpStream>;
type Error = ConnectError; type Error = ConnectError;
type Config = (); type Config = ();
type Service = TcpConnector<T>; type Service = TcpConnector<T>;
type InitError = (); type InitError = ();
type Future = Ready<Result<Self::Service, Self::InitError>>; type Future = FutureResult<Self::Service, Self::InitError>;
fn new_service(&self, _: ()) -> Self::Future { fn new_service(&self, _: &()) -> Self::Future {
ok(self.service()) ok(self.service())
} }
} }
/// TCP connector service /// Tcp connector service
#[derive(Default, Debug)] #[derive(Debug)]
pub struct TcpConnector<T>(PhantomData<T>); pub struct TcpConnector<T>(PhantomData<T>);
impl<T> TcpConnector<T> { impl<T> TcpConnector<T> {
@@ -74,11 +71,10 @@ impl<T: Address> Service for TcpConnector<T> {
type Request = Connect<T>; type Request = Connect<T>;
type Response = Connection<T, TcpStream>; type Response = Connection<T, TcpStream>;
type Error = ConnectError; type Error = ConnectError;
#[allow(clippy::type_complexity)] type Future = Either<TcpConnectorResponse<T>, FutureResult<Self::Response, Self::Error>>;
type Future = Either<TcpConnectorResponse<T>, Ready<Result<Self::Response, Self::Error>>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Poll::Ready(Ok(())) Ok(Async::Ready(()))
} }
fn call(&mut self, req: Connect<T>) -> Self::Future { fn call(&mut self, req: Connect<T>) -> Self::Future {
@@ -86,21 +82,21 @@ impl<T: Address> Service 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)) Either::A(TcpConnectorResponse::new(req, port, addr))
} else { } else {
error!("TCP connector: got unresolved address"); error!("TCP connector: got unresolved address");
Either::Right(err(ConnectError::Unresolved)) Either::B(err(ConnectError::Unresolverd))
} }
} }
} }
#[doc(hidden)] #[doc(hidden)]
/// TCP stream connector response future /// Tcp stream connector response future
pub struct TcpConnectorResponse<T> { pub struct TcpConnectorResponse<T> {
req: Option<T>, req: Option<T>,
port: u16, port: u16,
addrs: Option<VecDeque<SocketAddr>>, addrs: Option<VecDeque<SocketAddr>>,
stream: Option<BoxFuture<'static, Result<TcpStream, io::Error>>>, stream: Option<ConnectFuture>,
} }
impl<T: Address> TcpConnectorResponse<T> { impl<T: Address> TcpConnectorResponse<T> {
@@ -120,7 +116,7 @@ impl<T: Address> TcpConnectorResponse<T> {
req: Some(req), req: Some(req),
port, port,
addrs: None, addrs: None,
stream: Some(TcpStream::connect(addr).boxed()), stream: Some(TcpStream::connect(&addr)),
}, },
either::Either::Right(addrs) => TcpConnectorResponse { either::Either::Right(addrs) => TcpConnectorResponse {
req: Some(req), req: Some(req),
@@ -133,40 +129,40 @@ impl<T: Address> TcpConnectorResponse<T> {
} }
impl<T: Address> Future for TcpConnectorResponse<T> { impl<T: Address> Future for TcpConnectorResponse<T> {
type Output = Result<Connection<T, TcpStream>, ConnectError>; type Item = Connection<T, TcpStream>;
type Error = ConnectError;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
// connect // connect
loop { loop {
if let Some(new) = this.stream.as_mut() { if let Some(new) = self.stream.as_mut() {
match new.as_mut().poll(cx) { match new.poll() {
Poll::Ready(Ok(sock)) => { Ok(Async::Ready(sock)) => {
let req = this.req.take().unwrap(); let req = self.req.take().unwrap();
trace!( trace!(
"TCP connector - successfully connected to connecting to {:?} - {:?}", "TCP connector - successfully connected to connecting to {:?} - {:?}",
req.host(), sock.peer_addr() req.host(), sock.peer_addr()
); );
return Poll::Ready(Ok(Connection::new(sock, req))); return Ok(Async::Ready(Connection::new(sock, req)));
} }
Poll::Pending => return Poll::Pending, Ok(Async::NotReady) => return Ok(Async::NotReady),
Poll::Ready(Err(err)) => { Err(err) => {
trace!( trace!(
"TCP connector - failed to connect to connecting to {:?} port: {}", "TCP connector - failed to connect to connecting to {:?} port: {}",
this.req.as_ref().unwrap().host(), self.req.as_ref().unwrap().host(),
this.port, self.port,
); );
if this.addrs.is_none() || this.addrs.as_ref().unwrap().is_empty() { if self.addrs.is_none() || self.addrs.as_ref().unwrap().is_empty() {
return Poll::Ready(Err(err.into())); return Err(err.into());
} }
} }
} }
} }
// try to connect // try to connect
let addr = this.addrs.as_mut().unwrap().pop_front().unwrap(); self.stream = Some(TcpStream::connect(
this.stream = Some(TcpStream::connect(addr).boxed()); &self.addrs.as_mut().unwrap().pop_front().unwrap(),
));
} }
} }
} }

View File

@@ -18,9 +18,9 @@ pub enum ConnectError {
/// Unresolved host name /// Unresolved host name
#[display(fmt = "Connector received `Connect` method with unresolved host")] #[display(fmt = "Connector received `Connect` method with unresolved host")]
Unresolved, Unresolverd,
/// Connection IO error /// Connection io error
#[display(fmt = "{}", _0)] #[display(fmt = "{}", _0)]
Io(io::Error), Io(io::Error),
} }

View File

@@ -1,57 +1,56 @@
//! TCP connector service for Actix ecosystem. //! Actix connect - tcp connector service
//! //!
//! ## Package feature //! ## Package feature
//! //!
//! * `openssl` - enables ssl support via `openssl` crate //! * `ssl` - enables ssl support via `openssl` crate
//! * `rustls` - enables ssl support via `rustls` crate //! * `rust-tls` - enables ssl support via `rustls` crate
#![deny(rust_2018_idioms)]
#![recursion_limit = "128"] #![recursion_limit = "128"]
#[macro_use] #[macro_use]
extern crate log; extern crate log;
use std::cell::RefCell;
mod connect; mod connect;
mod connector; mod connector;
mod error; mod error;
mod resolve; mod resolver;
mod service; mod service;
pub mod ssl; pub mod ssl;
#[cfg(feature = "uri")] #[cfg(feature = "uri")]
mod uri; mod uri;
use actix_rt::{net::TcpStream, Arbiter}; pub use trust_dns_resolver::config::{ResolverConfig, ResolverOpts};
use actix_service::{pipeline, pipeline_factory, Service, ServiceFactory}; pub use trust_dns_resolver::system_conf::read_system_conf;
use trust_dns_resolver::config::{ResolverConfig, ResolverOpts}; pub use trust_dns_resolver::{error::ResolveError, AsyncResolver};
use trust_dns_resolver::system_conf::read_system_conf;
use trust_dns_resolver::TokioAsyncResolver as AsyncResolver;
pub mod resolver {
pub use trust_dns_resolver::config::{ResolverConfig, ResolverOpts};
pub use trust_dns_resolver::system_conf::read_system_conf;
pub use trust_dns_resolver::{error::ResolveError, AsyncResolver};
}
pub use self::connect::{Address, Connect, Connection}; pub use self::connect::{Address, Connect, Connection};
pub use self::connector::{TcpConnector, TcpConnectorFactory}; pub use self::connector::{TcpConnector, TcpConnectorFactory};
pub use self::error::ConnectError; pub use self::error::ConnectError;
pub use self::resolve::{Resolver, ResolverFactory}; pub use self::resolver::{Resolver, ResolverFactory};
pub use self::service::{ConnectService, ConnectServiceFactory, TcpConnectService}; pub use self::service::{ConnectService, ConnectServiceFactory};
pub async fn start_resolver( use actix_service::{NewService, Service, ServiceExt};
cfg: ResolverConfig, use tokio_tcp::TcpStream;
opts: ResolverOpts,
) -> Result<AsyncResolver, ConnectError> { pub fn start_resolver(cfg: ResolverConfig, opts: ResolverOpts) -> AsyncResolver {
Ok(AsyncResolver::tokio(cfg, opts).await?) let (resolver, bg) = AsyncResolver::new(cfg, opts);
tokio_current_thread::spawn(bg);
resolver
} }
struct DefaultResolver(AsyncResolver); thread_local! {
static DEFAULT_RESOLVER: RefCell<Option<AsyncResolver>> = RefCell::new(None);
}
pub(crate) fn get_default_resolver() -> AsyncResolver {
DEFAULT_RESOLVER.with(|cell| {
if let Some(ref resolver) = *cell.borrow() {
return resolver.clone();
}
pub(crate) async fn get_default_resolver() -> Result<AsyncResolver, ConnectError> {
if Arbiter::contains_item::<DefaultResolver>() {
Ok(Arbiter::get_item(|item: &DefaultResolver| item.0.clone()))
} else {
let (cfg, opts) = match read_system_conf() { let (cfg, opts) = match read_system_conf() {
Ok((cfg, opts)) => (cfg, opts), Ok((cfg, opts)) => (cfg, opts),
Err(e) => { Err(e) => {
@@ -60,52 +59,53 @@ pub(crate) async fn get_default_resolver() -> Result<AsyncResolver, ConnectError
} }
}; };
let resolver = AsyncResolver::tokio(cfg, opts).await?; let (resolver, bg) = AsyncResolver::new(cfg, opts);
tokio_current_thread::spawn(bg);
Arbiter::set_item(DefaultResolver(resolver.clone())); *cell.borrow_mut() = Some(resolver.clone());
Ok(resolver) resolver
} })
} }
pub async fn start_default_resolver() -> Result<AsyncResolver, ConnectError> { pub fn start_default_resolver() -> AsyncResolver {
get_default_resolver().await get_default_resolver()
} }
/// Create TCP connector service. /// Create tcp connector service
pub fn new_connector<T: Address + 'static>( pub fn new_connector<T: Address>(
resolver: AsyncResolver, resolver: AsyncResolver,
) -> impl Service<Request = Connect<T>, Response = Connection<T, TcpStream>, Error = ConnectError> ) -> impl Service<Request = Connect<T>, Response = Connection<T, TcpStream>, Error = ConnectError>
+ Clone { + Clone {
pipeline(Resolver::new(resolver)).and_then(TcpConnector::new()) Resolver::new(resolver).and_then(TcpConnector::new())
} }
/// Create TCP connector service factory. /// Create tcp connector service
pub fn new_connector_factory<T: Address + 'static>( pub fn new_connector_factory<T: Address>(
resolver: AsyncResolver, resolver: AsyncResolver,
) -> impl ServiceFactory< ) -> impl NewService<
Config = (), Config = (),
Request = Connect<T>, Request = Connect<T>,
Response = Connection<T, TcpStream>, Response = Connection<T, TcpStream>,
Error = ConnectError, Error = ConnectError,
InitError = (), InitError = (),
> + Clone { > + Clone {
pipeline_factory(ResolverFactory::new(resolver)).and_then(TcpConnectorFactory::new()) ResolverFactory::new(resolver).and_then(TcpConnectorFactory::new())
} }
/// Create connector service with default parameters. /// Create connector service with default parameters
pub fn default_connector<T: Address + 'static>( pub fn default_connector<T: Address>(
) -> impl Service<Request = Connect<T>, Response = Connection<T, TcpStream>, Error = ConnectError> ) -> impl Service<Request = Connect<T>, Response = Connection<T, TcpStream>, Error = ConnectError>
+ Clone { + Clone {
pipeline(Resolver::default()).and_then(TcpConnector::new()) Resolver::default().and_then(TcpConnector::new())
} }
/// Create connector service factory with default parameters. /// Create connector service factory with default parameters
pub fn default_connector_factory<T: Address + 'static>() -> impl ServiceFactory< pub fn default_connector_factory<T: Address>() -> impl NewService<
Config = (), Config = (),
Request = Connect<T>, Request = Connect<T>,
Response = Connection<T, TcpStream>, Response = Connection<T, TcpStream>,
Error = ConnectError, Error = ConnectError,
InitError = (), InitError = (),
> + Clone { > + Clone {
pipeline_factory(ResolverFactory::default()).and_then(TcpConnectorFactory::new()) ResolverFactory::default().and_then(TcpConnectorFactory::new())
} }

View File

@@ -1,13 +1,11 @@
use std::future::Future;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::pin::Pin;
use std::task::{Context, Poll};
use actix_service::{Service, ServiceFactory}; use actix_service::{NewService, Service};
use futures_util::future::{ok, Either, Ready}; use futures::future::{ok, Either, FutureResult};
use trust_dns_resolver::TokioAsyncResolver as AsyncResolver; use futures::{Async, Future, Poll};
use trust_dns_resolver::{error::ResolveError, lookup_ip::LookupIp}; use trust_dns_resolver::lookup_ip::LookupIpFuture;
use trust_dns_resolver::{AsyncResolver, Background};
use crate::connect::{Address, Connect}; use crate::connect::{Address, Connect};
use crate::error::ConnectError; use crate::error::ConnectError;
@@ -54,16 +52,16 @@ impl<T> Clone for ResolverFactory<T> {
} }
} }
impl<T: Address> ServiceFactory for ResolverFactory<T> { impl<T: Address> NewService for ResolverFactory<T> {
type Request = Connect<T>; type Request = Connect<T>;
type Response = Connect<T>; type Response = Connect<T>;
type Error = ConnectError; type Error = ConnectError;
type Config = (); type Config = ();
type Service = Resolver<T>; type Service = Resolver<T>;
type InitError = (); type InitError = ();
type Future = Ready<Result<Self::Service, Self::InitError>>; type Future = FutureResult<Self::Service, Self::InitError>;
fn new_service(&self, _: ()) -> Self::Future { fn new_service(&self, _: &()) -> Self::Future {
ok(self.service()) ok(self.service())
} }
} }
@@ -106,80 +104,69 @@ impl<T: Address> Service for Resolver<T> {
type Request = Connect<T>; type Request = Connect<T>;
type Response = Connect<T>; type Response = Connect<T>;
type Error = ConnectError; type Error = ConnectError;
#[allow(clippy::type_complexity)] type Future = Either<ResolverFuture<T>, FutureResult<Connect<T>, Self::Error>>;
type Future = Either<
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>,
Ready<Result<Connect<T>, Self::Error>>,
>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Poll::Ready(Ok(())) Ok(Async::Ready(()))
} }
fn call(&mut self, mut req: Connect<T>) -> Self::Future { fn call(&mut self, mut req: Connect<T>) -> Self::Future {
if req.addr.is_some() { if req.addr.is_some() {
Either::Right(ok(req)) Either::B(ok(req))
} else if let Ok(ip) = req.host().parse() {
req.addr = Some(either::Either::Left(SocketAddr::new(ip, req.port())));
Either::Right(ok(req))
} else { } else {
let resolver = self.resolver.as_ref().map(AsyncResolver::clone); if let Ok(ip) = req.host().parse() {
Either::Left(Box::pin(async move { req.addr = Some(either::Either::Left(SocketAddr::new(ip, req.port())));
Either::B(ok(req))
} else {
trace!("DNS resolver: resolving host {:?}", req.host()); trace!("DNS resolver: resolving host {:?}", req.host());
let resolver = if let Some(resolver) = resolver { if self.resolver.is_none() {
resolver self.resolver = Some(get_default_resolver());
} else { }
get_default_resolver() Either::A(ResolverFuture::new(req, self.resolver.as_ref().unwrap()))
.await }
.expect("Failed to get default resolver")
};
ResolverFuture::new(req, &resolver).await
}))
} }
} }
} }
type LookupIpFuture = Pin<Box<dyn Future<Output = Result<LookupIp, ResolveError>>>>;
#[doc(hidden)] #[doc(hidden)]
/// Resolver future /// Resolver future
pub struct ResolverFuture<T: Address> { pub struct ResolverFuture<T: Address> {
req: Option<Connect<T>>, req: Option<Connect<T>>,
lookup: LookupIpFuture, lookup: Background<LookupIpFuture>,
} }
impl<T: Address> ResolverFuture<T> { impl<T: Address> ResolverFuture<T> {
pub fn new(req: Connect<T>, resolver: &AsyncResolver) -> Self { pub fn new(req: Connect<T>, resolver: &AsyncResolver) -> Self {
let host = if let Some(host) = req.host().splitn(2, ':').next() { let lookup = if let Some(host) = req.host().splitn(2, ':').next() {
host resolver.lookup_ip(host)
} else { } else {
req.host() resolver.lookup_ip(req.host())
}; };
// Clone data to be moved to the lookup future
let host_clone = host.to_owned();
let resolver_clone = resolver.clone();
ResolverFuture { ResolverFuture {
lookup: Box::pin(async move { lookup,
let resolver = resolver_clone;
resolver.lookup_ip(host_clone).await
}),
req: Some(req), req: Some(req),
} }
} }
} }
impl<T: Address> Future for ResolverFuture<T> { impl<T: Address> Future for ResolverFuture<T> {
type Output = Result<Connect<T>, ConnectError>; type Item = Connect<T>;
type Error = ConnectError;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let this = self.get_mut(); match self.lookup.poll().map_err(|e| {
trace!(
"DNS resolver: failed to resolve host {:?} err: {}",
self.req.as_ref().unwrap().host(),
e
);
e
})? {
Async::NotReady => Ok(Async::NotReady),
Async::Ready(ips) => {
let req = self.req.take().unwrap();
match Pin::new(&mut this.lookup).poll(cx) {
Poll::Pending => Poll::Pending,
Poll::Ready(Ok(ips)) => {
let req = this.req.take().unwrap();
let port = req.port(); let port = req.port();
let req = req.set_addrs(ips.iter().map(|ip| SocketAddr::new(ip, port))); let req = req.set_addrs(ips.iter().map(|ip| SocketAddr::new(ip, port)));
@@ -190,19 +177,11 @@ impl<T: Address> Future for ResolverFuture<T> {
); );
if req.addr.is_none() { if req.addr.is_none() {
Poll::Ready(Err(ConnectError::NoRecords)) Err(ConnectError::NoRecords)
} else { } else {
Poll::Ready(Ok(req)) Ok(Async::Ready(req))
} }
} }
Poll::Ready(Err(e)) => {
trace!(
"DNS resolver: failed to resolve host {:?} err: {}",
this.req.as_ref().unwrap().host(),
e
);
Poll::Ready(Err(e.into()))
}
} }
} }
} }

View File

@@ -1,17 +1,13 @@
use std::future::Future; use actix_service::{NewService, Service};
use std::pin::Pin; use futures::future::{ok, FutureResult};
use std::task::{Context, Poll}; use futures::{try_ready, Async, Future, Poll};
use tokio_tcp::TcpStream;
use actix_rt::net::TcpStream; use trust_dns_resolver::AsyncResolver;
use actix_service::{Service, ServiceFactory};
use either::Either;
use futures_util::future::{ok, Ready};
use trust_dns_resolver::TokioAsyncResolver as AsyncResolver;
use crate::connect::{Address, Connect, Connection}; use crate::connect::{Address, Connect, Connection};
use crate::connector::{TcpConnector, TcpConnectorFactory}; use crate::connector::{TcpConnector, TcpConnectorFactory};
use crate::error::ConnectError; use crate::error::ConnectError;
use crate::resolve::{Resolver, ResolverFactory}; use crate::resolver::{Resolver, ResolverFactory};
pub struct ConnectServiceFactory<T> { pub struct ConnectServiceFactory<T> {
tcp: TcpConnectorFactory<T>, tcp: TcpConnectorFactory<T>,
@@ -42,14 +38,6 @@ impl<T> ConnectServiceFactory<T> {
resolver: self.resolver.service(), resolver: self.resolver.service(),
} }
} }
/// Construct new tcp stream service
pub fn tcp_service(&self) -> TcpConnectService<T> {
TcpConnectService {
tcp: self.tcp.service(),
resolver: self.resolver.service(),
}
}
} }
impl<T> Default for ConnectServiceFactory<T> { impl<T> Default for ConnectServiceFactory<T> {
@@ -70,16 +58,16 @@ impl<T> Clone for ConnectServiceFactory<T> {
} }
} }
impl<T: Address> ServiceFactory for ConnectServiceFactory<T> { impl<T: Address> NewService for ConnectServiceFactory<T> {
type Request = Connect<T>; type Request = Connect<T>;
type Response = Connection<T, TcpStream>; type Response = Connection<T, TcpStream>;
type Error = ConnectError; type Error = ConnectError;
type Config = (); type Config = ();
type Service = ConnectService<T>; type Service = ConnectService<T>;
type InitError = (); type InitError = ();
type Future = Ready<Result<Self::Service, Self::InitError>>; type Future = FutureResult<Self::Service, Self::InitError>;
fn new_service(&self, _: ()) -> Self::Future { fn new_service(&self, _: &()) -> Self::Future {
ok(self.service()) ok(self.service())
} }
} }
@@ -96,137 +84,40 @@ impl<T: Address> Service for ConnectService<T> {
type Error = ConnectError; type Error = ConnectError;
type Future = ConnectServiceResponse<T>; type Future = ConnectServiceResponse<T>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Poll::Ready(Ok(())) Ok(Async::Ready(()))
} }
fn call(&mut self, req: Connect<T>) -> Self::Future { fn call(&mut self, req: Connect<T>) -> Self::Future {
ConnectServiceResponse { ConnectServiceResponse {
state: ConnectState::Resolve(self.resolver.call(req)), fut1: Some(self.resolver.call(req)),
fut2: None,
tcp: self.tcp.clone(), tcp: self.tcp.clone(),
} }
} }
} }
enum ConnectState<T: Address> {
Resolve(<Resolver<T> as Service>::Future),
Connect(<TcpConnector<T> as Service>::Future),
}
impl<T: Address> ConnectState<T> {
#[allow(clippy::type_complexity)]
fn poll(
&mut self,
cx: &mut Context<'_>,
) -> Either<Poll<Result<Connection<T, TcpStream>, ConnectError>>, Connect<T>> {
match self {
ConnectState::Resolve(ref mut fut) => match Pin::new(fut).poll(cx) {
Poll::Pending => Either::Left(Poll::Pending),
Poll::Ready(Ok(res)) => Either::Right(res),
Poll::Ready(Err(err)) => Either::Left(Poll::Ready(Err(err))),
},
ConnectState::Connect(ref mut fut) => Either::Left(Pin::new(fut).poll(cx)),
}
}
}
pub struct ConnectServiceResponse<T: Address> { pub struct ConnectServiceResponse<T: Address> {
state: ConnectState<T>, fut1: Option<<Resolver<T> as Service>::Future>,
fut2: Option<<TcpConnector<T> as Service>::Future>,
tcp: TcpConnector<T>, tcp: TcpConnector<T>,
} }
impl<T: Address> Future for ConnectServiceResponse<T> { impl<T: Address> Future for ConnectServiceResponse<T> {
type Output = Result<Connection<T, TcpStream>, ConnectError>; type Item = Connection<T, TcpStream>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let res = match self.state.poll(cx) {
Either::Right(res) => {
self.state = ConnectState::Connect(self.tcp.call(res));
self.state.poll(cx)
}
Either::Left(res) => return res,
};
match res {
Either::Left(res) => res,
Either::Right(_) => panic!(),
}
}
}
#[derive(Clone)]
pub struct TcpConnectService<T> {
tcp: TcpConnector<T>,
resolver: Resolver<T>,
}
impl<T: Address + 'static> Service for TcpConnectService<T> {
type Request = Connect<T>;
type Response = TcpStream;
type Error = ConnectError; type Error = ConnectError;
type Future = TcpConnectServiceResponse<T>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
Poll::Ready(Ok(())) if let Some(ref mut fut) = self.fut1 {
} let res = try_ready!(fut.poll());
let _ = self.fut1.take();
fn call(&mut self, req: Connect<T>) -> Self::Future { self.fut2 = Some(self.tcp.call(res));
TcpConnectServiceResponse {
state: TcpConnectState::Resolve(self.resolver.call(req)),
tcp: self.tcp.clone(),
} }
}
}
enum TcpConnectState<T: Address> { if let Some(ref mut fut) = self.fut2 {
Resolve(<Resolver<T> as Service>::Future), return fut.poll();
Connect(<TcpConnector<T> as Service>::Future),
}
impl<T: Address> TcpConnectState<T> {
fn poll(
&mut self,
cx: &mut Context<'_>,
) -> Either<Poll<Result<TcpStream, ConnectError>>, Connect<T>> {
match self {
TcpConnectState::Resolve(ref mut fut) => match Pin::new(fut).poll(cx) {
Poll::Pending => (),
Poll::Ready(Ok(res)) => return Either::Right(res),
Poll::Ready(Err(err)) => return Either::Left(Poll::Ready(Err(err))),
},
TcpConnectState::Connect(ref mut fut) => {
if let Poll::Ready(res) = Pin::new(fut).poll(cx) {
return match res {
Ok(conn) => Either::Left(Poll::Ready(Ok(conn.into_parts().0))),
Err(err) => Either::Left(Poll::Ready(Err(err))),
};
}
}
}
Either::Left(Poll::Pending)
}
}
pub struct TcpConnectServiceResponse<T: Address> {
state: TcpConnectState<T>,
tcp: TcpConnector<T>,
}
impl<T: Address> Future for TcpConnectServiceResponse<T> {
type Output = Result<TcpStream, ConnectError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let res = match self.state.poll(cx) {
Either::Right(res) => {
self.state = TcpConnectState::Connect(self.tcp.call(res));
self.state.poll(cx)
}
Either::Left(res) => return res,
};
match res {
Either::Left(res) => res,
Either::Right(_) => panic!(),
} }
Ok(Async::NotReady)
} }
} }

View File

@@ -1,7 +1,12 @@
//! SSL Services //! SSL Services
#[cfg(feature = "openssl")] #[cfg(feature = "ssl")]
pub mod openssl; mod openssl;
#[cfg(feature = "ssl")]
#[cfg(feature = "rustls")] pub use self::openssl::{
pub mod rustls; OpensslConnectService, OpensslConnectServiceFactory, OpensslConnector,
};
#[cfg(feature = "rust-tls")]
mod rustls;
#[cfg(feature = "rust-tls")]
pub use self::rustls::RustlsConnector;

View File

@@ -1,23 +1,19 @@
use std::future::Future;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::pin::Pin;
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_service::{NewService, Service};
use actix_service::{Service, ServiceFactory}; use futures::{future::ok, future::FutureResult, try_ready, Async, Future, Poll};
use futures_util::future::{err, ok, Either, FutureExt, LocalBoxFuture, Ready}; use openssl::ssl::{HandshakeError, SslConnector};
use trust_dns_resolver::TokioAsyncResolver as AsyncResolver; use tokio_openssl::{ConnectAsync, SslConnectorExt, SslStream};
use tokio_tcp::TcpStream;
use trust_dns_resolver::AsyncResolver;
use crate::{ use crate::{
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<T, U> {
connector: SslConnector, connector: SslConnector,
_t: PhantomData<(T, U)>, _t: PhantomData<(T, U)>,
@@ -34,12 +30,18 @@ impl<T, U> OpensslConnector<T, U> {
impl<T, U> OpensslConnector<T, U> impl<T, U> OpensslConnector<T, U>
where where
T: Address + 'static, T: Address,
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static, U: AsyncRead + AsyncWrite + fmt::Debug,
{ {
pub fn service(connector: SslConnector) -> OpensslConnectorService<T, U> { pub fn service(
connector: SslConnector,
) -> impl Service<
Request = Connection<T, U>,
Response = Connection<T, SslStream<U>>,
Error = HandshakeError<U>,
> {
OpensslConnectorService { OpensslConnectorService {
connector, connector: connector,
_t: PhantomData, _t: PhantomData,
} }
} }
@@ -54,20 +56,19 @@ impl<T, U> Clone for OpensslConnector<T, U> {
} }
} }
impl<T, U> ServiceFactory for OpensslConnector<T, U> impl<T: Address, U> NewService for OpensslConnector<T, U>
where where
T: Address + 'static, U: AsyncRead + AsyncWrite + fmt::Debug,
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static,
{ {
type Request = Connection<T, U>; type Request = Connection<T, U>;
type Response = Connection<T, SslStream<U>>; type Response = Connection<T, SslStream<U>>;
type Error = io::Error; type Error = HandshakeError<U>;
type Config = (); type Config = ();
type Service = OpensslConnectorService<T, U>; type Service = OpensslConnectorService<T, U>;
type InitError = (); type InitError = ();
type Future = Ready<Result<Self::Service, Self::InitError>>; type Future = FutureResult<Self::Service, Self::InitError>;
fn new_service(&self, _: ()) -> Self::Future { fn new_service(&self, _: &()) -> Self::Future {
ok(OpensslConnectorService { ok(OpensslConnectorService {
connector: self.connector.clone(), connector: self.connector.clone(),
_t: PhantomData, _t: PhantomData,
@@ -89,64 +90,52 @@ impl<T, U> Clone for OpensslConnectorService<T, U> {
} }
} }
impl<T, U> Service for OpensslConnectorService<T, U> impl<T: Address, U> Service for OpensslConnectorService<T, U>
where where
T: Address + 'static, U: AsyncRead + AsyncWrite + fmt::Debug,
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static,
{ {
type Request = Connection<T, U>; type Request = Connection<T, U>;
type Response = Connection<T, SslStream<U>>; type Response = Connection<T, SslStream<U>>;
type Error = io::Error; type Error = HandshakeError<U>;
#[allow(clippy::type_complexity)] type Future = ConnectAsyncExt<T, U>;
type Future = Either<ConnectAsyncExt<T, U>, Ready<Result<Self::Response, Self::Error>>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Poll::Ready(Ok(())) Ok(Async::Ready(()))
} }
fn call(&mut self, stream: Connection<T, U>) -> Self::Future { fn call(&mut self, stream: Connection<T, U>) -> Self::Future {
trace!("SSL Handshake start for: {:?}", stream.host()); trace!("SSL Handshake start for: {:?}", stream.host());
let (io, stream) = stream.replace(()); let (io, stream) = stream.replace(());
let host = stream.host().to_string(); ConnectAsyncExt {
fut: SslConnectorExt::connect_async(&self.connector, stream.host(), io),
match self.connector.configure() { stream: Some(stream),
Err(e) => Either::Right(err(io::Error::new(io::ErrorKind::Other, e))),
Ok(config) => Either::Left(ConnectAsyncExt {
fut: async move { tokio_openssl::connect(config, &host, io).await }
.boxed_local(),
stream: Some(stream),
_t: PhantomData,
}),
} }
} }
} }
pub struct ConnectAsyncExt<T, U> { pub struct ConnectAsyncExt<T, U> {
fut: LocalBoxFuture<'static, Result<SslStream<U>, HandshakeError<U>>>, fut: ConnectAsync<U>,
stream: Option<Connection<T, ()>>, stream: Option<Connection<T, ()>>,
_t: PhantomData<U>,
} }
impl<T: Address, U> Future for ConnectAsyncExt<T, U> impl<T: Address, U> Future for ConnectAsyncExt<T, U>
where where
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static, U: AsyncRead + AsyncWrite + fmt::Debug,
{ {
type Output = Result<Connection<T, SslStream<U>>, io::Error>; type Item = Connection<T, SslStream<U>>;
type Error = HandshakeError<U>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let this = self.get_mut(); match self.fut.poll().map_err(|e| {
trace!("SSL Handshake error: {:?}", e);
match Pin::new(&mut this.fut).poll(cx) { e
Poll::Ready(Ok(stream)) => { })? {
let s = this.stream.take().unwrap(); Async::Ready(stream) => {
let s = self.stream.take().unwrap();
trace!("SSL Handshake success: {:?}", s.host()); trace!("SSL Handshake success: {:?}", s.host());
Poll::Ready(Ok(s.replace(stream).1)) Ok(Async::Ready(s.replace(stream).1))
} }
Poll::Ready(Err(e)) => { Async::NotReady => Ok(Async::NotReady),
trace!("SSL Handshake error: {:?}", e);
Poll::Ready(Err(io::Error::new(io::ErrorKind::Other, format!("{}", e))))
}
Poll::Pending => Poll::Pending,
} }
} }
} }
@@ -165,7 +154,7 @@ impl<T> OpensslConnectServiceFactory<T> {
} }
} }
/// Construct new connect service with custom DNS resolver /// Construct new connect service with custom dns resolver
pub fn with_resolver(connector: SslConnector, resolver: AsyncResolver) -> Self { pub fn with_resolver(connector: SslConnector, resolver: AsyncResolver) -> Self {
OpensslConnectServiceFactory { OpensslConnectServiceFactory {
tcp: ConnectServiceFactory::with_resolver(resolver), tcp: ConnectServiceFactory::with_resolver(resolver),
@@ -173,7 +162,7 @@ impl<T> OpensslConnectServiceFactory<T> {
} }
} }
/// Construct OpenSSL connect service /// Construct openssl connect service
pub fn service(&self) -> OpensslConnectService<T> { pub fn service(&self) -> OpensslConnectService<T> {
OpensslConnectService { OpensslConnectService {
tcp: self.tcp.service(), tcp: self.tcp.service(),
@@ -194,16 +183,16 @@ impl<T> Clone for OpensslConnectServiceFactory<T> {
} }
} }
impl<T: Address + 'static> ServiceFactory for OpensslConnectServiceFactory<T> { impl<T: Address> NewService for OpensslConnectServiceFactory<T> {
type Request = Connect<T>; type Request = Connect<T>;
type Response = SslStream<TcpStream>; type Response = SslStream<TcpStream>;
type Error = ConnectError; type Error = ConnectError;
type Config = (); type Config = ();
type Service = OpensslConnectService<T>; type Service = OpensslConnectService<T>;
type InitError = (); type InitError = ();
type Future = Ready<Result<Self::Service, Self::InitError>>; type Future = FutureResult<Self::Service, Self::InitError>;
fn new_service(&self, _: ()) -> Self::Future { fn new_service(&self, _: &()) -> Self::Future {
ok(self.service()) ok(self.service())
} }
} }
@@ -214,14 +203,14 @@ pub struct OpensslConnectService<T> {
openssl: OpensslConnectorService<T, TcpStream>, openssl: OpensslConnectorService<T, TcpStream>,
} }
impl<T: Address + 'static> Service for OpensslConnectService<T> { impl<T: Address> Service for OpensslConnectService<T> {
type Request = Connect<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>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Poll::Ready(Ok(())) Ok(Async::Ready(()))
} }
fn call(&mut self, req: Connect<T>) -> Self::Future { fn call(&mut self, req: Connect<T>) -> Self::Future {
@@ -233,36 +222,30 @@ impl<T: Address + 'static> Service for OpensslConnectService<T> {
} }
} }
pub struct OpensslConnectServiceResponse<T: Address + 'static> { pub struct OpensslConnectServiceResponse<T: Address> {
fut1: Option<<ConnectService<T> as Service>::Future>, fut1: Option<<ConnectService<T> as Service>::Future>,
fut2: Option<<OpensslConnectorService<T, TcpStream> as Service>::Future>, fut2: Option<<OpensslConnectorService<T, TcpStream> as Service>::Future>,
openssl: OpensslConnectorService<T, TcpStream>, openssl: OpensslConnectorService<T, TcpStream>,
} }
impl<T: Address> Future for OpensslConnectServiceResponse<T> { impl<T: Address> Future for OpensslConnectServiceResponse<T> {
type Output = Result<SslStream<TcpStream>, ConnectError>; type Item = SslStream<TcpStream>;
type Error = ConnectError;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
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)) { let res = try_ready!(fut.poll());
Ok(res) => { let _ = self.fut1.take();
let _ = self.fut1.take(); self.fut2 = Some(self.openssl.call(res));
self.fut2 = Some(self.openssl.call(res));
}
Err(e) => return Poll::Ready(Err(e)),
}
} }
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)) { let connect = try_ready!(fut
Ok(connect) => Poll::Ready(Ok(connect.into_parts().0)), .poll()
Err(e) => Poll::Ready(Err(ConnectError::Io(io::Error::new( .map_err(|e| ConnectError::Io(io::Error::new(io::ErrorKind::Other, e))));
io::ErrorKind::Other, Ok(Async::Ready(connect.into_parts().0))
e,
)))),
}
} else { } else {
Poll::Pending Ok(Async::NotReady)
} }
} }
} }

View File

@@ -1,17 +1,14 @@
use std::fmt; use std::fmt;
use std::future::Future;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
pub use rust_tls::Session;
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::{NewService, Service};
use futures_util::future::{ok, Ready}; use futures::{future::ok, future::FutureResult, Async, Future, Poll};
use tokio_rustls::{Connect, TlsConnector}; use std::sync::Arc;
use tokio_rustls::{
rustls::{ClientConfig, ClientSession},
Connect, TlsConnector, TlsStream,
};
use webpki::DNSNameRef; use webpki::DNSNameRef;
use crate::{Address, Connection}; use crate::{Address, Connection};
@@ -34,11 +31,17 @@ impl<T, U> RustlsConnector<T, U> {
impl<T, U> RustlsConnector<T, U> impl<T, U> RustlsConnector<T, U>
where where
T: Address, T: Address,
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug, U: AsyncRead + AsyncWrite + fmt::Debug,
{ {
pub fn service(connector: Arc<ClientConfig>) -> RustlsConnectorService<T, U> { pub fn service(
connector: Arc<ClientConfig>,
) -> impl Service<
Request = Connection<T, U>,
Response = Connection<T, TlsStream<U, ClientSession>>,
Error = std::io::Error,
> {
RustlsConnectorService { RustlsConnectorService {
connector, connector: connector,
_t: PhantomData, _t: PhantomData,
} }
} }
@@ -53,19 +56,19 @@ impl<T, U> Clone for RustlsConnector<T, U> {
} }
} }
impl<T: Address, U> ServiceFactory for RustlsConnector<T, U> impl<T: Address, U> NewService for RustlsConnector<T, U>
where where
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug, U: AsyncRead + AsyncWrite + fmt::Debug,
{ {
type Request = Connection<T, U>; type Request = Connection<T, U>;
type Response = Connection<T, TlsStream<U>>; type Response = Connection<T, TlsStream<U, ClientSession>>;
type Error = std::io::Error; type Error = std::io::Error;
type Config = (); type Config = ();
type Service = RustlsConnectorService<T, U>; type Service = RustlsConnectorService<T, U>;
type InitError = (); type InitError = ();
type Future = Ready<Result<Self::Service, Self::InitError>>; type Future = FutureResult<Self::Service, Self::InitError>;
fn new_service(&self, _: ()) -> Self::Future { fn new_service(&self, _: &()) -> Self::Future {
ok(RustlsConnectorService { ok(RustlsConnectorService {
connector: self.connector.clone(), connector: self.connector.clone(),
_t: PhantomData, _t: PhantomData,
@@ -78,33 +81,23 @@ pub struct RustlsConnectorService<T, U> {
_t: PhantomData<(T, U)>, _t: PhantomData<(T, U)>,
} }
impl<T, U> Clone for RustlsConnectorService<T, U> {
fn clone(&self) -> Self {
Self {
connector: self.connector.clone(),
_t: PhantomData,
}
}
}
impl<T: Address, U> Service for RustlsConnectorService<T, U> impl<T: Address, U> Service for RustlsConnectorService<T, U>
where where
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug, U: AsyncRead + AsyncWrite + fmt::Debug,
{ {
type Request = Connection<T, U>; type Request = Connection<T, U>;
type Response = Connection<T, TlsStream<U>>; type Response = Connection<T, TlsStream<U, ClientSession>>;
type Error = std::io::Error; type Error = std::io::Error;
type Future = ConnectAsyncExt<T, U>; type Future = ConnectAsyncExt<T, U>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Poll::Ready(Ok(())) Ok(Async::Ready(()))
} }
fn call(&mut self, stream: Connection<T, U>) -> Self::Future { fn call(&mut self, stream: Connection<T, U>) -> Self::Future {
trace!("SSL Handshake start for: {:?}", stream.host()); trace!("SSL Handshake start for: {:?}", stream.host());
let (io, stream) = stream.replace(()); let (io, stream) = stream.replace(());
let host = DNSNameRef::try_from_ascii_str(stream.host()) let host = DNSNameRef::try_from_ascii_str(stream.host()).unwrap();
.expect("rustls currently only handles hostname-based connections. See https://github.com/briansmith/webpki/issues/54");
ConnectAsyncExt { ConnectAsyncExt {
fut: TlsConnector::from(self.connector.clone()).connect(host, io), fut: TlsConnector::from(self.connector.clone()).connect(host, io),
stream: Some(stream), stream: Some(stream),
@@ -119,18 +112,22 @@ pub struct ConnectAsyncExt<T, U> {
impl<T: Address, U> Future for ConnectAsyncExt<T, U> impl<T: Address, U> Future for ConnectAsyncExt<T, U>
where where
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug, U: AsyncRead + AsyncWrite + fmt::Debug,
{ {
type Output = Result<Connection<T, TlsStream<U>>, std::io::Error>; type Item = Connection<T, TlsStream<U, ClientSession>>;
type Error = std::io::Error;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let this = self.get_mut(); match self.fut.poll().map_err(|e| {
Poll::Ready( trace!("SSL Handshake error: {:?}", e);
futures_util::ready!(Pin::new(&mut this.fut).poll(cx)).map(|stream| { e
let s = this.stream.take().unwrap(); })? {
Async::Ready(stream) => {
let s = self.stream.take().unwrap();
trace!("SSL Handshake success: {:?}", s.host()); trace!("SSL Handshake success: {:?}", s.host());
s.replace(stream).1 Ok(Async::Ready(s.replace(stream).1))
}), }
) Async::NotReady => Ok(Async::NotReady),
}
} }
} }

View File

@@ -1,127 +1,142 @@
use std::io;
use actix_codec::{BytesCodec, Framed}; use actix_codec::{BytesCodec, Framed};
use actix_rt::net::TcpStream; use actix_server_config::Io;
use actix_service::{fn_service, Service, ServiceFactory}; use actix_service::{service_fn, NewService, Service};
use actix_testing::TestServer; use actix_test_server::TestServer;
use bytes::Bytes; use bytes::Bytes;
use futures_util::sink::SinkExt; use futures::{future::lazy, Future, Sink};
use http::{HttpTryFrom, Uri};
use trust_dns_resolver::config::{ResolverConfig, ResolverOpts};
use actix_connect::resolver::{ResolverConfig, ResolverOpts}; use actix_connect::{default_connector, Connect};
use actix_connect::Connect;
#[cfg(feature = "openssl")] #[cfg(feature = "ssl")]
#[actix_rt::test] #[test]
async fn test_string() { fn test_string() {
let srv = TestServer::with(|| { let mut srv = TestServer::with(|| {
fn_service(|io: TcpStream| async { service_fn(|io: Io<tokio_tcp::TcpStream>| {
let mut framed = Framed::new(io, BytesCodec); Framed::new(io.into_parts().0, BytesCodec)
framed.send(Bytes::from_static(b"test")).await?; .send(Bytes::from_static(b"test"))
Ok::<_, io::Error>(()) .then(|_| Ok::<_, ()>(()))
}) })
}); });
let mut conn = actix_connect::default_connector(); let mut conn = default_connector();
let addr = format!("localhost:{}", srv.port()); let addr = format!("localhost:{}", srv.port());
let con = conn.call(addr.into()).await.unwrap(); let con = srv.run_on(move || conn.call(addr.into())).unwrap();
assert_eq!(con.peer_addr().unwrap(), srv.addr()); assert_eq!(con.peer_addr().unwrap(), srv.addr());
} }
#[cfg(feature = "rustls")] #[cfg(feature = "rust-tls")]
#[actix_rt::test] #[test]
async fn test_rustls_string() { fn test_rustls_string() {
let srv = TestServer::with(|| { let mut srv = TestServer::with(|| {
fn_service(|io: TcpStream| async { service_fn(|io: Io<tokio_tcp::TcpStream>| {
let mut framed = Framed::new(io, BytesCodec); Framed::new(io.into_parts().0, BytesCodec)
framed.send(Bytes::from_static(b"test")).await?; .send(Bytes::from_static(b"test"))
Ok::<_, io::Error>(()) .then(|_| Ok::<_, ()>(()))
}) })
}); });
let mut conn = actix_connect::default_connector(); let mut conn = default_connector();
let addr = format!("localhost:{}", srv.port()); let addr = format!("localhost:{}", srv.port());
let con = conn.call(addr.into()).await.unwrap(); let con = srv.run_on(move || conn.call(addr.into())).unwrap();
assert_eq!(con.peer_addr().unwrap(), srv.addr()); assert_eq!(con.peer_addr().unwrap(), srv.addr());
} }
#[test]
#[actix_rt::test] fn test_static_str() {
async fn test_static_str() { let mut srv = TestServer::with(|| {
let srv = TestServer::with(|| { service_fn(|io: Io<tokio_tcp::TcpStream>| {
fn_service(|io: TcpStream| async { Framed::new(io.into_parts().0, BytesCodec)
let mut framed = Framed::new(io, BytesCodec); .send(Bytes::from_static(b"test"))
framed.send(Bytes::from_static(b"test")).await?; .then(|_| Ok::<_, ()>(()))
Ok::<_, io::Error>(())
}) })
}); });
let resolver = actix_connect::start_default_resolver().await.unwrap(); let resolver = srv
let mut conn = actix_connect::new_connector(resolver.clone()); .block_on(lazy(
|| Ok::<_, ()>(actix_connect::start_default_resolver()),
))
.unwrap();
let mut conn = srv
.block_on(lazy(|| {
Ok::<_, ()>(actix_connect::new_connector(resolver.clone()))
}))
.unwrap();
let con = conn.call(Connect::with("10", srv.addr())).await.unwrap(); let con = srv
.block_on(conn.call(Connect::with("10", srv.addr())))
.unwrap();
assert_eq!(con.peer_addr().unwrap(), srv.addr()); assert_eq!(con.peer_addr().unwrap(), srv.addr());
let connect = Connect::new(srv.host().to_owned()); let connect = Connect::new(srv.host().to_owned());
let mut conn = actix_connect::new_connector(resolver); let mut conn = srv
let con = conn.call(connect).await; .block_on(lazy(|| Ok::<_, ()>(actix_connect::new_connector(resolver))))
.unwrap();
let con = srv.block_on(conn.call(connect));
assert!(con.is_err()); assert!(con.is_err());
} }
#[actix_rt::test] #[test]
async fn test_new_service() { fn test_new_service() {
let srv = TestServer::with(|| { let mut srv = TestServer::with(|| {
fn_service(|io: TcpStream| async { service_fn(|io: Io<tokio_tcp::TcpStream>| {
let mut framed = Framed::new(io, BytesCodec); Framed::new(io.into_parts().0, BytesCodec)
framed.send(Bytes::from_static(b"test")).await?; .send(Bytes::from_static(b"test"))
Ok::<_, io::Error>(()) .then(|_| Ok::<_, ()>(()))
}) })
}); });
let resolver = let resolver = srv
actix_connect::start_resolver(ResolverConfig::default(), ResolverOpts::default()) .block_on(lazy(|| {
.await Ok::<_, ()>(actix_connect::start_resolver(
.unwrap(); ResolverConfig::default(),
ResolverOpts::default(),
))
}))
.unwrap();
let factory = srv
.block_on(lazy(|| {
Ok::<_, ()>(actix_connect::new_connector_factory(resolver))
}))
.unwrap();
let factory = actix_connect::new_connector_factory(resolver); let mut conn = srv.block_on(factory.new_service(&())).unwrap();
let con = srv
let mut conn = factory.new_service(()).await.unwrap(); .block_on(conn.call(Connect::with("10", srv.addr())))
let con = conn.call(Connect::with("10", srv.addr())).await.unwrap(); .unwrap();
assert_eq!(con.peer_addr().unwrap(), srv.addr()); assert_eq!(con.peer_addr().unwrap(), srv.addr());
} }
#[cfg(all(feature = "openssl", feature = "uri"))] #[cfg(feature = "ssl")]
#[actix_rt::test] #[test]
async fn test_openssl_uri() { fn test_uri() {
use std::convert::TryFrom; let mut srv = TestServer::with(|| {
service_fn(|io: Io<tokio_tcp::TcpStream>| {
let srv = TestServer::with(|| { Framed::new(io.into_parts().0, BytesCodec)
fn_service(|io: TcpStream| async { .send(Bytes::from_static(b"test"))
let mut framed = Framed::new(io, BytesCodec); .then(|_| Ok::<_, ()>(()))
framed.send(Bytes::from_static(b"test")).await?;
Ok::<_, io::Error>(())
}) })
}); });
let mut conn = actix_connect::default_connector(); let mut conn = default_connector();
let addr = http::Uri::try_from(format!("https://localhost:{}", srv.port())).unwrap(); let addr = Uri::try_from(format!("https://localhost:{}", srv.port())).unwrap();
let con = conn.call(addr.into()).await.unwrap(); let con = srv.run_on(move || conn.call(addr.into())).unwrap();
assert_eq!(con.peer_addr().unwrap(), srv.addr()); assert_eq!(con.peer_addr().unwrap(), srv.addr());
} }
#[cfg(all(feature = "rustls", feature = "uri"))] #[cfg(feature = "rust-tls")]
#[actix_rt::test] #[test]
async fn test_rustls_uri() { fn test_rustls_uri() {
use std::convert::TryFrom; let mut srv = TestServer::with(|| {
service_fn(|io: Io<tokio_tcp::TcpStream>| {
let srv = TestServer::with(|| { Framed::new(io.into_parts().0, BytesCodec)
fn_service(|io: TcpStream| async { .send(Bytes::from_static(b"test"))
let mut framed = Framed::new(io, BytesCodec); .then(|_| Ok::<_, ()>(()))
framed.send(Bytes::from_static(b"test")).await?;
Ok::<_, io::Error>(())
}) })
}); });
let mut conn = actix_connect::default_connector(); let mut conn = default_connector();
let addr = http::Uri::try_from(format!("https://localhost:{}", srv.port())).unwrap(); let addr = Uri::try_from(format!("https://localhost:{}", srv.port())).unwrap();
let con = conn.call(addr.into()).await.unwrap(); let con = srv.run_on(move || conn.call(addr.into())).unwrap();
assert_eq!(con.peer_addr().unwrap(), srv.addr()); assert_eq!(con.peer_addr().unwrap(), srv.addr());
} }

View File

@@ -1,5 +1,5 @@
# Changes # Changes
## [0.1.0] - 2020-01-15 ## [0.1.0] - 2019-07-17
* Initial release * Initial release

35
actix-ioframe/Cargo.toml Normal file
View File

@@ -0,0 +1,35 @@
[package]
name = "actix-ioframe"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix framed service"
keywords = ["network", "framework", "async", "futures"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-ioframed/"
categories = ["network-programming", "asynchronous"]
license = "MIT/Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
edition = "2018"
workspace = ".."
[lib]
name = "actix_ioframe"
path = "src/lib.rs"
[dependencies]
actix-service = "0.4.1"
actix-codec = "0.1.2"
bytes = "0.4"
either = "1.5.2"
futures = "0.1.25"
tokio-current-thread = "0.1.4"
log = "0.4"
[dev-dependencies]
actix-rt = "0.2.2"
actix-connect = "0.2.0"
actix-test-server = "0.2.2"
actix-server-config = "0.1.1"
tokio-tcp = "0.1"
tokio-timer = "0.2"

View File

@@ -1,3 +0,0 @@
# actix-ioframe
**This crate has been deprecated and removed.**

35
actix-ioframe/src/cell.rs Normal file
View File

@@ -0,0 +1,35 @@
//! Custom cell impl
use std::cell::UnsafeCell;
use std::fmt;
use std::rc::Rc;
pub(crate) struct Cell<T> {
inner: Rc<UnsafeCell<T>>,
}
impl<T> Clone for Cell<T> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}
impl<T: fmt::Debug> fmt::Debug for Cell<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.inner.fmt(f)
}
}
impl<T> Cell<T> {
pub fn new(inner: T) -> Self {
Self {
inner: Rc::new(UnsafeCell::new(inner)),
}
}
pub(crate) unsafe fn get_mut(&mut self) -> &mut T {
&mut *self.inner.as_ref().get()
}
}

View File

@@ -0,0 +1,110 @@
use std::marker::PhantomData;
use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed};
use futures::unsync::mpsc;
use crate::dispatcher::FramedMessage;
use crate::sink::Sink;
pub struct Connect<Io, St = (), Codec = ()> {
io: Io,
_t: PhantomData<(St, Codec)>,
}
impl<Io> Connect<Io>
where
Io: AsyncRead + AsyncWrite,
{
pub(crate) fn new(io: Io) -> Self {
Self {
io,
_t: PhantomData,
}
}
pub fn codec<Codec>(self, codec: Codec) -> ConnectResult<Io, (), Codec>
where
Codec: Encoder + Decoder,
{
let (tx, rx) = mpsc::unbounded();
let sink = Sink::new(tx);
ConnectResult {
state: (),
framed: Framed::new(self.io, codec),
rx,
sink,
}
}
}
pub struct ConnectResult<Io, St, Codec: Encoder + Decoder> {
pub(crate) state: St,
pub(crate) framed: Framed<Io, Codec>,
pub(crate) rx: mpsc::UnboundedReceiver<FramedMessage<<Codec as Encoder>::Item>>,
pub(crate) sink: Sink<<Codec as Encoder>::Item>,
}
impl<Io, St, Codec: Encoder + Decoder> ConnectResult<Io, St, Codec> {
#[inline]
pub fn sink(&self) -> &Sink<<Codec as Encoder>::Item> {
&self.sink
}
#[inline]
pub fn get_ref(&self) -> &Io {
self.framed.get_ref()
}
#[inline]
pub fn get_mut(&mut self) -> &mut Io {
self.framed.get_mut()
}
#[inline]
pub fn state<S>(self, state: S) -> ConnectResult<Io, S, Codec> {
ConnectResult {
state,
framed: self.framed,
rx: self.rx,
sink: self.sink,
}
}
}
impl<Io, St, Codec> futures::Stream for ConnectResult<Io, St, Codec>
where
Io: AsyncRead + AsyncWrite,
Codec: Encoder + Decoder,
{
type Item = <Codec as Decoder>::Item;
type Error = <Codec as Decoder>::Error;
fn poll(&mut self) -> futures::Poll<Option<Self::Item>, Self::Error> {
self.framed.poll()
}
}
impl<Io, St, Codec> futures::Sink for ConnectResult<Io, St, Codec>
where
Io: AsyncRead + AsyncWrite,
Codec: Encoder + Decoder,
{
type SinkItem = <Codec as Encoder>::Item;
type SinkError = <Codec as Encoder>::Error;
fn start_send(
&mut self,
item: Self::SinkItem,
) -> futures::StartSend<Self::SinkItem, Self::SinkError> {
self.framed.start_send(item)
}
fn poll_complete(&mut self) -> futures::Poll<(), Self::SinkError> {
self.framed.poll_complete()
}
fn close(&mut self) -> futures::Poll<(), Self::SinkError> {
self.framed.close()
}
}

View File

@@ -0,0 +1,325 @@
//! Framed dispatcher service and related utilities
use std::collections::VecDeque;
use std::mem;
use std::rc::Rc;
use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed};
use actix_service::{IntoService, Service};
use futures::task::AtomicTask;
use futures::unsync::{mpsc, oneshot};
use futures::{Async, Future, Poll, Sink as FutureSink, Stream};
use log::debug;
use crate::cell::Cell;
use crate::error::ServiceError;
use crate::item::Item;
use crate::sink::Sink;
use crate::state::State;
type Request<S, U> = Item<S, U>;
type Response<U> = <U as Encoder>::Item;
pub(crate) enum FramedMessage<T> {
Message(T),
Close,
WaitClose(oneshot::Sender<()>),
}
/// FramedTransport - is a future that reads frames from Framed object
/// and pass then to the service.
pub(crate) struct FramedDispatcher<St, S, T, U>
where
S: Service<Request = Request<St, U>, Response = Option<Response<U>>>,
S::Error: 'static,
S::Future: 'static,
T: AsyncRead + AsyncWrite,
U: Encoder + Decoder,
<U as Encoder>::Item: 'static,
<U as Encoder>::Error: std::fmt::Debug,
{
service: S,
sink: Sink<<U as Encoder>::Item>,
state: State<St>,
dispatch_state: FramedState<S, U>,
framed: Framed<T, U>,
rx: Option<mpsc::UnboundedReceiver<FramedMessage<<U as Encoder>::Item>>>,
inner: Cell<FramedDispatcherInner<<U as Encoder>::Item, S::Error>>,
disconnect: Option<Rc<Fn(&mut St, bool)>>,
}
impl<St, S, T, U> FramedDispatcher<St, S, T, U>
where
S: Service<Request = Request<St, U>, Response = Option<Response<U>>>,
S::Error: 'static,
S::Future: 'static,
T: AsyncRead + AsyncWrite,
U: Decoder + Encoder,
<U as Encoder>::Item: 'static,
<U as Encoder>::Error: std::fmt::Debug,
{
pub(crate) fn new<F: IntoService<S>>(
framed: Framed<T, U>,
state: State<St>,
service: F,
rx: mpsc::UnboundedReceiver<FramedMessage<<U as Encoder>::Item>>,
sink: Sink<<U as Encoder>::Item>,
disconnect: Option<Rc<Fn(&mut St, bool)>>,
) -> Self {
FramedDispatcher {
framed,
state,
sink,
disconnect,
rx: Some(rx),
service: service.into_service(),
dispatch_state: FramedState::Processing,
inner: Cell::new(FramedDispatcherInner {
buf: VecDeque::new(),
task: AtomicTask::new(),
}),
}
}
}
enum FramedState<S: Service, U: Encoder + Decoder> {
Processing,
Error(ServiceError<S::Error, U>),
FramedError(ServiceError<S::Error, U>),
FlushAndStop(Vec<oneshot::Sender<()>>),
Stopping,
}
impl<S: Service, U: Encoder + Decoder> FramedState<S, U> {
fn stop(&mut self, tx: Option<oneshot::Sender<()>>) {
match self {
FramedState::FlushAndStop(ref mut vec) => {
if let Some(tx) = tx {
vec.push(tx)
}
}
FramedState::Processing => {
*self = FramedState::FlushAndStop(if let Some(tx) = tx {
vec![tx]
} else {
Vec::new()
})
}
FramedState::Error(_) | FramedState::FramedError(_) | FramedState::Stopping => {
if let Some(tx) = tx {
let _ = tx.send(());
}
}
}
}
}
struct FramedDispatcherInner<I, E> {
buf: VecDeque<Result<I, E>>,
task: AtomicTask,
}
impl<St, S, T, U> FramedDispatcher<St, S, T, U>
where
S: Service<Request = Request<St, U>, Response = Option<Response<U>>>,
S::Error: 'static,
S::Future: 'static,
T: AsyncRead + AsyncWrite,
U: Decoder + Encoder,
<U as Encoder>::Item: 'static,
<U as Encoder>::Error: std::fmt::Debug,
{
fn disconnect(&mut self, error: bool) {
if let Some(ref disconnect) = self.disconnect {
(&*disconnect)(&mut *self.state.get_mut(), error);
}
}
fn poll_read(&mut self) -> bool {
loop {
match self.service.poll_ready() {
Ok(Async::Ready(_)) => {
let item = match self.framed.poll() {
Ok(Async::Ready(Some(el))) => el,
Err(err) => {
self.dispatch_state =
FramedState::FramedError(ServiceError::Decoder(err));
return true;
}
Ok(Async::NotReady) => return false,
Ok(Async::Ready(None)) => {
self.dispatch_state = FramedState::Stopping;
return true;
}
};
let mut cell = self.inner.clone();
unsafe { cell.get_mut().task.register() };
tokio_current_thread::spawn(
self.service
.call(Item::new(self.state.clone(), self.sink.clone(), item))
.then(move |item| {
let item = match item {
Ok(Some(item)) => Ok(item),
Ok(None) => return Ok(()),
Err(err) => Err(err),
};
unsafe {
let inner = cell.get_mut();
inner.buf.push_back(item);
inner.task.notify();
}
Ok(())
}),
);
}
Ok(Async::NotReady) => return false,
Err(err) => {
self.dispatch_state = FramedState::Error(ServiceError::Service(err));
return true;
}
}
}
}
/// write to framed object
fn poll_write(&mut self) -> bool {
let inner = unsafe { self.inner.get_mut() };
let mut rx_done = self.rx.is_none();
let mut buf_empty = inner.buf.is_empty();
loop {
while !self.framed.is_write_buf_full() {
if !buf_empty {
match inner.buf.pop_front().unwrap() {
Ok(msg) => {
if let Err(err) = self.framed.force_send(msg) {
self.dispatch_state =
FramedState::FramedError(ServiceError::Encoder(err));
return true;
}
buf_empty = inner.buf.is_empty();
}
Err(err) => {
self.dispatch_state =
FramedState::Error(ServiceError::Service(err));
return true;
}
}
}
if !rx_done && self.rx.is_some() {
match self.rx.as_mut().unwrap().poll() {
Ok(Async::Ready(Some(FramedMessage::Message(msg)))) => {
if let Err(err) = self.framed.force_send(msg) {
self.dispatch_state =
FramedState::FramedError(ServiceError::Encoder(err));
return true;
}
}
Ok(Async::Ready(Some(FramedMessage::Close))) => {
self.dispatch_state.stop(None);
return true;
}
Ok(Async::Ready(Some(FramedMessage::WaitClose(tx)))) => {
self.dispatch_state.stop(Some(tx));
return true;
}
Ok(Async::Ready(None)) => {
rx_done = true;
let _ = self.rx.take();
}
Ok(Async::NotReady) => rx_done = true,
Err(_e) => {
rx_done = true;
let _ = self.rx.take();
}
}
}
if rx_done && buf_empty {
break;
}
}
if !self.framed.is_write_buf_empty() {
match self.framed.poll_complete() {
Ok(Async::NotReady) => break,
Err(err) => {
debug!("Error sending data: {:?}", err);
self.dispatch_state =
FramedState::FramedError(ServiceError::Encoder(err));
return true;
}
Ok(Async::Ready(_)) => (),
}
} else {
break;
}
}
false
}
}
impl<St, S, T, U> Future for FramedDispatcher<St, S, T, U>
where
S: Service<Request = Request<St, U>, Response = Option<Response<U>>>,
S::Error: 'static,
S::Future: 'static,
T: AsyncRead + AsyncWrite,
U: Decoder + Encoder,
<U as Encoder>::Item: 'static,
<U as Encoder>::Error: std::fmt::Debug,
{
type Item = ();
type Error = ServiceError<S::Error, U>;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match mem::replace(&mut self.dispatch_state, FramedState::Processing) {
FramedState::Processing => {
if self.poll_read() || self.poll_write() {
self.poll()
} else {
Ok(Async::NotReady)
}
}
FramedState::Error(err) => {
if self.framed.is_write_buf_empty()
|| (self.poll_write() || self.framed.is_write_buf_empty())
{
self.disconnect(true);
Err(err)
} else {
self.dispatch_state = FramedState::Error(err);
Ok(Async::NotReady)
}
}
FramedState::FlushAndStop(mut vec) => {
if !self.framed.is_write_buf_empty() {
match self.framed.poll_complete() {
Err(err) => {
debug!("Error sending data: {:?}", err);
}
Ok(Async::NotReady) => {
self.dispatch_state = FramedState::FlushAndStop(vec);
return Ok(Async::NotReady);
}
Ok(Async::Ready(_)) => (),
}
};
for tx in vec.drain(..) {
let _ = tx.send(());
}
self.disconnect(false);
Ok(Async::Ready(()))
}
FramedState::FramedError(err) => {
self.disconnect(true);
Err(err)
}
FramedState::Stopping => {
self.disconnect(false);
Ok(Async::Ready(()))
}
}
}
}

View File

@@ -0,0 +1,49 @@
use std::fmt;
use actix_codec::{Decoder, Encoder};
/// Framed service errors
pub enum ServiceError<E, U: Encoder + Decoder> {
/// Inner service error
Service(E),
/// Encoder parse error
Encoder(<U as Encoder>::Error),
/// Decoder parse error
Decoder(<U as Decoder>::Error),
}
impl<E, U: Encoder + Decoder> From<E> for ServiceError<E, U> {
fn from(err: E) -> Self {
ServiceError::Service(err)
}
}
impl<E, U: Encoder + Decoder> fmt::Debug for ServiceError<E, U>
where
E: fmt::Debug,
<U as Encoder>::Error: fmt::Debug,
<U as Decoder>::Error: fmt::Debug,
{
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match *self {
ServiceError::Service(ref e) => write!(fmt, "ServiceError::Service({:?})", e),
ServiceError::Encoder(ref e) => write!(fmt, "ServiceError::Encoder({:?})", e),
ServiceError::Decoder(ref e) => write!(fmt, "ServiceError::Encoder({:?})", e),
}
}
}
impl<E, U: Encoder + Decoder> fmt::Display for ServiceError<E, U>
where
E: fmt::Display,
<U as Encoder>::Error: fmt::Debug,
<U as Decoder>::Error: fmt::Debug,
{
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match *self {
ServiceError::Service(ref e) => write!(fmt, "{}", e),
ServiceError::Encoder(ref e) => write!(fmt, "{:?}", e),
ServiceError::Decoder(ref e) => write!(fmt, "{:?}", e),
}
}
}

90
actix-ioframe/src/item.rs Normal file
View File

@@ -0,0 +1,90 @@
use std::cell::{Ref, RefMut};
use std::fmt;
use std::ops::{Deref, DerefMut};
use actix_codec::{Decoder, Encoder};
use crate::sink::Sink;
use crate::state::State;
pub struct Item<St, Codec: Encoder + Decoder> {
state: State<St>,
sink: Sink<<Codec as Encoder>::Item>,
item: <Codec as Decoder>::Item,
}
impl<St, Codec> Item<St, Codec>
where
Codec: Encoder + Decoder,
{
pub(crate) fn new(
state: State<St>,
sink: Sink<<Codec as Encoder>::Item>,
item: <Codec as Decoder>::Item,
) -> Self {
Item { state, sink, item }
}
#[inline]
pub fn state(&self) -> Ref<St> {
self.state.get_ref()
}
#[inline]
pub fn state_mut(&mut self) -> RefMut<St> {
self.state.get_mut()
}
#[inline]
pub fn sink(&self) -> &Sink<<Codec as Encoder>::Item> {
&self.sink
}
#[inline]
pub fn into_inner(self) -> <Codec as Decoder>::Item {
self.item
}
#[inline]
pub fn into_parts(
self,
) -> (
State<St>,
Sink<<Codec as Encoder>::Item>,
<Codec as Decoder>::Item,
) {
(self.state, self.sink, self.item)
}
}
impl<St, Codec> Deref for Item<St, Codec>
where
Codec: Encoder + Decoder,
{
type Target = <Codec as Decoder>::Item;
#[inline]
fn deref(&self) -> &<Codec as Decoder>::Item {
&self.item
}
}
impl<St, Codec> DerefMut for Item<St, Codec>
where
Codec: Encoder + Decoder,
{
#[inline]
fn deref_mut(&mut self) -> &mut <Codec as Decoder>::Item {
&mut self.item
}
}
impl<St, Codec> fmt::Debug for Item<St, Codec>
where
Codec: Encoder + Decoder,
<Codec as Decoder>::Item: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("FramedItem").field(&self.item).finish()
}
}

15
actix-ioframe/src/lib.rs Normal file
View File

@@ -0,0 +1,15 @@
mod cell;
mod connect;
mod dispatcher;
mod error;
mod item;
mod service;
mod sink;
mod state;
pub use self::connect::{Connect, ConnectResult};
pub use self::error::ServiceError;
pub use self::item::Item;
pub use self::service::{Builder, NewServiceBuilder, ServiceBuilder};
pub use self::sink::Sink;
pub use self::state::State;

View File

@@ -0,0 +1,363 @@
use std::marker::PhantomData;
use std::rc::Rc;
use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder};
use actix_service::{IntoNewService, IntoService, NewService, Service};
use futures::{Async, Future, Poll};
use crate::connect::{Connect, ConnectResult};
use crate::dispatcher::FramedDispatcher;
use crate::error::ServiceError;
use crate::item::Item;
use crate::state::State;
type RequestItem<S, U> = Item<S, U>;
type ResponseItem<U> = Option<<U as Encoder>::Item>;
/// Service builder - structure that follows the builder pattern
/// for building instances for framed services.
pub struct Builder<St, Codec>(PhantomData<(St, Codec)>);
impl<St, Codec> Builder<St, Codec> {
pub fn new() -> Builder<St, Codec> {
Builder(PhantomData)
}
/// Construct framed handler service with specified connect service
pub fn service<Io, C, F>(self, connect: F) -> ServiceBuilder<St, C, Io, Codec>
where
F: IntoService<C>,
Io: AsyncRead + AsyncWrite,
C: Service<Request = Connect<Io>, Response = ConnectResult<Io, St, Codec>>,
Codec: Decoder + Encoder,
{
ServiceBuilder {
connect: connect.into_service(),
disconnect: None,
_t: PhantomData,
}
}
/// Construct framed handler new service with specified connect service
pub fn factory<Io, C, F>(self, connect: F) -> NewServiceBuilder<St, C, Io, Codec>
where
F: IntoNewService<C>,
Io: AsyncRead + AsyncWrite,
C: NewService<
Config = (),
Request = Connect<Io>,
Response = ConnectResult<Io, St, Codec>,
>,
C::Error: 'static,
C::Future: 'static,
Codec: Decoder + Encoder,
{
NewServiceBuilder {
connect: connect.into_new_service(),
disconnect: None,
_t: PhantomData,
}
}
}
pub struct ServiceBuilder<St, C, Io, Codec> {
connect: C,
disconnect: Option<Rc<Fn(&mut St, bool)>>,
_t: PhantomData<(St, Io, Codec)>,
}
impl<St, C, Io, Codec> ServiceBuilder<St, C, Io, Codec>
where
St: 'static,
Io: AsyncRead + AsyncWrite,
C: Service<Request = Connect<Io>, Response = ConnectResult<Io, St, Codec>>,
C::Error: 'static,
Codec: Decoder + Encoder,
<Codec as Encoder>::Item: 'static,
<Codec as Encoder>::Error: std::fmt::Debug,
{
/// Callback to execute on disconnect
///
/// Second parameter indicates error occured during disconnect.
pub fn disconnect<F, Out>(mut self, disconnect: F) -> Self
where
F: Fn(&mut St, bool) + 'static,
{
self.disconnect = Some(Rc::new(disconnect));
self
}
/// Provide stream items handler service and construct service factory.
pub fn finish<F, T>(
self,
service: F,
) -> impl Service<Request = Io, Response = (), Error = ServiceError<C::Error, Codec>>
where
F: IntoNewService<T>,
T: NewService<
Config = St,
Request = RequestItem<St, Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
> + 'static,
{
FramedServiceImpl {
connect: self.connect,
handler: Rc::new(service.into_new_service()),
disconnect: self.disconnect.clone(),
_t: PhantomData,
}
}
}
pub struct NewServiceBuilder<St, C, Io, Codec> {
connect: C,
disconnect: Option<Rc<Fn(&mut St, bool)>>,
_t: PhantomData<(St, Io, Codec)>,
}
impl<St, C, Io, Codec> NewServiceBuilder<St, C, Io, Codec>
where
St: 'static,
Io: AsyncRead + AsyncWrite,
C: NewService<Config = (), Request = Connect<Io>, Response = ConnectResult<Io, St, Codec>>,
C::Error: 'static,
C::Future: 'static,
Codec: Decoder + Encoder,
<Codec as Encoder>::Item: 'static,
<Codec as Encoder>::Error: std::fmt::Debug,
{
/// Callback to execute on disconnect
///
/// Second parameter indicates error occured during disconnect.
pub fn disconnect<F>(mut self, disconnect: F) -> Self
where
F: Fn(&mut St, bool) + 'static,
{
self.disconnect = Some(Rc::new(disconnect));
self
}
pub fn finish<F, T, Cfg>(
self,
service: F,
) -> impl NewService<
Config = Cfg,
Request = Io,
Response = (),
Error = ServiceError<C::Error, Codec>,
>
where
F: IntoNewService<T>,
T: NewService<
Config = St,
Request = RequestItem<St, Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
> + 'static,
{
FramedService {
connect: self.connect,
handler: Rc::new(service.into_new_service()),
disconnect: self.disconnect,
_t: PhantomData,
}
}
}
pub(crate) struct FramedService<St, C, T, Io, Codec, Cfg> {
connect: C,
handler: Rc<T>,
disconnect: Option<Rc<Fn(&mut St, bool)>>,
_t: PhantomData<(St, Io, Codec, Cfg)>,
}
impl<St, C, T, Io, Codec, Cfg> NewService for FramedService<St, C, T, Io, Codec, Cfg>
where
St: 'static,
Io: AsyncRead + AsyncWrite,
C: NewService<Config = (), Request = Connect<Io>, Response = ConnectResult<Io, St, Codec>>,
C::Error: 'static,
C::Future: 'static,
T: NewService<
Config = St,
Request = RequestItem<St, Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
> + 'static,
Codec: Decoder + Encoder,
<Codec as Encoder>::Item: 'static,
<Codec as Encoder>::Error: std::fmt::Debug,
{
type Config = Cfg;
type Request = Io;
type Response = ();
type Error = ServiceError<C::Error, Codec>;
type InitError = C::InitError;
type Service = FramedServiceImpl<St, C::Service, T, Io, Codec>;
type Future = Box<Future<Item = Self::Service, Error = Self::InitError>>;
fn new_service(&self, _: &Cfg) -> Self::Future {
let handler = self.handler.clone();
let disconnect = self.disconnect.clone();
// create connect service and then create service impl
Box::new(
self.connect
.new_service(&())
.map(move |connect| FramedServiceImpl {
connect,
handler,
disconnect,
_t: PhantomData,
}),
)
}
}
pub struct FramedServiceImpl<St, C, T, Io, Codec> {
connect: C,
handler: Rc<T>,
disconnect: Option<Rc<Fn(&mut St, bool)>>,
_t: PhantomData<(St, Io, Codec)>,
}
impl<St, C, T, Io, Codec> Service for FramedServiceImpl<St, C, T, Io, Codec>
where
Io: AsyncRead + AsyncWrite,
C: Service<Request = Connect<Io>, Response = ConnectResult<Io, St, Codec>>,
C::Error: 'static,
T: NewService<
Config = St,
Request = RequestItem<St, Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
>,
<<T as NewService>::Service as Service>::Future: 'static,
Codec: Decoder + Encoder,
<Codec as Encoder>::Item: 'static,
<Codec as Encoder>::Error: std::fmt::Debug,
{
type Request = Io;
type Response = ();
type Error = ServiceError<C::Error, Codec>;
type Future = FramedServiceImplResponse<St, Io, Codec, C, T>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.connect.poll_ready().map_err(|e| e.into())
}
fn call(&mut self, req: Io) -> Self::Future {
FramedServiceImplResponse {
inner: FramedServiceImplResponseInner::Connect(
self.connect.call(Connect::new(req)),
self.handler.clone(),
),
disconnect: self.disconnect.clone(),
}
}
}
pub struct FramedServiceImplResponse<St, Io, Codec, C, T>
where
C: Service<Request = Connect<Io>, Response = ConnectResult<Io, St, Codec>>,
C::Error: 'static,
T: NewService<
Config = St,
Request = RequestItem<St, Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
>,
<<T as NewService>::Service as Service>::Future: 'static,
Io: AsyncRead + AsyncWrite,
Codec: Encoder + Decoder,
<Codec as Encoder>::Item: 'static,
<Codec as Encoder>::Error: std::fmt::Debug,
{
inner: FramedServiceImplResponseInner<St, Io, Codec, C, T>,
disconnect: Option<Rc<Fn(&mut St, bool)>>,
}
enum FramedServiceImplResponseInner<St, Io, Codec, C, T>
where
C: Service<Request = Connect<Io>, Response = ConnectResult<Io, St, Codec>>,
C::Error: 'static,
T: NewService<
Config = St,
Request = RequestItem<St, Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
>,
<<T as NewService>::Service as Service>::Future: 'static,
Io: AsyncRead + AsyncWrite,
Codec: Encoder + Decoder,
<Codec as Encoder>::Item: 'static,
<Codec as Encoder>::Error: std::fmt::Debug,
{
Connect(C::Future, Rc<T>),
Handler(T::Future, Option<ConnectResult<Io, St, Codec>>),
Dispatcher(FramedDispatcher<St, T::Service, Io, Codec>),
}
impl<St, Io, Codec, C, T> Future for FramedServiceImplResponse<St, Io, Codec, C, T>
where
C: Service<Request = Connect<Io>, Response = ConnectResult<Io, St, Codec>>,
C::Error: 'static,
T: NewService<
Config = St,
Request = RequestItem<St, Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
>,
<<T as NewService>::Service as Service>::Future: 'static,
Io: AsyncRead + AsyncWrite,
Codec: Encoder + Decoder,
<Codec as Encoder>::Item: 'static,
<Codec as Encoder>::Error: std::fmt::Debug,
{
type Item = ();
type Error = ServiceError<C::Error, Codec>;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self.inner {
FramedServiceImplResponseInner::Connect(ref mut fut, ref handler) => {
match fut.poll()? {
Async::Ready(res) => {
self.inner = FramedServiceImplResponseInner::Handler(
handler.new_service(&res.state),
Some(res),
);
self.poll()
}
Async::NotReady => Ok(Async::NotReady),
}
}
FramedServiceImplResponseInner::Handler(ref mut fut, ref mut res) => {
match fut.poll()? {
Async::Ready(handler) => {
let res = res.take().unwrap();
self.inner =
FramedServiceImplResponseInner::Dispatcher(FramedDispatcher::new(
res.framed,
State::new(res.state),
handler,
res.rx,
res.sink,
self.disconnect.clone(),
));
self.poll()
}
Async::NotReady => Ok(Async::NotReady),
}
}
FramedServiceImplResponseInner::Dispatcher(ref mut fut) => fut.poll(),
}
}
}

44
actix-ioframe/src/sink.rs Normal file
View File

@@ -0,0 +1,44 @@
use std::fmt;
use futures::unsync::{mpsc, oneshot};
use futures::Future;
use crate::dispatcher::FramedMessage;
pub struct Sink<T>(mpsc::UnboundedSender<FramedMessage<T>>);
impl<T> Clone for Sink<T> {
fn clone(&self) -> Self {
Sink(self.0.clone())
}
}
impl<T> Sink<T> {
pub(crate) fn new(tx: mpsc::UnboundedSender<FramedMessage<T>>) -> Self {
Sink(tx)
}
/// Close connection
pub fn close(&self) {
let _ = self.0.unbounded_send(FramedMessage::Close);
}
/// Close connection
pub fn wait_close(&self) -> impl Future<Item = (), Error = ()> {
let (tx, rx) = oneshot::channel();
let _ = self.0.unbounded_send(FramedMessage::WaitClose(tx));
rx.map_err(|_| ())
}
/// Send item
pub fn send(&self, item: T) {
let _ = self.0.unbounded_send(FramedMessage::Message(item));
}
}
impl<T> fmt::Debug for Sink<T> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("Sink").finish()
}
}

View File

@@ -0,0 +1,30 @@
use std::cell::{Ref, RefCell, RefMut};
use std::rc::Rc;
/// Connection state
///
/// Connection state is an arbitrary data attached to the each incoming message.
#[derive(Debug)]
pub struct State<T>(Rc<RefCell<T>>);
impl<T> State<T> {
pub(crate) fn new(st: T) -> Self {
State(Rc::new(RefCell::new(st)))
}
#[inline]
pub fn get_ref(&self) -> Ref<T> {
self.0.borrow()
}
#[inline]
pub fn get_mut(&mut self) -> RefMut<T> {
self.0.borrow_mut()
}
}
impl<T> Clone for State<T> {
fn clone(&self) -> Self {
State(self.0.clone())
}
}

View File

@@ -0,0 +1,60 @@
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
use actix_codec::BytesCodec;
use actix_server_config::Io;
use actix_service::{new_apply_fn, Service};
use actix_test_server::TestServer;
use futures::Future;
use tokio_tcp::TcpStream;
use tokio_timer::sleep;
use actix_ioframe::{Builder, Connect};
struct State;
#[test]
fn test_disconnect() -> std::io::Result<()> {
let disconnect = Arc::new(AtomicBool::new(false));
let disconnect1 = disconnect.clone();
let mut srv = TestServer::with(move || {
let disconnect1 = disconnect1.clone();
new_apply_fn(
Builder::new()
.factory(|conn: Connect<_>| Ok(conn.codec(BytesCodec).state(State)))
.disconnect(move |_, _| {
disconnect1.store(true, Ordering::Relaxed);
})
.finish(|_t| Ok(None)),
|io: Io<TcpStream>, srv| srv.call(io.into_parts().0),
)
});
let mut client = Builder::new()
.service(|conn: Connect<_>| {
let conn = conn.codec(BytesCodec).state(State);
conn.sink().close();
Ok(conn)
})
.finish(|_t| Ok(None));
let conn = srv
.block_on(
actix_connect::default_connector()
.call(actix_connect::Connect::with(String::new(), srv.addr())),
)
.unwrap();
srv.block_on(client.call(conn.into_parts().0)).unwrap();
let _ = srv.block_on(
sleep(Duration::from_millis(100))
.map(|_| ())
.map_err(|_| ()),
);
assert!(disconnect.load(Ordering::Relaxed));
Ok(())
}

View File

@@ -0,0 +1,21 @@
[package]
name = "actix-lets-encrypt"
version = "0.1.0"
authors = ["Jordan Deitch <jd@rsa.pub>"]
description = "Actix Let's Encrypt"
keywords = ["network", "framework", "async", "futures"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-lets-encrypt/"
categories = ["network-programming", "asynchronous"]
license = "MIT/Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
edition = "2018"
workspace = ".."
[lib]
name = "actix_lets_encrypt"
path = "src/lib.rs"
[dependencies]
acme-client = {version = "0.5", default-features = false}

View File

View File

@@ -0,0 +1,64 @@
use acme_client::Directory;
struct CertificateError {
message: String,
}
impl std::error::Error for CertificateError {
fn description(&self) -> &str { self.message.as_str() }
fn cause(&self) -> Option<&dyn std::error::Error> { None }
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
}
impl std::fmt::Display for CertificateError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "An Error Occurred, Please Try Again!")
}
}
impl std::fmt::Debug for CertificateError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{{ file: {}, line: {} }}", file!(), line!())
}
}
impl CertificateError {
fn new(message: String) -> Self {
CertificateError { message }
}
}
impl std::convert::From<acme_client::error::Error> for CertificateError {
fn from(e: acme_client::error::Error) -> Self {
return CertificateError::new(e.to_string());
}
}
struct CertificateRequest<'a> {
domain: &'a str,
email: &'a str,
}
impl<'a> CertificateRequest<'a> {
fn new(email: &'a str, domain: &'a str) -> Self {
return CertificateRequest { domain, email };
}
fn sign(self: &Self) -> Result<(), CertificateError> {
let directory = Directory::lets_encrypt()?;
let account = directory.account_registration()
.email(self.email)
.register()?;
let authorization = account.authorization(self.domain)?;
let http_challenge = authorization.get_http_challenge().ok_or("HTTP challenge failed")?;
http_challenge.save_key_authorization("/var/www")?;
http_challenge.validate()?;
let cert = account.certificate_signer(&[self.domain]).sign_certificate()?;
cert.save_signed_certificate("certificate.pem")?;
cert.save_private_key("certificate.key")?;
Ok(())
}
}

View File

@@ -0,0 +1,2 @@
mod certificate_signer;
mod authorization;

View File

@@ -1 +0,0 @@
/wip

View File

@@ -1,9 +0,0 @@
# CHANGES
## 0.1.2 - 2020-05-18
### Changed
* Forward actix_rt::test arguments to test function [#127]
[#127]: https://github.com/actix/actix-net/pull/127

View File

@@ -1,23 +0,0 @@
[package]
name = "actix-macros"
version = "0.1.2"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix runtime macros"
repository = "https://github.com/actix/actix-net"
documentation = "https://docs.rs/actix-macros/"
categories = ["network-programming", "asynchronous"]
license = "MIT OR Apache-2.0"
edition = "2018"
[lib]
proc-macro = true
[dependencies]
quote = "1.0.3"
syn = { version = "^1", features = ["full"] }
[dev-dependencies]
actix-rt = "1.0"
futures-util = { version = "0.3", default-features = false }
trybuild = "1"

View File

@@ -1,102 +0,0 @@
//! Macros for use with Tokio
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
/// Marks async function to be executed by actix system.
///
/// ## Usage
///
/// ```rust
/// #[actix_rt::main]
/// async fn main() {
/// println!("Hello world");
/// }
/// ```
#[allow(clippy::needless_doctest_main)]
#[proc_macro_attribute]
#[cfg(not(test))] // Work around for rust-lang/rust#62127
pub fn main(_: TokenStream, item: TokenStream) -> TokenStream {
let mut input = syn::parse_macro_input!(item as syn::ItemFn);
let attrs = &input.attrs;
let vis = &input.vis;
let sig = &mut input.sig;
let body = &input.block;
let name = &sig.ident;
if sig.asyncness.is_none() {
return syn::Error::new_spanned(sig.fn_token, "only async fn is supported")
.to_compile_error()
.into();
}
sig.asyncness = None;
(quote! {
#(#attrs)*
#vis #sig {
actix_rt::System::new(stringify!(#name))
.block_on(async move { #body })
}
})
.into()
}
/// Marks async test function to be executed by actix runtime.
///
/// ## Usage
///
/// ```no_run
/// #[actix_rt::test]
/// async fn my_test() {
/// assert!(true);
/// }
/// ```
#[proc_macro_attribute]
pub fn test(_: TokenStream, item: TokenStream) -> TokenStream {
let mut input = syn::parse_macro_input!(item as syn::ItemFn);
let attrs = &input.attrs;
let vis = &input.vis;
let sig = &mut input.sig;
let body = &input.block;
let mut has_test_attr = false;
for attr in attrs {
if attr.path.is_ident("test") {
has_test_attr = true;
}
}
if sig.asyncness.is_none() {
return syn::Error::new_spanned(
input.sig.fn_token,
format!("only async fn is supported, {}", input.sig.ident),
)
.to_compile_error()
.into();
}
sig.asyncness = None;
let result = if has_test_attr {
quote! {
#(#attrs)*
#vis #sig {
actix_rt::System::new("test")
.block_on(async { #body })
}
}
} else {
quote! {
#[test]
#(#attrs)*
#vis #sig {
actix_rt::System::new("test")
.block_on(async { #body })
}
}
};
result.into()
}

View File

@@ -1,9 +0,0 @@
#[test]
fn compile_macros() {
let t = trybuild::TestCases::new();
t.pass("tests/trybuild/main-01-basic.rs");
t.compile_fail("tests/trybuild/main-02-only-async.rs");
t.pass("tests/trybuild/test-01-basic.rs");
t.pass("tests/trybuild/test-02-keep-attrs.rs");
}

View File

@@ -1,4 +0,0 @@
#[actix_rt::main]
async fn main() {
println!("Hello world");
}

View File

@@ -1,4 +0,0 @@
#[actix_rt::main]
fn main() {
futures_util::future::ready(()).await
}

View File

@@ -1,14 +0,0 @@
error: only async fn is supported
--> $DIR/main-02-only-async.rs:2:1
|
2 | fn main() {
| ^^
error[E0601]: `main` function not found in crate `$CRATE`
--> $DIR/main-02-only-async.rs:1:1
|
1 | / #[actix_rt::main]
2 | | fn main() {
3 | | futures_util::future::ready(()).await
4 | | }
| |_^ consider adding a `main` function to `$DIR/tests/trybuild/main-02-only-async.rs`

View File

@@ -1,6 +0,0 @@
#[actix_rt::test]
async fn my_test() {
assert!(true);
}
fn main() {}

View File

@@ -1,7 +0,0 @@
#[actix_rt::test]
#[should_panic]
async fn my_test() {
todo!()
}
fn main() {}

View File

@@ -1,79 +1,5 @@
# Changes # Changes
## [1.1.1] - 2020-04-30
### Fixed
* Fix memory leak due to [#94] (see [#129] for more detail)
[#129]: https://github.com/actix/actix-net/issues/129
## [1.1.0] - 2020-04-08
**This version has been yanked.**
### Added
* Expose `System::is_set` to check if current system has ben started [#99]
* Add `Arbiter::is_running` to check if event loop is running [#124]
* Add `Arbiter::local_join` associated function
to get be able to `await` for spawned futures [#94]
[#94]: https://github.com/actix/actix-net/pull/94
[#99]: https://github.com/actix/actix-net/pull/99
[#124]: https://github.com/actix/actix-net/pull/124
## [1.0.0] - 2019-12-11
* Update dependencies
## [1.0.0-alpha.3] - 2019-12-07
### Fixed
* Fix compilation on non-unix platforms
### Changed
* Migrate to tokio 0.2
## [1.0.0-alpha.2] - 2019-12-02
Added
* Export `main` and `test` attribute macros
* Export `time` module (re-export of tokio-timer)
* Export `net` module (re-export of tokio-net)
## [1.0.0-alpha.1] - 2019-11-22
### Changed
* Migrate to std::future and tokio 0.2
## [0.2.6] - 2019-11-14
### Fixed
* Fix arbiter's thread panic message.
### Added
* Allow to join arbiter's thread. #60
## [0.2.5] - 2019-09-02
### Added
* Add arbiter specific storage
## [0.2.4] - 2019-07-17 ## [0.2.4] - 2019-07-17
### Changed ### Changed

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-rt" name = "actix-rt"
version = "1.1.1" version = "0.2.4"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix runtime" description = "Actix runtime"
keywords = ["network", "framework", "async", "futures"] keywords = ["network", "framework", "async", "futures"]
@@ -8,18 +8,20 @@ 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-rt/" documentation = "https://docs.rs/actix-rt/"
categories = ["network-programming", "asynchronous"] categories = ["network-programming", "asynchronous"]
license = "MIT OR Apache-2.0" license = "MIT/Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
edition = "2018" edition = "2018"
workspace = ".."
[lib] [lib]
name = "actix_rt" name = "actix_rt"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
actix-macros = "0.1.0" actix-threadpool = "0.1.1"
actix-threadpool = "0.3" futures = "0.1.25"
futures-channel = { version = "0.3.4", default-features = false } tokio-current-thread = "0.1"
futures-util = { version = "0.3.4", default-features = false, features = ["alloc"] } tokio-executor = "0.1.5"
tokio-reactor = "0.1.7"
tokio-timer = "0.2.8"
copyless = "0.1.4" copyless = "0.1.4"
smallvec = "1"
tokio = { version = "0.2.6", default-features = false, features = ["rt-core", "rt-util", "io-driver", "tcp", "uds", "udp", "time", "signal", "stream"] }

View File

@@ -1,44 +1,34 @@
use std::any::{Any, TypeId};
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::collections::HashMap; use std::collections::HashMap;
use std::pin::Pin;
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use std::task::{Context, Poll};
use std::{fmt, thread}; use std::{fmt, thread};
use futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}; use futures::sync::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
use futures_channel::oneshot::{channel, Canceled, Sender}; use futures::sync::oneshot::{channel, Canceled, Sender};
use futures_util::{ use futures::{future, Async, Future, IntoFuture, Poll, Stream};
future::{self, Future, FutureExt}, use tokio_current_thread::spawn;
stream::Stream,
};
use crate::runtime::Runtime; use crate::builder::Builder;
use crate::system::System; use crate::system::System;
use copyless::BoxHelper; use copyless::BoxHelper;
use smallvec::SmallVec;
pub use tokio::task::JoinHandle;
thread_local!( thread_local!(
static ADDR: RefCell<Option<Arbiter>> = RefCell::new(None); static ADDR: RefCell<Option<Arbiter>> = RefCell::new(None);
static RUNNING: Cell<bool> = Cell::new(false); static RUNNING: Cell<bool> = Cell::new(false);
static Q: RefCell<Vec<Pin<Box<dyn Future<Output = ()>>>>> = RefCell::new(Vec::new()); static Q: RefCell<Vec<Box<Future<Item = (), Error = ()>>>> = RefCell::new(Vec::new());
static PENDING: RefCell<SmallVec<[JoinHandle<()>; 8]>> = RefCell::new(SmallVec::new());
static STORAGE: RefCell<HashMap<TypeId, Box<dyn Any>>> = RefCell::new(HashMap::new());
); );
pub(crate) static COUNT: AtomicUsize = AtomicUsize::new(0); pub(crate) static COUNT: AtomicUsize = AtomicUsize::new(0);
pub(crate) enum ArbiterCommand { pub(crate) enum ArbiterCommand {
Stop, Stop,
Execute(Box<dyn Future<Output = ()> + Unpin + Send>), Execute(Box<Future<Item = (), Error = ()> + Send>),
ExecuteFn(Box<dyn FnExec>), ExecuteFn(Box<FnExec>),
} }
impl fmt::Debug for ArbiterCommand { impl fmt::Debug for ArbiterCommand {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
ArbiterCommand::Stop => write!(f, "ArbiterCommand::Stop"), ArbiterCommand::Stop => write!(f, "ArbiterCommand::Stop"),
ArbiterCommand::Execute(_) => write!(f, "ArbiterCommand::Execute"), ArbiterCommand::Execute(_) => write!(f, "ArbiterCommand::Execute"),
@@ -47,20 +37,11 @@ impl fmt::Debug for ArbiterCommand {
} }
} }
#[derive(Debug)] #[derive(Debug, Clone)]
/// Arbiters provide an asynchronous execution environment for actors, functions /// Arbiters provide an asynchronous execution environment for actors, functions
/// and futures. When an Arbiter is created, it spawns a new OS thread, and /// and futures. When an Arbiter is created, they spawn a new OS thread, and
/// hosts an event loop. Some Arbiter functions execute on the current thread. /// host an event loop. Some Arbiter functions execute on the current thread.
pub struct Arbiter { pub struct Arbiter(UnboundedSender<ArbiterCommand>);
sender: UnboundedSender<ArbiterCommand>,
thread_handle: Option<thread::JoinHandle<()>>,
}
impl Clone for Arbiter {
fn clone(&self) -> Self {
Self::with_sender(self.sender.clone())
}
}
impl Default for Arbiter { impl Default for Arbiter {
fn default() -> Self { fn default() -> Self {
@@ -72,10 +53,9 @@ impl Arbiter {
pub(crate) fn new_system() -> Self { pub(crate) fn new_system() -> Self {
let (tx, rx) = unbounded(); let (tx, rx) = unbounded();
let arb = Arbiter::with_sender(tx); let arb = Arbiter(tx);
ADDR.with(|cell| *cell.borrow_mut() = Some(arb.clone())); ADDR.with(|cell| *cell.borrow_mut() = Some(arb.clone()));
RUNNING.with(|cell| cell.set(false)); RUNNING.with(|cell| cell.set(false));
STORAGE.with(|cell| cell.borrow_mut().clear());
Arbiter::spawn(ArbiterController { stop: None, rx }); Arbiter::spawn(ArbiterController { stop: None, rx });
arb arb
@@ -90,14 +70,9 @@ impl Arbiter {
}) })
} }
/// Check if current arbiter is running.
pub fn is_running() -> bool {
RUNNING.with(|cell| cell.get())
}
/// Stop arbiter from continuing it's event loop. /// Stop arbiter from continuing it's event loop.
pub fn stop(&self) { pub fn stop(&self) {
let _ = self.sender.unbounded_send(ArbiterCommand::Stop); let _ = self.0.unbounded_send(ArbiterCommand::Stop);
} }
/// Spawn new thread and run event loop in spawned thread. /// Spawn new thread and run event loop in spawned thread.
@@ -109,61 +84,48 @@ impl Arbiter {
let (arb_tx, arb_rx) = unbounded(); let (arb_tx, arb_rx) = unbounded();
let arb_tx2 = arb_tx.clone(); let arb_tx2 = arb_tx.clone();
let handle = thread::Builder::new() let _ = thread::Builder::new().name(name.clone()).spawn(move || {
.name(name.clone()) let mut rt = Builder::new().build_rt().expect("Can not create Runtime");
.spawn(move || { let arb = Arbiter(arb_tx);
let mut rt = Runtime::new().expect("Can not create Runtime");
let arb = Arbiter::with_sender(arb_tx);
let (stop, stop_rx) = channel(); let (stop, stop_rx) = channel();
RUNNING.with(|cell| cell.set(true)); RUNNING.with(|cell| cell.set(true));
STORAGE.with(|cell| cell.borrow_mut().clear());
System::set_current(sys); System::set_current(sys);
// start arbiter controller // start arbiter controller
rt.spawn(ArbiterController { rt.spawn(ArbiterController {
stop: Some(stop), stop: Some(stop),
rx: arb_rx, rx: arb_rx,
});
ADDR.with(|cell| *cell.borrow_mut() = Some(arb.clone()));
// register arbiter
let _ = System::current()
.sys()
.unbounded_send(SystemCommand::RegisterArbiter(id, arb));
// run loop
let _ = match rt.block_on(stop_rx) {
Ok(code) => code,
Err(_) => 1,
};
// unregister arbiter
let _ = System::current()
.sys()
.unbounded_send(SystemCommand::UnregisterArbiter(id));
})
.unwrap_or_else(|err| {
panic!("Cannot spawn an arbiter's thread {:?}: {:?}", &name, err)
}); });
ADDR.with(|cell| *cell.borrow_mut() = Some(arb.clone()));
Arbiter { // register arbiter
sender: arb_tx2, let _ = System::current()
thread_handle: Some(handle), .sys()
} .unbounded_send(SystemCommand::RegisterArbiter(id, arb.clone()));
// run loop
let _ = match rt.block_on(stop_rx) {
Ok(code) => code,
Err(_) => 1,
};
// unregister arbiter
let _ = System::current()
.sys()
.unbounded_send(SystemCommand::UnregisterArbiter(id));
});
Arbiter(arb_tx2)
} }
pub(crate) fn run_system(rt: Option<&Runtime>) { pub(crate) fn run_system() {
RUNNING.with(|cell| cell.set(true)); RUNNING.with(|cell| cell.set(true));
Q.with(|cell| { Q.with(|cell| {
let mut v = cell.borrow_mut(); let mut v = cell.borrow_mut();
for fut in v.drain(..) { for fut in v.drain(..) {
if let Some(rt) = rt { spawn(fut);
rt.spawn(fut);
} else {
tokio::task::spawn_local(fut);
}
} }
}); });
} }
@@ -177,26 +139,13 @@ impl Arbiter {
/// thread. /// thread.
pub fn spawn<F>(future: F) pub fn spawn<F>(future: F)
where where
F: Future<Output = ()> + 'static, F: Future<Item = (), Error = ()> + 'static,
{ {
RUNNING.with(move |cell| { RUNNING.with(move |cell| {
if cell.get() { if cell.get() {
// Spawn the future on running executor spawn(Box::alloc().init(future));
let len = PENDING.with(move |cell| {
let mut p = cell.borrow_mut();
p.push(tokio::task::spawn_local(future));
p.len()
});
if len > 7 {
// Before reaching the inline size
tokio::task::spawn_local(CleanupPending);
}
} else { } else {
// Box the future and push it to the queue, this results in double boxing Q.with(move |cell| cell.borrow_mut().push(Box::alloc().init(future)));
// because the executor boxes the future again, but works for now
Q.with(move |cell| {
cell.borrow_mut().push(Pin::from(Box::alloc().init(future)))
});
} }
}); });
} }
@@ -207,18 +156,18 @@ impl Arbiter {
pub fn spawn_fn<F, R>(f: F) pub fn spawn_fn<F, R>(f: F)
where where
F: FnOnce() -> R + 'static, F: FnOnce() -> R + 'static,
R: Future<Output = ()> + 'static, R: IntoFuture<Item = (), Error = ()> + 'static,
{ {
Arbiter::spawn(future::lazy(|_| f()).flatten()) Arbiter::spawn(future::lazy(f))
} }
/// Send a future to the Arbiter's thread, and spawn it. /// Send a future to the Arbiter's thread, and spawn it.
pub fn send<F>(&self, future: F) pub fn send<F>(&self, future: F)
where where
F: Future<Output = ()> + Send + Unpin + 'static, F: Future<Item = (), Error = ()> + Send + 'static,
{ {
let _ = self let _ = self
.sender .0
.unbounded_send(ArbiterCommand::Execute(Box::new(future))); .unbounded_send(ArbiterCommand::Execute(Box::new(future)));
} }
@@ -229,23 +178,23 @@ impl Arbiter {
F: FnOnce() + Send + 'static, F: FnOnce() + Send + 'static,
{ {
let _ = self let _ = self
.sender .0
.unbounded_send(ArbiterCommand::ExecuteFn(Box::new(move || { .unbounded_send(ArbiterCommand::ExecuteFn(Box::new(move || {
f(); let _ = f();
}))); })));
} }
/// Send a function to the Arbiter's thread. This function will be executed asynchronously. /// Send a function to the Arbiter's thread. This function will be executed asynchronously.
/// A future is created, and when resolved will contain the result of the function sent /// A future is created, and when resolved will contain the result of the function sent
/// to the Arbiters thread. /// to the Arbiters thread.
pub fn exec<F, R>(&self, f: F) -> impl Future<Output = Result<R, Canceled>> pub fn exec<F, R>(&self, f: F) -> impl Future<Item = R, Error = Canceled>
where where
F: FnOnce() -> R + Send + 'static, F: FnOnce() -> R + Send + 'static,
R: Send + 'static, R: Send + 'static,
{ {
let (tx, rx) = channel(); let (tx, rx) = channel();
let _ = self let _ = self
.sender .0
.unbounded_send(ArbiterCommand::ExecuteFn(Box::new(move || { .unbounded_send(ArbiterCommand::ExecuteFn(Box::new(move || {
if !tx.is_canceled() { if !tx.is_canceled() {
let _ = tx.send(f()); let _ = tx.send(f());
@@ -253,99 +202,6 @@ impl Arbiter {
}))); })));
rx rx
} }
/// Set item to arbiter storage
pub fn set_item<T: 'static>(item: T) {
STORAGE.with(move |cell| cell.borrow_mut().insert(TypeId::of::<T>(), Box::new(item)));
}
/// Check if arbiter storage contains item
pub fn contains_item<T: 'static>() -> bool {
STORAGE.with(move |cell| cell.borrow().get(&TypeId::of::<T>()).is_some())
}
/// Get a reference to a type previously inserted on this arbiter's storage.
///
/// Panics is item is not inserted
pub fn get_item<T: 'static, F, R>(mut f: F) -> R
where
F: FnMut(&T) -> R,
{
STORAGE.with(move |cell| {
let st = cell.borrow();
let item = st
.get(&TypeId::of::<T>())
.and_then(|boxed| (&**boxed as &(dyn Any + 'static)).downcast_ref())
.unwrap();
f(item)
})
}
/// Get a mutable reference to a type previously inserted on this arbiter's storage.
///
/// Panics is item is not inserted
pub fn get_mut_item<T: 'static, F, R>(mut f: F) -> R
where
F: FnMut(&mut T) -> R,
{
STORAGE.with(move |cell| {
let mut st = cell.borrow_mut();
let item = st
.get_mut(&TypeId::of::<T>())
.and_then(|boxed| (&mut **boxed as &mut (dyn Any + 'static)).downcast_mut())
.unwrap();
f(item)
})
}
fn with_sender(sender: UnboundedSender<ArbiterCommand>) -> Self {
Self {
sender,
thread_handle: None,
}
}
/// Wait for the event loop to stop by joining the underlying thread (if have Some).
pub fn join(&mut self) -> thread::Result<()> {
if let Some(thread_handle) = self.thread_handle.take() {
thread_handle.join()
} else {
Ok(())
}
}
/// Returns a future that will be completed once all currently spawned futures
/// have completed.
pub fn local_join() -> impl Future<Output = ()> {
PENDING.with(move |cell| {
let current = cell.replace(SmallVec::new());
future::join_all(current).map(|_| ())
})
}
}
/// Future used for cleaning-up already finished `JoinHandle`s
/// from the `PENDING` list so the vector doesn't grow indefinitely
struct CleanupPending;
impl Future for CleanupPending {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
PENDING.with(move |cell| {
let mut pending = cell.borrow_mut();
let mut i = 0;
while i != pending.len() {
if let Poll::Ready(_) = Pin::new(&mut pending[i]).poll(cx) {
pending.remove(i);
} else {
i += 1;
}
}
});
Poll::Ready(())
}
} }
struct ArbiterController { struct ArbiterController {
@@ -356,46 +212,37 @@ struct ArbiterController {
impl Drop for ArbiterController { impl Drop for ArbiterController {
fn drop(&mut self) { fn drop(&mut self) {
if thread::panicking() { if thread::panicking() {
eprintln!("Panic in Arbiter thread, shutting down system.");
if System::current().stop_on_panic() { if System::current().stop_on_panic() {
eprintln!("Panic in Arbiter thread, shutting down system.");
System::current().stop_with_code(1) System::current().stop_with_code(1)
} else {
eprintln!("Panic in Arbiter thread.");
} }
} }
} }
} }
impl Future for ArbiterController { impl Future for ArbiterController {
type Output = (); type Item = ();
type Error = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
loop { loop {
match Pin::new(&mut self.rx).poll_next(cx) { match self.rx.poll() {
Poll::Ready(None) => return Poll::Ready(()), Ok(Async::Ready(None)) | Err(_) => return Ok(Async::Ready(())),
Poll::Ready(Some(item)) => match item { Ok(Async::Ready(Some(item))) => match item {
ArbiterCommand::Stop => { ArbiterCommand::Stop => {
if let Some(stop) = self.stop.take() { if let Some(stop) = self.stop.take() {
let _ = stop.send(0); let _ = stop.send(0);
}; };
return Poll::Ready(()); return Ok(Async::Ready(()));
} }
ArbiterCommand::Execute(fut) => { ArbiterCommand::Execute(fut) => {
let len = PENDING.with(move |cell| { spawn(fut);
let mut p = cell.borrow_mut();
p.push(tokio::task::spawn_local(fut));
p.len()
});
if len > 7 {
// Before reaching the inline size
tokio::task::spawn_local(CleanupPending);
}
} }
ArbiterCommand::ExecuteFn(f) => { ArbiterCommand::ExecuteFn(f) => {
f.call_box(); f.call_box();
} }
}, },
Poll::Pending => return Poll::Pending, Ok(Async::NotReady) => return Ok(Async::NotReady),
} }
} }
} }
@@ -426,13 +273,14 @@ impl SystemArbiter {
} }
impl Future for SystemArbiter { impl Future for SystemArbiter {
type Output = (); type Item = ();
type Error = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
loop { loop {
match Pin::new(&mut self.commands).poll_next(cx) { match self.commands.poll() {
Poll::Ready(None) => return Poll::Ready(()), Ok(Async::Ready(None)) | Err(_) => return Ok(Async::Ready(())),
Poll::Ready(Some(cmd)) => match cmd { Ok(Async::Ready(Some(cmd))) => match cmd {
SystemCommand::Exit(code) => { SystemCommand::Exit(code) => {
// stop arbiters // stop arbiters
for arb in self.arbiters.values() { for arb in self.arbiters.values() {
@@ -450,7 +298,7 @@ impl Future for SystemArbiter {
self.arbiters.remove(&name); self.arbiters.remove(&name);
} }
}, },
Poll::Pending => return Poll::Pending, Ok(Async::NotReady) => return Ok(Async::NotReady),
} }
} }
} }
@@ -464,7 +312,7 @@ impl<F> FnExec for F
where where
F: FnOnce() + Send + 'static, F: FnOnce() + Send + 'static,
{ {
#[allow(clippy::boxed_local)] #[cfg_attr(feature = "cargo-clippy", allow(boxed_local))]
fn call_box(self: Box<Self>) { fn call_box(self: Box<Self>) {
(*self)() (*self)()
} }

View File

@@ -1,10 +1,15 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::io; use std::io;
use futures_channel::mpsc::unbounded; use futures::future;
use futures_channel::oneshot::{channel, Receiver}; use futures::future::{lazy, Future};
use futures_util::future::{lazy, Future, FutureExt}; use futures::sync::mpsc::unbounded;
use tokio::task::LocalSet; use futures::sync::oneshot::{channel, Receiver};
use tokio_current_thread::{CurrentThread, Handle};
use tokio_reactor::Reactor;
use tokio_timer::clock::Clock;
use tokio_timer::timer::Timer;
use crate::arbiter::{Arbiter, SystemArbiter}; use crate::arbiter::{Arbiter, SystemArbiter};
use crate::runtime::Runtime; use crate::runtime::Runtime;
@@ -19,6 +24,9 @@ pub struct Builder {
/// Name of the System. Defaults to "actix" if unset. /// Name of the System. Defaults to "actix" if unset.
name: Cow<'static, str>, name: Cow<'static, str>,
/// The clock to use
clock: Clock,
/// Whether the Arbiter will stop the whole System on uncaught panic. Defaults to false. /// Whether the Arbiter will stop the whole System on uncaught panic. Defaults to false.
stop_on_panic: bool, stop_on_panic: bool,
} }
@@ -27,6 +35,7 @@ impl Builder {
pub(crate) fn new() -> Self { pub(crate) fn new() -> Self {
Builder { Builder {
name: Cow::Borrowed("actix"), name: Cow::Borrowed("actix"),
clock: Clock::new(),
stop_on_panic: false, stop_on_panic: false,
} }
} }
@@ -37,6 +46,14 @@ impl Builder {
self self
} }
/// Set the Clock instance that will be used by this System.
///
/// Defaults to the system clock.
pub fn clock(mut self, clock: Clock) -> Self {
self.clock = clock;
self
}
/// Sets the option 'stop_on_panic' which controls whether the System is stopped when an /// Sets the option 'stop_on_panic' which controls whether the System is stopped when an
/// uncaught panic is thrown from a worker thread. /// uncaught panic is thrown from a worker thread.
/// ///
@@ -56,8 +73,8 @@ impl Builder {
/// Create new System that can run asynchronously. /// Create new System that can run asynchronously.
/// ///
/// This method panics if it cannot start the system arbiter /// This method panics if it cannot start the system arbiter
pub(crate) fn build_async(self, local: &LocalSet) -> AsyncSystemRunner { pub(crate) fn build_async(self, executor: Handle) -> AsyncSystemRunner {
self.create_async_runtime(local) self.create_async_runtime(executor)
} }
/// This function will start tokio runtime and will finish once the /// This function will start tokio runtime and will finish once the
@@ -70,7 +87,7 @@ impl Builder {
self.create_runtime(f).run() self.create_runtime(f).run()
} }
fn create_async_runtime(self, local: &LocalSet) -> AsyncSystemRunner { fn create_async_runtime(self, executor: Handle) -> AsyncSystemRunner {
let (stop_tx, stop) = channel(); let (stop_tx, stop) = channel();
let (sys_sender, sys_receiver) = unbounded(); let (sys_sender, sys_receiver) = unbounded();
@@ -80,7 +97,7 @@ impl Builder {
let arb = SystemArbiter::new(stop_tx, sys_receiver); let arb = SystemArbiter::new(stop_tx, sys_receiver);
// start the system arbiter // start the system arbiter
let _ = local.spawn_local(arb); executor.spawn(arb).expect("could not start system arbiter");
AsyncSystemRunner { stop, system } AsyncSystemRunner { stop, system }
} }
@@ -97,14 +114,40 @@ impl Builder {
// system arbiter // system arbiter
let arb = SystemArbiter::new(stop_tx, sys_receiver); let arb = SystemArbiter::new(stop_tx, sys_receiver);
let mut rt = Runtime::new().unwrap(); let mut rt = self.build_rt().unwrap();
rt.spawn(arb); rt.spawn(arb);
// init system arbiter and run configuration method // init system arbiter and run configuration method
rt.block_on(lazy(move |_| f())); let _ = rt.block_on(lazy(move || {
f();
Ok::<_, ()>(())
}));
SystemRunner { rt, stop, system } SystemRunner { rt, stop, system }
} }
pub(crate) fn build_rt(&self) -> io::Result<Runtime> {
// We need a reactor to receive events about IO objects from kernel
let reactor = Reactor::new()?;
let reactor_handle = reactor.handle();
// Place a timer wheel on top of the reactor. If there are no timeouts to fire, it'll let the
// reactor pick up some new external events.
let timer = Timer::new_with_now(reactor, self.clock.clone());
let timer_handle = timer.handle();
// And now put a single-threaded executor on top of the timer. When there are no futures ready
// to do something, it'll let the timer or the reactor to generate some new stimuli for the
// futures to continue in their life.
let executor = CurrentThread::new_with_park(timer);
Ok(Runtime::new2(
reactor_handle,
timer_handle,
self.clock.clone(),
executor,
))
}
} }
#[derive(Debug)] #[derive(Debug)]
@@ -116,31 +159,30 @@ pub(crate) struct AsyncSystemRunner {
impl AsyncSystemRunner { impl AsyncSystemRunner {
/// This function will start event loop and returns a future that /// This function will start event loop and returns a future that
/// resolves once the `System::stop()` function is called. /// resolves once the `System::stop()` function is called.
pub(crate) fn run_nonblocking(self) -> impl Future<Output = Result<(), io::Error>> + Send { pub(crate) fn run_nonblocking(self) -> impl Future<Item = (), Error = io::Error> + Send {
let AsyncSystemRunner { stop, .. } = self; let AsyncSystemRunner { stop, .. } = self;
// run loop // run loop
lazy(|_| { future::lazy(|| {
Arbiter::run_system(None); Arbiter::run_system();
async { stop.then(|res| match res {
let res = match stop.await { Ok(code) => {
Ok(code) => { if code != 0 {
if code != 0 { Err(io::Error::new(
Err(io::Error::new( io::ErrorKind::Other,
io::ErrorKind::Other, format!("Non-zero exit code: {}", code),
format!("Non-zero exit code: {}", code), ))
)) } else {
} else { Ok(())
Ok(())
}
} }
Err(e) => Err(io::Error::new(io::ErrorKind::Other, e)), }
}; Err(e) => Err(io::Error::new(io::ErrorKind::Other, e)),
})
.then(|result| {
Arbiter::stop_system(); Arbiter::stop_system();
return res; result
} })
}) })
.flatten()
} }
} }
@@ -160,7 +202,10 @@ impl SystemRunner {
let SystemRunner { mut rt, stop, .. } = self; let SystemRunner { mut rt, stop, .. } = self;
// run loop // run loop
Arbiter::run_system(Some(&rt)); let _ = rt.block_on(lazy(move || {
Arbiter::run_system();
Ok::<_, ()>(())
}));
let result = match rt.block_on(stop) { let result = match rt.block_on(stop) {
Ok(code) => { Ok(code) => {
if code != 0 { if code != 0 {
@@ -179,13 +224,19 @@ impl SystemRunner {
} }
/// Execute a future and wait for result. /// Execute a future and wait for result.
pub fn block_on<F, O>(&mut self, fut: F) -> O pub fn block_on<F, I, E>(&mut self, fut: F) -> Result<I, E>
where where
F: Future<Output = O> + 'static, F: Future<Item = I, Error = E>,
{ {
Arbiter::run_system(Some(&self.rt)); let _ = self.rt.block_on(lazy(move || {
Arbiter::run_system();
Ok::<_, ()>(())
}));
let res = self.rt.block_on(fut); let res = self.rt.block_on(fut);
Arbiter::stop_system(); let _ = self.rt.block_on(lazy(move || {
Arbiter::stop_system();
Ok::<_, ()>(())
}));
res res
} }
} }

View File

@@ -1,9 +1,4 @@
//! A runtime implementation that runs everything on the current thread. //! A runtime implementation that runs everything on the current thread.
#![deny(rust_2018_idioms, warnings)]
#![allow(clippy::type_complexity)]
#[cfg(not(test))] // Work around for rust-lang/rust#62127
pub use actix_macros::{main, test};
mod arbiter; mod arbiter;
mod builder; mod builder;
@@ -25,7 +20,7 @@ pub use actix_threadpool as blocking;
/// This function panics if actix system is not running. /// This function panics if actix system is not running.
pub fn spawn<F>(f: F) pub fn spawn<F>(f: F)
where where
F: futures_util::future::Future<Output = ()> + 'static, F: futures::Future<Item = (), Error = ()> + 'static,
{ {
if !System::is_set() { if !System::is_set() {
panic!("System is not running"); panic!("System is not running");
@@ -33,34 +28,3 @@ where
Arbiter::spawn(f); Arbiter::spawn(f);
} }
/// Asynchronous signal handling
pub mod signal {
#[cfg(unix)]
pub mod unix {
pub use tokio::signal::unix::*;
}
pub use tokio::signal::ctrl_c;
}
/// TCP/UDP/Unix bindings
pub mod net {
pub use tokio::net::UdpSocket;
pub use tokio::net::{TcpListener, TcpStream};
#[cfg(unix)]
mod unix {
pub use tokio::net::{UnixDatagram, UnixListener, UnixStream};
}
#[cfg(unix)]
pub use self::unix::*;
}
/// Utilities for tracking time.
pub mod time {
pub use tokio::time::Instant;
pub use tokio::time::{delay_for, delay_until, Delay};
pub use tokio::time::{interval, interval_at, Interval};
pub use tokio::time::{timeout, Timeout};
}

92
actix-rt/src/mod.rs Normal file
View File

@@ -0,0 +1,92 @@
//! A runtime implementation that runs everything on the current thread.
//!
//! [`current_thread::Runtime`][rt] is similar to the primary
//! [`Runtime`][concurrent-rt] except that it runs all components on the current
//! thread instead of using a thread pool. This means that it is able to spawn
//! futures that do not implement `Send`.
//!
//! Same as the default [`Runtime`][concurrent-rt], the
//! [`current_thread::Runtime`][rt] includes:
//!
//! * A [reactor] to drive I/O resources.
//! * An [executor] to execute tasks that use these I/O resources.
//! * A [timer] for scheduling work to run after a set period of time.
//!
//! Note that [`current_thread::Runtime`][rt] does not implement `Send` itself
//! and cannot be safely moved to other threads.
//!
//! # Spawning from other threads
//!
//! While [`current_thread::Runtime`][rt] does not implement `Send` and cannot
//! safely be moved to other threads, it provides a `Handle` that can be sent
//! to other threads and allows to spawn new tasks from there.
//!
//! For example:
//!
//! ```
//! # extern crate tokio;
//! # extern crate futures;
//! use tokio::runtime::current_thread::Runtime;
//! use tokio::prelude::*;
//! use std::thread;
//!
//! # fn main() {
//! let mut runtime = Runtime::new().unwrap();
//! let handle = runtime.handle();
//!
//! thread::spawn(move || {
//! handle.spawn(future::ok(()));
//! }).join().unwrap();
//!
//! # /*
//! runtime.run().unwrap();
//! # */
//! # }
//! ```
//!
//! # Examples
//!
//! Creating a new `Runtime` and running a future `f` until its completion and
//! returning its result.
//!
//! ```
//! use tokio::runtime::current_thread::Runtime;
//! use tokio::prelude::*;
//!
//! let mut runtime = Runtime::new().unwrap();
//!
//! // Use the runtime...
//! // runtime.block_on(f); // where f is a future
//! ```
//!
//! [rt]: struct.Runtime.html
//! [concurrent-rt]: ../struct.Runtime.html
//! [chan]: https://docs.rs/futures/0.1/futures/sync/mpsc/fn.channel.html
//! [reactor]: ../../reactor/struct.Reactor.html
//! [executor]: https://tokio.rs/docs/getting-started/runtime-model/#executors
//! [timer]: ../../timer/index.html
mod builder;
mod runtime;
pub use self::builder::Builder;
pub use self::runtime::{Runtime, Handle};
pub use tokio_current_thread::spawn;
pub use tokio_current_thread::TaskExecutor;
use futures::Future;
/// Run the provided future to completion using a runtime running on the current thread.
///
/// This first creates a new [`Runtime`], and calls [`Runtime::block_on`] with the provided future,
/// which blocks the current thread until the provided future completes. It then calls
/// [`Runtime::run`] to wait for any other spawned futures to resolve.
pub fn block_on_all<F>(future: F) -> Result<F::Item, F::Error>
where
F: Future,
{
let mut r = Runtime::new().expect("failed to start runtime on current thread");
let v = r.block_on(future)?;
r.run().expect("failed to resolve remaining futures");
Ok(v)
}

View File

@@ -1,36 +1,72 @@
use std::future::Future; use std::error::Error;
use std::io; use std::{fmt, io};
use tokio::{runtime, task::LocalSet};
use futures::Future;
use tokio_current_thread::{self as current_thread, CurrentThread};
use tokio_executor;
use tokio_reactor::{self, Reactor};
use tokio_timer::clock::{self, Clock};
use tokio_timer::timer::{self, Timer};
use crate::builder::Builder;
/// Single-threaded runtime provides a way to start reactor /// Single-threaded runtime provides a way to start reactor
/// and runtime on the current thread. /// and executor on the current thread.
/// ///
/// See [module level][mod] documentation for more details. /// See [module level][mod] documentation for more details.
/// ///
/// [mod]: index.html /// [mod]: index.html
#[derive(Debug)] #[derive(Debug)]
pub struct Runtime { pub struct Runtime {
local: LocalSet, reactor_handle: tokio_reactor::Handle,
rt: runtime::Runtime, timer_handle: timer::Handle,
clock: Clock,
executor: CurrentThread<Timer<Reactor>>,
}
/// Error returned by the `run` function.
#[derive(Debug)]
pub struct RunError {
inner: current_thread::RunError,
}
impl fmt::Display for RunError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}", self.inner)
}
}
impl Error for RunError {
fn description(&self) -> &str {
self.inner.description()
}
fn cause(&self) -> Option<&Error> {
self.inner.source()
}
} }
impl Runtime { impl Runtime {
#[allow(clippy::new_ret_no_self)] #[allow(clippy::new_ret_no_self)]
/// Returns a new runtime initialized with default configuration values. /// Returns a new runtime initialized with default configuration values.
pub fn new() -> io::Result<Runtime> { pub fn new() -> io::Result<Runtime> {
let rt = runtime::Builder::new() Builder::new().build_rt()
.enable_io()
.enable_time()
.basic_scheduler()
.build()?;
Ok(Runtime {
rt,
local: LocalSet::new(),
})
} }
/// Spawn a future onto the single-threaded runtime. pub(super) fn new2(
reactor_handle: tokio_reactor::Handle,
timer_handle: timer::Handle,
clock: Clock,
executor: CurrentThread<Timer<Reactor>>,
) -> Runtime {
Runtime {
reactor_handle,
timer_handle,
clock,
executor,
}
}
/// Spawn a future onto the single-threaded Tokio runtime.
/// ///
/// See [module level][mod] documentation for more details. /// See [module level][mod] documentation for more details.
/// ///
@@ -38,7 +74,7 @@ impl Runtime {
/// ///
/// # Examples /// # Examples
/// ///
/// ```rust,ignore /// ```rust
/// # use futures::{future, Future, Stream}; /// # use futures::{future, Future, Stream};
/// use actix_rt::Runtime; /// use actix_rt::Runtime;
/// ///
@@ -47,8 +83,9 @@ impl Runtime {
/// let mut rt = Runtime::new().unwrap(); /// let mut rt = Runtime::new().unwrap();
/// ///
/// // Spawn a future onto the runtime /// // Spawn a future onto the runtime
/// rt.spawn(future::lazy(|_| { /// rt.spawn(future::lazy(|| {
/// println!("running on the runtime"); /// println!("running on the runtime");
/// Ok(())
/// })); /// }));
/// # } /// # }
/// # pub fn main() {} /// # pub fn main() {}
@@ -58,11 +95,11 @@ impl Runtime {
/// ///
/// This function panics if the spawn fails. Failure occurs if the executor /// This function panics if the spawn fails. Failure occurs if the executor
/// is currently at capacity and is unable to spawn a new future. /// is currently at capacity and is unable to spawn a new future.
pub fn spawn<F>(&self, future: F) -> &Self pub fn spawn<F>(&mut self, future: F) -> &mut Self
where where
F: Future<Output = ()> + 'static, F: Future<Item = (), Error = ()> + 'static,
{ {
self.local.spawn_local(future); self.executor.spawn(future);
self self
} }
@@ -82,10 +119,56 @@ impl Runtime {
/// ///
/// The caller is responsible for ensuring that other spawned futures /// The caller is responsible for ensuring that other spawned futures
/// complete execution by calling `block_on` or `run`. /// complete execution by calling `block_on` or `run`.
pub fn block_on<F>(&mut self, f: F) -> F::Output pub fn block_on<F>(&mut self, f: F) -> Result<F::Item, F::Error>
where where
F: Future + 'static, F: Future,
{ {
self.local.block_on(&mut self.rt, f) self.enter(|executor| {
// Run the provided future
let ret = executor.block_on(f);
ret.map_err(|e| e.into_inner().expect("unexpected execution error"))
})
}
/// Run the executor to completion, blocking the thread until **all**
/// spawned futures have completed.
pub fn run(&mut self) -> Result<(), RunError> {
self.enter(|executor| executor.run())
.map_err(|e| RunError { inner: e })
}
fn enter<F, R>(&mut self, f: F) -> R
where
F: FnOnce(&mut current_thread::Entered<Timer<Reactor>>) -> R,
{
let Runtime {
ref reactor_handle,
ref timer_handle,
ref clock,
ref mut executor,
..
} = *self;
// Binds an executor to this thread
let mut enter = tokio_executor::enter().expect("Multiple executors at once");
// This will set the default handle and timer to use inside the closure
// and run the future.
tokio_reactor::with_default(&reactor_handle, &mut enter, |enter| {
clock::with_default(clock, enter, |enter| {
timer::with_default(&timer_handle, enter, |enter| {
// The TaskExecutor is a fake executor that looks into the
// current single-threaded executor when used. This is a trick,
// because we need two mutable references to the executor (one
// to run the provided future, another to install as the default
// one). We use the fake one here as the default one.
let mut default_executor = current_thread::TaskExecutor::current();
tokio_executor::with_default(&mut default_executor, enter, |enter| {
let mut executor = executor.enter(enter);
f(&mut executor)
})
})
})
})
} }
} }

View File

@@ -1,10 +1,10 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::future::Future;
use std::io; use std::io;
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use futures_channel::mpsc::UnboundedSender; use futures::sync::mpsc::UnboundedSender;
use tokio::task::LocalSet; use futures::Future;
use tokio_current_thread::Handle;
use crate::arbiter::{Arbiter, SystemCommand}; use crate::arbiter::{Arbiter, SystemCommand};
use crate::builder::{Builder, SystemRunner}; use crate::builder::{Builder, SystemRunner};
@@ -58,16 +58,16 @@ impl System {
} }
#[allow(clippy::new_ret_no_self)] #[allow(clippy::new_ret_no_self)]
/// Create new system using provided tokio Handle. /// Create new system using provided CurrentThread Handle.
/// ///
/// This method panics if it can not spawn system arbiter /// This method panics if it can not spawn system arbiter
pub fn run_in_tokio<T: Into<String>>( pub fn run_in_executor<T: Into<String>>(
name: T, name: T,
local: &LocalSet, executor: Handle,
) -> impl Future<Output = io::Result<()>> { ) -> impl Future<Item = (), Error = io::Error> + Send {
Self::builder() Self::builder()
.name(name) .name(name)
.build_async(local) .build_async(executor)
.run_nonblocking() .run_nonblocking()
} }
@@ -79,8 +79,8 @@ impl System {
}) })
} }
/// Check if current system is set, i.e., as already been started. /// Set current running system.
pub fn is_set() -> bool { pub(crate) fn is_set() -> bool {
CURRENT.with(|cell| cell.borrow().is_some()) CURRENT.with(|cell| cell.borrow().is_some())
} }

View File

@@ -1,114 +0,0 @@
use std::time::{Duration, Instant};
#[test]
fn start_and_stop() {
actix_rt::System::new("start_and_stop").block_on(async move {
assert!(
actix_rt::Arbiter::is_running(),
"System doesn't seem to have started"
);
});
assert!(
!actix_rt::Arbiter::is_running(),
"System doesn't seem to have stopped"
);
}
#[test]
fn await_for_timer() {
let time = Duration::from_secs(2);
let instant = Instant::now();
actix_rt::System::new("test_wait_timer").block_on(async move {
tokio::time::delay_for(time).await;
});
assert!(
instant.elapsed() >= time,
"Block on should poll awaited future to completion"
);
}
#[test]
fn join_another_arbiter() {
let time = Duration::from_secs(2);
let instant = Instant::now();
actix_rt::System::new("test_join_another_arbiter").block_on(async move {
let mut arbiter = actix_rt::Arbiter::new();
arbiter.send(Box::pin(async move {
tokio::time::delay_for(time).await;
actix_rt::Arbiter::current().stop();
}));
arbiter.join().unwrap();
});
assert!(
instant.elapsed() >= time,
"Join on another arbiter should complete only when it calls stop"
);
let instant = Instant::now();
actix_rt::System::new("test_join_another_arbiter").block_on(async move {
let mut arbiter = actix_rt::Arbiter::new();
arbiter.exec_fn(move || {
actix_rt::spawn(async move {
tokio::time::delay_for(time).await;
actix_rt::Arbiter::current().stop();
});
});
arbiter.join().unwrap();
});
assert!(
instant.elapsed() >= time,
"Join on a arbiter that has used actix_rt::spawn should wait for said future"
);
let instant = Instant::now();
actix_rt::System::new("test_join_another_arbiter").block_on(async move {
let mut arbiter = actix_rt::Arbiter::new();
arbiter.send(Box::pin(async move {
tokio::time::delay_for(time).await;
actix_rt::Arbiter::current().stop();
}));
arbiter.stop();
arbiter.join().unwrap();
});
assert!(
instant.elapsed() < time,
"Premature stop of arbiter should conclude regardless of it's current state"
);
}
#[test]
fn join_current_arbiter() {
let time = Duration::from_secs(2);
let instant = Instant::now();
actix_rt::System::new("test_join_current_arbiter").block_on(async move {
actix_rt::spawn(async move {
tokio::time::delay_for(time).await;
actix_rt::Arbiter::current().stop();
});
actix_rt::Arbiter::local_join().await;
});
assert!(
instant.elapsed() >= time,
"Join on current arbiter should wait for all spawned futures"
);
let large_timer = Duration::from_secs(20);
let instant = Instant::now();
actix_rt::System::new("test_join_current_arbiter").block_on(async move {
actix_rt::spawn(async move {
tokio::time::delay_for(time).await;
actix_rt::Arbiter::current().stop();
});
let f = actix_rt::Arbiter::local_join();
actix_rt::spawn(async move {
tokio::time::delay_for(large_timer).await;
actix_rt::Arbiter::current().stop();
});
f.await;
});
assert!(
instant.elapsed() < large_timer,
"local_join should await only for the already spawned futures"
);
}

View File

@@ -0,0 +1,38 @@
[package]
name = "actix-server-config"
version = "0.1.2"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix server config utils"
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git"
license = "MIT/Apache-2.0"
edition = "2018"
workspace = ".."
[lib]
name = "actix_server_config"
path = "src/lib.rs"
[package.metadata.docs.rs]
features = ["ssl", "rust-tls", "uds"]
[features]
default = []
# openssl
ssl = ["tokio-openssl"]
# rustls
rust-tls = ["rustls", "tokio-rustls"]
# unix domain sockets
uds = ["tokio-uds"]
[dependencies]
futures = "0.1.25"
tokio-io = "0.1.12"
tokio-tcp = "0.1"
tokio-openssl = { version="0.3.0", optional = true }
rustls = { version = "0.15.2", optional = true }
tokio-rustls = { version = "0.9.1", optional = true }
tokio-uds = { version="0.2.5", optional = true }

View File

@@ -0,0 +1,14 @@
# Changes
## [0.1.2] - 2019-07-18
### Added
* Add unix domnain sockets support
## [0.1.1] - 2019-04-16
### Added
* `IoStream` trait and impls for TcpStream, SslStream and TlsStream

View File

@@ -0,0 +1,241 @@
use std::cell::Cell;
use std::net::SocketAddr;
use std::rc::Rc;
use std::{fmt, io, net, time};
use tokio_io::{AsyncRead, AsyncWrite};
use tokio_tcp::TcpStream;
#[derive(Debug, Clone)]
pub struct ServerConfig {
addr: SocketAddr,
secure: Rc<Cell<bool>>,
}
impl ServerConfig {
pub fn new(addr: SocketAddr) -> Self {
ServerConfig {
addr,
secure: Rc::new(Cell::new(false)),
}
}
/// Returns the address of the local half of this TCP server socket
pub fn local_addr(&self) -> SocketAddr {
self.addr
}
/// Returns true if connection is secure (tls enabled)
pub fn secure(&self) -> bool {
self.secure.as_ref().get()
}
/// Set secure flag
pub fn set_secure(&self) {
self.secure.as_ref().set(true)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum Protocol {
Unknown,
Http10,
Http11,
Http2,
Proto1,
Proto2,
Proto3,
Proto4,
Proto5,
Proto6,
}
pub struct Io<T, P = ()> {
io: T,
proto: Protocol,
params: P,
}
impl<T> Io<T, ()> {
pub fn new(io: T) -> Self {
Self {
io,
proto: Protocol::Unknown,
params: (),
}
}
}
impl<T, P> Io<T, P> {
/// Reconstruct from a parts.
pub fn from_parts(io: T, params: P, proto: Protocol) -> Self {
Self { io, params, proto }
}
/// Deconstruct into a parts.
pub fn into_parts(self) -> (T, P, Protocol) {
(self.io, self.params, self.proto)
}
/// Returns a shared reference to the underlying stream.
pub fn get_ref(&self) -> &T {
&self.io
}
/// Returns a mutable reference to the underlying stream.
pub fn get_mut(&mut self) -> &mut T {
&mut self.io
}
/// Get selected protocol
pub fn protocol(&self) -> Protocol {
self.proto
}
/// Return new Io object with new parameter.
pub fn set<U>(self, params: U) -> Io<T, U> {
Io {
params,
io: self.io,
proto: self.proto,
}
}
/// Maps an Io<_, P> to Io<_, U> by applying a function to a contained value.
pub fn map<U, F>(self, op: F) -> Io<T, U>
where
F: FnOnce(P) -> U,
{
Io {
io: self.io,
proto: self.proto,
params: op(self.params),
}
}
}
impl<T, P> std::ops::Deref for Io<T, P> {
type Target = T;
fn deref(&self) -> &T {
&self.io
}
}
impl<T, P> std::ops::DerefMut for Io<T, P> {
fn deref_mut(&mut self) -> &mut T {
&mut self.io
}
}
impl<T: fmt::Debug, P> fmt::Debug for Io<T, P> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Io {{{:?}}}", self.io)
}
}
/// Low-level io stream operations
pub trait IoStream: AsyncRead + AsyncWrite {
/// Returns the socket address of the remote peer of this TCP connection.
fn peer_addr(&self) -> Option<SocketAddr> {
None
}
/// Sets the value of the TCP_NODELAY option on this socket.
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()>;
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()>;
fn set_keepalive(&mut self, dur: Option<time::Duration>) -> io::Result<()>;
}
impl IoStream for TcpStream {
#[inline]
fn peer_addr(&self) -> Option<net::SocketAddr> {
TcpStream::peer_addr(self).ok()
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
TcpStream::set_nodelay(self, nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
TcpStream::set_linger(self, dur)
}
#[inline]
fn set_keepalive(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
TcpStream::set_keepalive(self, dur)
}
}
#[cfg(any(feature = "ssl"))]
impl<T: IoStream> IoStream for tokio_openssl::SslStream<T> {
#[inline]
fn peer_addr(&self) -> Option<net::SocketAddr> {
self.get_ref().get_ref().peer_addr()
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
self.get_mut().get_mut().set_nodelay(nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().get_mut().set_linger(dur)
}
#[inline]
fn set_keepalive(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().get_mut().set_keepalive(dur)
}
}
#[cfg(any(feature = "rust-tls"))]
impl<T: IoStream> IoStream for tokio_rustls::TlsStream<T, rustls::ServerSession> {
#[inline]
fn peer_addr(&self) -> Option<net::SocketAddr> {
self.get_ref().0.peer_addr()
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
self.get_mut().0.set_nodelay(nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().0.set_linger(dur)
}
#[inline]
fn set_keepalive(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().0.set_keepalive(dur)
}
}
#[cfg(all(unix, feature = "uds"))]
impl IoStream for tokio_uds::UnixStream {
#[inline]
fn peer_addr(&self) -> Option<net::SocketAddr> {
None
}
#[inline]
fn set_nodelay(&mut self, _: bool) -> io::Result<()> {
Ok(())
}
#[inline]
fn set_linger(&mut self, _: Option<time::Duration>) -> io::Result<()> {
Ok(())
}
#[inline]
fn set_keepalive(&mut self, _: Option<time::Duration>) -> io::Result<()> {
Ok(())
}
}

View File

@@ -1,90 +1,5 @@
# Changes # Changes
## Unreleased
### Changed
* workers must be greater than 0
## [1.0.3] - 2020-05-19
### Changed
* Replace deprecated `net2` crate with `socket2` [#140]
[#140]: https://github.com/actix/actix-net/pull/140
## [1.0.2] - 2020-02-26
### Fixed
* Avoid error by calling `reregister()` on Windows [#103]
[#103]: https://github.com/actix/actix-net/pull/103
## [1.0.1] - 2019-12-29
### Changed
* Rename `.start()` method to `.run()`
## [1.0.0] - 2019-12-11
### Changed
* Use actix-net releases
## [1.0.0-alpha.4] - 2019-12-08
### Changed
* Use actix-service 1.0.0-alpha.4
## [1.0.0-alpha.3] - 2019-12-07
### Changed
* Migrate to tokio 0.2
### Fixed
* Fix compilation on non-unix platforms
* Better handling server configuration
## [1.0.0-alpha.2] - 2019-12-02
### Changed
* Simplify server service (remove actix-server-config)
* Allow to wait on `Server` until server stops
## [0.8.0-alpha.1] - 2019-11-22
### Changed
* Migrate to `std::future`
## [0.7.0] - 2019-10-04
### Changed
* Update `rustls` to 0.16
* Minimum required Rust version upped to 1.37.0
## [0.6.1] - 2019-09-25
### Added
* Add UDS listening support to `ServerBuilder`
## [0.6.0] - 2019-07-18 ## [0.6.0] - 2019-07-18
### Added ### Added

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-server" name = "actix-server"
version = "1.0.3" version = "0.6.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix server - General purpose tcp server" description = "Actix server - General purpose tcp server"
keywords = ["network", "framework", "async", "futures"] keywords = ["network", "framework", "async", "futures"]
@@ -8,11 +8,14 @@ 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-server/" documentation = "https://docs.rs/actix-server/"
categories = ["network-programming", "asynchronous"] categories = ["network-programming", "asynchronous"]
license = "MIT OR Apache-2.0" license = "MIT/Apache-2.0"
exclude = [".gitignore", ".cargo/config"] exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
edition = "2018" edition = "2018"
workspace = ".." workspace = ".."
[package.metadata.docs.rs]
features = ["ssl", "tls", "rust-tls", "uds"]
[lib] [lib]
name = "actix_server" name = "actix_server"
path = "src/lib.rs" path = "src/lib.rs"
@@ -20,25 +23,54 @@ path = "src/lib.rs"
[features] [features]
default = [] default = []
# tls
tls = ["native-tls"]
# openssl
ssl = ["openssl", "tokio-openssl", "actix-server-config/ssl"]
# rustls
rust-tls = ["rustls", "tokio-rustls", "webpki", "webpki-roots", "actix-server-config/rust-tls"]
# uds
uds = ["mio-uds", "tokio-uds", "actix-server-config/uds"]
[dependencies] [dependencies]
actix-service = "1.0.1" actix-rt = "0.2.2"
actix-rt = "1.0.0" actix-service = "0.4.1"
actix-codec = "0.3.0" actix-server-config = "0.1.2"
actix-utils = "2.0.0"
log = "0.4" log = "0.4"
num_cpus = "1.11" num_cpus = "1.0"
mio = "0.6.19" mio = "0.6.19"
socket2 = "0.3" net2 = "0.2"
futures-channel = { version = "0.3.4", default-features = false } futures = "0.1"
futures-util = { version = "0.3.4", default-features = false, features = ["sink"] }
slab = "0.4" slab = "0.4"
tokio-io = "0.1"
tokio-tcp = "0.1"
tokio-timer = "0.2.8"
tokio-reactor = "0.1"
tokio-signal = "0.2"
# unix domain sockets # unix domain sockets
# FIXME: Remove it and use mio own uds feature once mio 0.7 is released mio-uds = { version="0.6.7", optional = true }
mio-uds = { version = "0.6.7" } tokio-uds = { version="0.2.5", optional = true }
# native-tls
native-tls = { version="0.2", optional = true }
# openssl
openssl = { version="0.10", optional = true }
tokio-openssl = { version="0.3", optional = true }
# rustls
rustls = { version = "0.15.2", optional = true }
tokio-rustls = { version = "0.9.1", optional = true }
webpki = { version = "0.19", optional = true }
webpki-roots = { version = "0.16", optional = true }
[dev-dependencies] [dev-dependencies]
bytes = "0.5" bytes = "0.4"
env_logger = "0.7" actix-codec = "0.1.2"
actix-testing = "1.0.0" env_logger = "0.6"

View File

@@ -1,11 +1,12 @@
use std::sync::mpsc as sync_mpsc; use std::sync::mpsc as sync_mpsc;
use std::time::Duration; use std::time::{Duration, Instant};
use std::{io, thread}; use std::{io, thread};
use actix_rt::time::{delay_until, Instant};
use actix_rt::System; use actix_rt::System;
use futures::future::{lazy, Future};
use log::{error, info}; use log::{error, info};
use slab::Slab; use slab::Slab;
use tokio_timer::Delay;
use crate::server::Server; use crate::server::Server;
use crate::socket::{SocketAddr, SocketListener, StdListener}; use crate::socket::{SocketAddr, SocketListener, StdListener};
@@ -298,7 +299,12 @@ impl Accept {
} }
Command::Resume => { Command::Resume => {
for (token, info) in self.sockets.iter() { for (token, info) in self.sockets.iter() {
if let Err(err) = self.register(token, info) { if let Err(err) = self.poll.register(
&info.sock,
mio::Token(token + DELTA),
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
error!("Can not resume socket accept process: {}", err); error!("Can not resume socket accept process: {}", err);
} else { } else {
info!( info!(
@@ -333,44 +339,17 @@ impl Accept {
true true
} }
#[cfg(not(target_os = "windows"))]
fn register(&self, token: usize, info: &ServerSocketInfo) -> io::Result<()> {
self.poll.register(
&info.sock,
mio::Token(token + DELTA),
mio::Ready::readable(),
mio::PollOpt::edge(),
)
}
#[cfg(target_os = "windows")]
fn register(&self, token: usize, info: &ServerSocketInfo) -> io::Result<()> {
// On windows, calling register without deregister cause an error.
// See https://github.com/actix/actix-web/issues/905
// Calling reregister seems to fix the issue.
self.poll
.register(
&info.sock,
mio::Token(token + DELTA),
mio::Ready::readable(),
mio::PollOpt::edge(),
)
.or_else(|_| {
self.poll.reregister(
&info.sock,
mio::Token(token + DELTA),
mio::Ready::readable(),
mio::PollOpt::edge(),
)
})
}
fn backpressure(&mut self, on: bool) { fn backpressure(&mut self, 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 self.sockets.iter() {
if let Err(err) = self.register(token, info) { if let Err(err) = self.poll.register(
&info.sock,
mio::Token(token + DELTA),
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
error!("Can not resume socket accept process: {}", err); error!("Can not resume socket accept process: {}", err);
} else { } else {
info!("Accepting connections on {} has been resumed", info.addr); info!("Accepting connections on {} has been resumed", info.addr);
@@ -391,7 +370,7 @@ impl Accept {
match self.workers[self.next].send(msg) { match self.workers[self.next].send(msg) {
Ok(_) => (), Ok(_) => (),
Err(tmp) => { Err(tmp) => {
self.srv.worker_faulted(self.workers[self.next].idx); self.srv.worker_died(self.workers[self.next].idx);
msg = tmp; msg = tmp;
self.workers.swap_remove(self.next); self.workers.swap_remove(self.next);
if self.workers.is_empty() { if self.workers.is_empty() {
@@ -417,7 +396,7 @@ impl Accept {
return; return;
} }
Err(tmp) => { Err(tmp) => {
self.srv.worker_faulted(self.workers[self.next].idx); self.srv.worker_died(self.workers[self.next].idx);
msg = tmp; msg = tmp;
self.workers.swap_remove(self.next); self.workers.swap_remove(self.next);
if self.workers.is_empty() { if self.workers.is_empty() {
@@ -461,9 +440,13 @@ impl Accept {
info.timeout = Some(Instant::now() + Duration::from_millis(500)); info.timeout = Some(Instant::now() + Duration::from_millis(500));
let r = self.timer.1.clone(); let r = self.timer.1.clone();
System::current().arbiter().send(Box::pin(async move { System::current().arbiter().send(lazy(move || {
delay_until(Instant::now() + Duration::from_millis(510)).await; Delay::new(Instant::now() + Duration::from_millis(510))
let _ = r.set_readiness(mio::Ready::readable()); .map_err(|_| ())
.and_then(move |_| {
let _ = r.set_readiness(mio::Ready::readable());
Ok(())
})
})); }));
return; return;
} }

View File

@@ -1,27 +1,25 @@
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Duration; use std::time::Duration;
use std::{io, mem, net}; use std::{io, mem, net};
use actix_rt::net::TcpStream; use actix_rt::{spawn, Arbiter, System};
use actix_rt::time::{delay_until, Instant}; use futures::future::{lazy, ok};
use actix_rt::{spawn, System}; use futures::stream::futures_unordered;
use futures_channel::mpsc::{unbounded, UnboundedReceiver}; use futures::sync::mpsc::{unbounded, UnboundedReceiver};
use futures_channel::oneshot; use futures::{Async, Future, Poll, Stream};
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 net2::TcpBuilder;
use num_cpus;
use tokio_tcp::TcpStream;
use tokio_timer::sleep;
use crate::accept::{AcceptLoop, AcceptNotify, Command}; use crate::accept::{AcceptLoop, AcceptNotify, Command};
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::services::{InternalServiceFactory, ServiceFactory, StreamNewService};
use crate::signals::{Signal, Signals}; use crate::signals::{Signal, Signals};
use crate::socket::StdListener; use crate::socket::StdListener;
use crate::worker::{self, Worker, WorkerAvailability, WorkerClient}; use crate::worker::{self, Worker, WorkerAvailability, WorkerClient};
use crate::Token; use crate::{ssl, Token};
/// Server builder /// Server builder
pub struct ServerBuilder { pub struct ServerBuilder {
@@ -30,14 +28,13 @@ pub struct ServerBuilder {
backlog: i32, backlog: i32,
workers: Vec<(usize, WorkerClient)>, workers: Vec<(usize, WorkerClient)>,
services: Vec<Box<dyn InternalServiceFactory>>, services: Vec<Box<dyn InternalServiceFactory>>,
sockets: Vec<(Token, String, StdListener)>, sockets: Vec<(Token, StdListener)>,
accept: AcceptLoop, accept: AcceptLoop,
exit: bool, exit: bool,
shutdown_timeout: Duration, shutdown_timeout: Duration,
no_signals: bool, no_signals: bool,
cmd: UnboundedReceiver<ServerCommand>, cmd: UnboundedReceiver<ServerCommand>,
server: Server, server: Server,
notify: Vec<oneshot::Sender<()>>,
} }
impl Default for ServerBuilder { impl Default for ServerBuilder {
@@ -64,7 +61,6 @@ impl ServerBuilder {
shutdown_timeout: Duration::from_secs(30), shutdown_timeout: Duration::from_secs(30),
no_signals: false, no_signals: false,
cmd: rx, cmd: rx,
notify: Vec::new(),
server, server,
} }
} }
@@ -72,9 +68,8 @@ impl ServerBuilder {
/// Set number of workers to start. /// Set number of workers to start.
/// ///
/// By default server uses number of available logical cpu as workers /// By default server uses number of available logical cpu as workers
/// count. Workers must be greater than 0. /// count.
pub fn workers(mut self, num: usize) -> Self { pub fn workers(mut self, num: usize) -> Self {
assert_ne!(num, 0, "workers must be greater than 0");
self.threads = num; self.threads = num;
self self
} }
@@ -105,6 +100,17 @@ impl ServerBuilder {
self self
} }
/// Sets the maximum per-worker concurrent connection establish process.
///
/// All listeners will stop accepting connections when this limit is reached. It
/// can be used to limit the global SSL CPU usage.
///
/// By default max connections is set to a 256.
pub fn maxconnrate(self, num: usize) -> Self {
ssl::max_concurrent_ssl_connect(num);
self
}
/// Stop actix system. /// Stop actix system.
pub fn system_exit(mut self) -> Self { pub fn system_exit(mut self) -> Self {
self.exit = true; self.exit = true;
@@ -146,8 +152,8 @@ impl ServerBuilder {
let mut srv = ConfiguredService::new(apply); let mut srv = ConfiguredService::new(apply);
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, lst.local_addr()?);
self.sockets.push((token, name, StdListener::Tcp(lst))); self.sockets.push((token, StdListener::Tcp(lst)));
} }
self.services.push(Box::new(srv)); self.services.push(Box::new(srv));
} }
@@ -172,59 +178,36 @@ impl ServerBuilder {
factory.clone(), factory.clone(),
lst.local_addr()?, lst.local_addr()?,
)); ));
self.sockets self.sockets.push((token, StdListener::Tcp(lst)));
.push((token, name.as_ref().to_string(), StdListener::Tcp(lst)));
} }
Ok(self) Ok(self)
} }
#[cfg(all(unix))] #[cfg(all(unix, feature = "uds"))]
/// 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>(mut self, name: N, addr: U, factory: F) -> io::Result<Self>
where where
F: ServiceFactory<actix_rt::net::UnixStream>, F: ServiceFactory<tokio_uds::UnixStream>,
N: AsRef<str>, N: AsRef<str>,
U: AsRef<std::path::Path>, U: AsRef<std::path::Path>,
{ {
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::os::unix::net::UnixListener; use std::os::unix::net::UnixListener;
// The path must not exist when we try to bind. // TODO: need to do something with existing paths
// Try to remove it to avoid bind error. let _ = std::fs::remove_file(addr.as_ref());
if let Err(e) = std::fs::remove_file(addr.as_ref()) {
// NotFound is expected and not an issue. Anything else is.
if e.kind() != std::io::ErrorKind::NotFound {
return Err(e);
}
}
let lst = UnixListener::bind(addr)?; let lst = UnixListener::bind(addr)?;
self.listen_uds(name, lst, factory)
}
#[cfg(all(unix))]
/// Add new unix domain service to the server.
/// Useful when running as a systemd service and
/// a socket FD can be acquired using the systemd crate.
pub fn listen_uds<F, N: AsRef<str>>(
mut self,
name: N,
lst: std::os::unix::net::UnixListener,
factory: F,
) -> io::Result<Self>
where
F: ServiceFactory<actix_rt::net::UnixStream>,
{
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
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 = SocketAddr::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,
factory, factory.clone(),
addr, addr,
)); ));
self.sockets self.sockets.push((token, StdListener::Uds(lst)));
.push((token, name.as_ref().to_string(), StdListener::Uds(lst)));
Ok(self) Ok(self)
} }
@@ -245,48 +228,59 @@ impl ServerBuilder {
factory, factory,
lst.local_addr()?, lst.local_addr()?,
)); ));
self.sockets self.sockets.push((token, StdListener::Tcp(lst)));
.push((token, name.as_ref().to_string(), StdListener::Tcp(lst)));
Ok(self) Ok(self)
} }
#[doc(hidden)] /// Spawn new thread and start listening for incoming connections.
pub fn start(self) -> Server { ///
self.run() /// This method spawns new thread and starts new actix system. Other than
/// that it is similar to `start()` method. This method blocks.
///
/// This methods panics if no socket addresses get bound.
///
/// ```rust,ignore
/// use actix_web::*;
///
/// fn main() -> std::io::Result<()> {
/// Server::new().
/// .service(
/// HttpServer::new(|| App::new().service(web::service("/").to(|| HttpResponse::Ok())))
/// .bind("127.0.0.1:0")
/// .run()
/// }
/// ```
pub fn run(self) -> io::Result<()> {
let sys = System::new("http-server");
self.start();
sys.run()
} }
/// Starts processing incoming connections and return server controller. /// Starts processing incoming connections and return server controller.
pub fn run(mut self) -> Server { pub fn start(mut self) -> Server {
if self.sockets.is_empty() { if self.sockets.is_empty() {
panic!("Server should have at least one bound socket"); panic!("Server should have at least one bound socket");
} else { } else {
info!("Starting {} workers", self.threads); info!("Starting {} workers", self.threads);
// start workers // start workers
let workers = (0..self.threads) let mut workers = Vec::new();
.map(|idx| { for idx in 0..self.threads {
let worker = self.start_worker(idx, self.accept.get_notify()); let worker = self.start_worker(idx, self.accept.get_notify());
self.workers.push((idx, worker.clone())); workers.push(worker.clone());
self.workers.push((idx, worker));
worker }
})
.collect();
// start accept thread // start accept thread
for sock in &self.sockets { for sock in &self.sockets {
info!("Starting \"{}\" service on {}", sock.1, sock.2); info!("Starting server on {}", sock.1);
} }
self.accept.start( self.accept
mem::take(&mut self.sockets) .start(mem::replace(&mut self.sockets, Vec::new()), workers);
.into_iter()
.map(|t| (t.0, t.2))
.collect(),
workers,
);
// handle signals // handle signals
if !self.no_signals { if !self.no_signals {
Signals::start(self.server.clone()).unwrap(); Signals::start(self.server.clone());
} }
// start http server actor // start http server actor
@@ -297,11 +291,20 @@ impl ServerBuilder {
} }
fn start_worker(&self, idx: usize, notify: AcceptNotify) -> WorkerClient { fn start_worker(&self, idx: usize, notify: AcceptNotify) -> WorkerClient {
let (tx1, rx1) = unbounded();
let (tx2, rx2) = unbounded();
let timeout = self.shutdown_timeout;
let avail = WorkerAvailability::new(notify); let avail = WorkerAvailability::new(notify);
let worker = WorkerClient::new(idx, tx1, tx2, avail.clone());
let services: Vec<Box<dyn InternalServiceFactory>> = let services: Vec<Box<dyn InternalServiceFactory>> =
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) Arbiter::new().send(lazy(move || {
Worker::start(rx1, rx2, services, avail, timeout);
Ok::<_, ()>(())
}));
worker
} }
fn handle_cmd(&mut self, item: ServerCommand) { fn handle_cmd(&mut self, item: ServerCommand) {
@@ -345,9 +348,6 @@ impl ServerBuilder {
_ => (), _ => (),
} }
} }
ServerCommand::Notify(tx) => {
self.notify.push(tx);
}
ServerCommand::Stop { ServerCommand::Stop {
graceful, graceful,
completion, completion,
@@ -356,59 +356,43 @@ impl ServerBuilder {
// stop accept thread // stop accept thread
self.accept.send(Command::Stop); self.accept.send(Command::Stop);
let notify = std::mem::take(&mut self.notify);
// stop workers // stop workers
if !self.workers.is_empty() && graceful { if !self.workers.is_empty() && graceful {
spawn( spawn(
self.workers futures_unordered(
.iter() self.workers
.map(move |worker| worker.1.stop(graceful)) .iter()
.collect::<FuturesUnordered<_>>() .map(move |worker| worker.1.stop(graceful)),
.collect::<Vec<_>>() )
.then(move |_| { .collect()
if let Some(tx) = completion { .then(move |_| {
let _ = tx.send(()); if let Some(tx) = completion {
} let _ = tx.send(());
for tx in notify { }
let _ = tx.send(()); if exit {
} spawn(sleep(Duration::from_millis(300)).then(|_| {
if exit { System::current().stop();
spawn( ok(())
async { }));
delay_until( }
Instant::now() + Duration::from_millis(300), ok(())
) }),
.await;
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(sleep(Duration::from_millis(300)).then(|_| {
delay_until(Instant::now() + Duration::from_millis(300)).then( System::current().stop();
|_| { ok(())
System::current().stop(); }));
ready(())
},
),
);
} }
if let Some(tx) = completion { if let Some(tx) = completion {
let _ = tx.send(()); let _ = tx.send(());
} }
for tx in notify {
let _ = tx.send(());
}
} }
} }
ServerCommand::WorkerFaulted(idx) => { ServerCommand::WorkerDied(idx) => {
let mut found = false; let mut found = false;
for i in 0..self.workers.len() { for i in 0..self.workers.len() {
if self.workers[i].0 == idx { if self.workers[i].0 == idx {
@@ -442,15 +426,15 @@ impl ServerBuilder {
} }
impl Future for ServerBuilder { impl Future for ServerBuilder {
type Output = (); type Item = ();
type Error = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
loop { loop {
match ready!(Pin::new(&mut self.cmd).poll_next(cx)) { match self.cmd.poll() {
Some(it) => self.as_mut().get_mut().handle_cmd(it), Ok(Async::Ready(None)) | Err(_) => return Ok(Async::Ready(())),
None => { Ok(Async::NotReady) => return Ok(Async::NotReady),
return Poll::Pending; Ok(Async::Ready(Some(item))) => self.handle_cmd(item),
}
} }
} }
} }
@@ -488,13 +472,11 @@ 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: net::SocketAddr, backlog: i32) -> io::Result<net::TcpListener> {
let domain = match addr { let builder = match addr {
net::SocketAddr::V4(_) => Domain::ipv4(), net::SocketAddr::V4(_) => TcpBuilder::new_v4()?,
net::SocketAddr::V6(_) => Domain::ipv6(), net::SocketAddr::V6(_) => TcpBuilder::new_v6()?,
}; };
let socket = Socket::new(domain, Type::stream(), Some(Protocol::tcp()))?; builder.reuse_address(true)?;
socket.set_reuse_address(true)?; builder.bind(addr)?;
socket.bind(&addr.into())?; Ok(builder.listen(backlog)?)
socket.listen(backlog)?;
Ok(socket.into_tcp_listener())
} }

View File

@@ -1,14 +1,16 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::{fmt, io, net}; use std::{fmt, io, net};
use actix_rt::net::TcpStream; use actix_server_config::{Io, ServerConfig};
use actix_service as actix; use actix_service::{IntoNewService, NewService};
use actix_utils::counter::CounterGuard; use futures::future::{join_all, Future};
use futures_util::future::{ok, Future, FutureExt, LocalBoxFuture};
use log::error; use log::error;
use tokio_tcp::TcpStream;
use crate::counter::CounterGuard;
use super::builder::bind_addr; use super::builder::bind_addr;
use super::service::{ use super::services::{
BoxedServerService, InternalServiceFactory, ServerMessage, StreamService, BoxedServerService, InternalServiceFactory, ServerMessage, StreamService,
}; };
use super::Token; use super::Token;
@@ -75,8 +77,7 @@ impl ServiceConfig {
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, net::SocketAddr)>,
topics: HashMap<String, Token>, services: HashMap<String, Token>,
services: Vec<Token>,
} }
impl ConfiguredService { impl ConfiguredService {
@@ -84,15 +85,13 @@ impl ConfiguredService {
ConfiguredService { ConfiguredService {
rt, rt,
names: HashMap::new(), names: HashMap::new(),
topics: HashMap::new(), services: HashMap::new(),
services: Vec::new(),
} }
} }
pub(super) fn stream(&mut self, token: Token, name: String, addr: net::SocketAddr) { pub(super) fn stream(&mut self, token: Token, name: String, addr: net::SocketAddr) {
self.names.insert(token, (name.clone(), addr)); self.names.insert(token, (name.clone(), addr));
self.topics.insert(name, token); self.services.insert(name, token);
self.services.push(token);
} }
} }
@@ -105,55 +104,54 @@ impl InternalServiceFactory for ConfiguredService {
Box::new(Self { Box::new(Self {
rt: self.rt.clone(), rt: self.rt.clone(),
names: self.names.clone(), names: self.names.clone(),
topics: self.topics.clone(),
services: self.services.clone(), services: self.services.clone(),
}) })
} }
fn create(&self) -> LocalBoxFuture<'static, Result<Vec<(Token, BoxedServerService)>, ()>> { fn create(&self) -> Box<dyn Future<Item = Vec<(Token, BoxedServerService)>, Error = ()>> {
// configure services // configure services
let mut rt = ServiceRuntime::new(self.topics.clone()); let mut rt = ServiceRuntime::new(self.services.clone());
self.rt.configure(&mut rt); self.rt.configure(&mut rt);
rt.validate(); rt.validate();
let mut names = self.names.clone();
let tokens = self.services.clone();
// construct services let services = rt.services;
async move {
let mut services = rt.services; // on start futures
// TODO: Proper error handling here if rt.onstart.is_empty() {
for f in rt.onstart.into_iter() { // construct services
f.await; let mut fut = Vec::new();
for (token, ns) in services {
let config = ServerConfig::new(self.names[&token].1);
fut.push(ns.new_service(&config).map(move |service| (token, service)));
} }
let mut res = vec![];
for token in tokens { Box::new(join_all(fut).map_err(|e| {
if let Some(srv) = services.remove(&token) { error!("Can not construct service: {:?}", e);
let newserv = srv.new_service(()); }))
match newserv.await { } else {
Ok(serv) => { let names = self.names.clone();
res.push((token, serv));
// run onstart future and then construct services
Box::new(
join_all(rt.onstart)
.map_err(|e| {
error!("Can not construct service: {:?}", e);
})
.and_then(move |_| {
// construct services
let mut fut = Vec::new();
for (token, ns) in services {
let config = ServerConfig::new(names[&token].1);
fut.push(
ns.new_service(&config).map(move |service| (token, service)),
);
} }
Err(_) => { join_all(fut).map_err(|e| {
error!("Can not construct service"); error!("Can not construct service: {:?}", e);
return Err(()); })
} }),
} )
} else {
let name = names.remove(&token).unwrap().0;
res.push((
token,
Box::new(StreamService::new(actix::fn_service(
move |_: TcpStream| {
error!("Service {:?} is not configured", name);
ok::<_, ()>(())
},
))),
));
};
}
return Ok(res);
} }
.boxed_local()
} }
} }
@@ -183,7 +181,7 @@ fn not_configured(_: &mut ServiceRuntime) {
pub struct ServiceRuntime { pub struct ServiceRuntime {
names: HashMap<String, Token>, names: HashMap<String, Token>,
services: HashMap<Token, BoxedNewService>, services: HashMap<Token, BoxedNewService>,
onstart: Vec<LocalBoxFuture<'static, ()>>, onstart: Vec<Box<dyn Future<Item = (), Error = ()>>>,
} }
impl ServiceRuntime { impl ServiceRuntime {
@@ -209,8 +207,8 @@ impl ServiceRuntime {
/// *ServiceConfig::bind()* or *ServiceConfig::listen()* methods. /// *ServiceConfig::bind()* or *ServiceConfig::listen()* methods.
pub fn service<T, F>(&mut self, name: &str, service: F) pub fn service<T, F>(&mut self, name: &str, service: F)
where where
F: actix::IntoServiceFactory<T>, F: IntoNewService<T>,
T: actix::ServiceFactory<Config = (), Request = TcpStream> + 'static, T: NewService<Config = ServerConfig, Request = Io<TcpStream>> + 'static,
T::Future: 'static, T::Future: 'static,
T::Service: 'static, T::Service: 'static,
T::InitError: fmt::Debug, T::InitError: fmt::Debug,
@@ -218,9 +216,9 @@ impl ServiceRuntime {
// let name = name.to_owned(); // let name = name.to_owned();
if let Some(token) = self.names.get(name) { if let Some(token) = self.names.get(name) {
self.services.insert( self.services.insert(
*token, token.clone(),
Box::new(ServiceFactory { Box::new(ServiceFactory {
inner: service.into_factory(), inner: service.into_new_service(),
}), }),
); );
} else { } else {
@@ -231,21 +229,21 @@ impl ServiceRuntime {
/// Execute future before services initialization. /// Execute future before services initialization.
pub fn on_start<F>(&mut self, fut: F) pub fn on_start<F>(&mut self, fut: F)
where where
F: Future<Output = ()> + 'static, F: Future<Item = (), Error = ()> + 'static,
{ {
self.onstart.push(fut.boxed_local()) self.onstart.push(Box::new(fut))
} }
} }
type BoxedNewService = Box< type BoxedNewService = Box<
dyn actix::ServiceFactory< dyn NewService<
Request = (Option<CounterGuard>, ServerMessage), Request = (Option<CounterGuard>, ServerMessage),
Response = (), Response = (),
Error = (), Error = (),
InitError = (), InitError = (),
Config = (), Config = ServerConfig,
Service = BoxedServerService, Service = BoxedServerService,
Future = LocalBoxFuture<'static, Result<BoxedServerService, ()>>, Future = Box<dyn Future<Item = BoxedServerService, Error = ()>>,
>, >,
>; >;
@@ -253,9 +251,9 @@ struct ServiceFactory<T> {
inner: T, inner: T,
} }
impl<T> actix::ServiceFactory for ServiceFactory<T> impl<T> NewService for ServiceFactory<T>
where where
T: actix::ServiceFactory<Config = (), Request = TcpStream>, T: NewService<Config = ServerConfig, Request = Io<TcpStream>>,
T::Future: 'static, T::Future: 'static,
T::Service: 'static, T::Service: 'static,
T::Error: 'static, T::Error: 'static,
@@ -265,21 +263,14 @@ where
type Response = (); type Response = ();
type Error = (); type Error = ();
type InitError = (); type InitError = ();
type Config = (); type Config = ServerConfig;
type Service = BoxedServerService; type Service = BoxedServerService;
type Future = LocalBoxFuture<'static, Result<BoxedServerService, ()>>; type Future = Box<dyn Future<Item = BoxedServerService, Error = ()>>;
fn new_service(&self, _: ()) -> Self::Future { fn new_service(&self, cfg: &ServerConfig) -> Self::Future {
let fut = self.inner.new_service(()); Box::new(self.inner.new_service(cfg).map_err(|_| ()).map(|s| {
async move { let service: BoxedServerService = Box::new(StreamService::new(s));
return match fut.await { service
Ok(s) => Ok(Box::new(StreamService::new(s)) as BoxedServerService), }))
Err(e) => {
error!("Can not construct service: {:?}", e);
Err(())
}
};
}
.boxed_local()
} }
} }

View File

@@ -0,0 +1,80 @@
use std::cell::Cell;
use std::rc::Rc;
use futures::task::AtomicTask;
#[derive(Clone)]
/// Simple counter with ability to notify task on reaching specific number
///
/// Counter could be cloned, total ncount is shared across all clones.
pub struct Counter(Rc<CounterInner>);
#[derive(Debug)]
struct CounterInner {
count: Cell<usize>,
capacity: usize,
task: AtomicTask,
}
impl Counter {
/// Create `Counter` instance and set max value.
pub fn new(capacity: usize) -> Self {
Counter(Rc::new(CounterInner {
capacity,
count: Cell::new(0),
task: AtomicTask::new(),
}))
}
pub fn get(&self) -> CounterGuard {
CounterGuard::new(self.0.clone())
}
/// Check if counter is not at capacity
pub fn available(&self) -> bool {
self.0.available()
}
/// Get total number of acquired counts
pub fn total(&self) -> usize {
self.0.count.get()
}
}
#[derive(Debug)]
pub struct CounterGuard(Rc<CounterInner>);
impl CounterGuard {
fn new(inner: Rc<CounterInner>) -> Self {
inner.inc();
CounterGuard(inner)
}
}
impl Drop for CounterGuard {
fn drop(&mut self) {
self.0.dec();
}
}
impl CounterInner {
fn inc(&self) {
self.count.set(self.count.get() + 1);
}
fn dec(&self) {
let num = self.count.get();
self.count.set(num - 1);
if num == self.capacity {
self.task.notify();
}
}
fn available(&self) -> bool {
let avail = self.count.get() < self.capacity;
if !avail {
self.task.register();
}
avail
}
}

View File

@@ -1,31 +1,36 @@
//! General purpose tcp server //! General purpose tcp server
#![deny(rust_2018_idioms, warnings)]
#![allow(clippy::type_complexity)]
mod accept; mod accept;
mod builder; mod builder;
mod config; mod config;
mod counter;
mod server; mod server;
mod service; mod services;
mod signals; mod signals;
mod socket; mod socket;
pub mod ssl;
mod worker; mod worker;
pub use actix_server_config::{Io, IoStream, Protocol, ServerConfig};
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::services::ServiceFactory;
#[doc(hidden)] #[doc(hidden)]
pub use self::socket::FromStream; pub use self::socket::FromStream;
#[doc(hidden)]
pub use self::services::ServiceFactory as StreamServiceFactory;
/// 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 Token { impl Token {
pub(crate) fn next(&mut self) -> Token { pub(crate) fn next(&mut self) -> Token {
let token = Token(self.0); let token = Token(self.0 + 1);
self.0 += 1; self.0 += 1;
token token
} }

View File

@@ -1,18 +1,13 @@
use std::future::Future; use futures::sync::mpsc::UnboundedSender;
use std::io; use futures::sync::oneshot;
use std::pin::Pin; use futures::Future;
use std::task::{Context, Poll};
use futures_channel::mpsc::UnboundedSender;
use futures_channel::oneshot;
use futures_util::FutureExt;
use crate::builder::ServerBuilder; use crate::builder::ServerBuilder;
use crate::signals::Signal; use crate::signals::Signal;
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum ServerCommand { pub(crate) enum ServerCommand {
WorkerFaulted(usize), WorkerDied(usize),
Pause(oneshot::Sender<()>), Pause(oneshot::Sender<()>),
Resume(oneshot::Sender<()>), Resume(oneshot::Sender<()>),
Signal(Signal), Signal(Signal),
@@ -21,19 +16,14 @@ pub(crate) enum ServerCommand {
graceful: bool, graceful: bool,
completion: Option<oneshot::Sender<()>>, completion: Option<oneshot::Sender<()>>,
}, },
/// Notify of server stop
Notify(oneshot::Sender<()>),
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct Server( pub struct Server(UnboundedSender<ServerCommand>);
UnboundedSender<ServerCommand>,
Option<oneshot::Receiver<()>>,
);
impl Server { impl Server {
pub(crate) fn new(tx: UnboundedSender<ServerCommand>) -> Self { pub(crate) fn new(tx: UnboundedSender<ServerCommand>) -> Self {
Server(tx, None) Server(tx)
} }
/// Start server building process /// Start server building process
@@ -45,64 +35,36 @@ impl Server {
let _ = self.0.unbounded_send(ServerCommand::Signal(sig)); let _ = self.0.unbounded_send(ServerCommand::Signal(sig));
} }
pub(crate) fn worker_faulted(&self, idx: usize) { pub(crate) fn worker_died(&self, idx: usize) {
let _ = self.0.unbounded_send(ServerCommand::WorkerFaulted(idx)); let _ = self.0.unbounded_send(ServerCommand::WorkerDied(idx));
} }
/// Pause accepting incoming connections /// Pause accepting incoming connections
/// ///
/// If socket contains some pending connection, they might be dropped. /// If socket contains some pending connection, they might be dropped.
/// All opened connection remains active. /// All opened connection remains active.
pub fn pause(&self) -> impl Future<Output = ()> { pub fn pause(&self) -> impl Future<Item = (), Error = ()> {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
let _ = self.0.unbounded_send(ServerCommand::Pause(tx)); let _ = self.0.unbounded_send(ServerCommand::Pause(tx));
rx.map(|_| ()) rx.map_err(|_| ())
} }
/// Resume accepting incoming connections /// Resume accepting incoming connections
pub fn resume(&self) -> impl Future<Output = ()> { pub fn resume(&self) -> impl Future<Item = (), Error = ()> {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
let _ = self.0.unbounded_send(ServerCommand::Resume(tx)); let _ = self.0.unbounded_send(ServerCommand::Resume(tx));
rx.map(|_| ()) rx.map_err(|_| ())
} }
/// Stop incoming connection processing, stop all workers and exit. /// Stop incoming connection processing, stop all workers and exit.
/// ///
/// 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<Item = (), Error = ()> {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
let _ = self.0.unbounded_send(ServerCommand::Stop { let _ = self.0.unbounded_send(ServerCommand::Stop {
graceful, graceful,
completion: Some(tx), completion: Some(tx),
}); });
rx.map(|_| ()) rx.map_err(|_| ())
}
}
impl Clone for Server {
fn clone(&self) -> Self {
Self(self.0.clone(), None)
}
}
impl Future for Server {
type Output = io::Result<()>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
if this.1.is_none() {
let (tx, rx) = oneshot::channel();
if this.0.unbounded_send(ServerCommand::Notify(tx)).is_err() {
return Poll::Ready(Ok(()));
}
this.1 = Some(rx);
}
match Pin::new(this.1.as_mut().unwrap()).poll(cx) {
Poll::Pending => Poll::Pending,
Poll::Ready(Ok(_)) => Poll::Ready(Ok(())),
Poll::Ready(Err(_)) => Poll::Ready(Ok(())),
}
} }
} }

View File

@@ -1,16 +1,16 @@
use std::marker::PhantomData; use std::marker::PhantomData;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::task::{Context, Poll};
use std::time::Duration; use std::time::Duration;
use actix_rt::spawn; use actix_rt::spawn;
use actix_service::{self as actix, Service, ServiceFactory as ActixServiceFactory}; use actix_server_config::{Io, ServerConfig};
use actix_utils::counter::CounterGuard; use actix_service::{NewService, Service};
use futures_util::future::{err, ok, LocalBoxFuture, Ready}; use futures::future::{err, ok, FutureResult};
use futures_util::{FutureExt, TryFutureExt}; use futures::{Future, Poll};
use log::error; use log::error;
use super::Token; use super::Token;
use crate::counter::CounterGuard;
use crate::socket::{FromStream, StdStream}; use crate::socket::{FromStream, StdStream};
/// Server message /// Server message
@@ -24,9 +24,9 @@ pub(crate) enum ServerMessage {
} }
pub trait ServiceFactory<Stream: FromStream>: Send + Clone + 'static { pub trait ServiceFactory<Stream: FromStream>: Send + Clone + 'static {
type Factory: actix::ServiceFactory<Config = (), Request = Stream>; type NewService: NewService<Config = ServerConfig, Request = Io<Stream>>;
fn create(&self) -> Self::Factory; fn create(&self) -> Self::NewService;
} }
pub(crate) trait InternalServiceFactory: Send { pub(crate) trait InternalServiceFactory: Send {
@@ -34,7 +34,7 @@ pub(crate) trait InternalServiceFactory: Send {
fn clone_factory(&self) -> Box<dyn InternalServiceFactory>; fn clone_factory(&self) -> Box<dyn InternalServiceFactory>;
fn create(&self) -> LocalBoxFuture<'static, Result<Vec<(Token, BoxedServerService)>, ()>>; fn create(&self) -> Box<dyn Future<Item = Vec<(Token, BoxedServerService)>, Error = ()>>;
} }
pub(crate) type BoxedServerService = Box< pub(crate) type BoxedServerService = Box<
@@ -42,7 +42,7 @@ pub(crate) type BoxedServerService = Box<
Request = (Option<CounterGuard>, ServerMessage), Request = (Option<CounterGuard>, ServerMessage),
Response = (), Response = (),
Error = (), Error = (),
Future = Ready<Result<(), ()>>, Future = FutureResult<(), ()>,
>, >,
>; >;
@@ -58,7 +58,7 @@ impl<T> StreamService<T> {
impl<T, I> Service for StreamService<T> impl<T, I> Service for StreamService<T>
where where
T: Service<Request = I>, T: Service<Request = Io<I>>,
T::Future: 'static, T::Future: 'static,
T::Error: 'static, T::Error: 'static,
I: FromStream, I: FromStream,
@@ -66,10 +66,10 @@ where
type Request = (Option<CounterGuard>, ServerMessage); type Request = (Option<CounterGuard>, ServerMessage);
type Response = (); type Response = ();
type Error = (); type Error = ();
type Future = Ready<Result<(), ()>>; type Future = FutureResult<(), ()>;
fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.service.poll_ready(ctx).map_err(|_| ()) self.service.poll_ready().map_err(|_| ())
} }
fn call(&mut self, (guard, req): (Option<CounterGuard>, ServerMessage)) -> Self::Future { fn call(&mut self, (guard, req): (Option<CounterGuard>, ServerMessage)) -> Self::Future {
@@ -80,11 +80,10 @@ where
}); });
if let Ok(stream) = stream { if let Ok(stream) = stream {
let f = self.service.call(stream); spawn(self.service.call(Io::new(stream)).then(move |res| {
spawn(async move {
let _ = f.await;
drop(guard); drop(guard);
}); res.map_err(|_| ()).map(|_| ())
}));
ok(()) ok(())
} else { } else {
err(()) err(())
@@ -143,17 +142,19 @@ where
}) })
} }
fn create(&self) -> LocalBoxFuture<'static, Result<Vec<(Token, BoxedServerService)>, ()>> { fn create(&self) -> Box<dyn Future<Item = Vec<(Token, BoxedServerService)>, Error = ()>> {
let token = self.token; let token = self.token;
self.inner let config = ServerConfig::new(self.addr);
.create() Box::new(
.new_service(()) self.inner
.map_err(|_| ()) .create()
.map_ok(move |inner| { .new_service(&config)
let service: BoxedServerService = Box::new(StreamService::new(inner)); .map_err(|_| ())
vec![(token, service)] .map(move |inner| {
}) let service: BoxedServerService = Box::new(StreamService::new(inner));
.boxed_local() vec![(token, service)]
}),
)
} }
} }
@@ -166,7 +167,7 @@ impl InternalServiceFactory for Box<dyn InternalServiceFactory> {
self.as_ref().clone_factory() self.as_ref().clone_factory()
} }
fn create(&self) -> LocalBoxFuture<'static, Result<Vec<(Token, BoxedServerService)>, ()>> { fn create(&self) -> Box<dyn Future<Item = Vec<(Token, BoxedServerService)>, Error = ()>> {
self.as_ref().create() self.as_ref().create()
} }
} }
@@ -174,10 +175,10 @@ impl InternalServiceFactory for Box<dyn InternalServiceFactory> {
impl<F, T, I> ServiceFactory<I> for F impl<F, T, I> ServiceFactory<I> for F
where where
F: Fn() -> T + Send + Clone + 'static, F: Fn() -> T + Send + Clone + 'static,
T: actix::ServiceFactory<Config = (), Request = I>, T: NewService<Config = ServerConfig, Request = Io<I>>,
I: FromStream, I: FromStream,
{ {
type Factory = T; type NewService = T;
fn create(&self) -> T { fn create(&self) -> T {
(self)() (self)()

View File

@@ -1,14 +1,12 @@
use std::future::Future;
use std::io; use std::io;
use std::pin::Pin;
use std::task::{Context, Poll};
use futures_util::future::lazy; use actix_rt::spawn;
use futures::stream::futures_unordered;
use futures::{Async, Future, Poll, Stream};
use crate::server::Server; use crate::server::Server;
/// Different types of process signals /// Different types of process signals
#[allow(dead_code)]
#[derive(PartialEq, Clone, Copy, Debug)] #[derive(PartialEq, Clone, Copy, Debug)]
pub(crate) enum Signal { pub(crate) enum Signal {
/// SIGHUP /// SIGHUP
@@ -24,80 +22,97 @@ 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 = io::Result<()>>>>, stream: SigStream,
#[cfg(unix)] #[cfg(unix)]
streams: Vec<(Signal, actix_rt::signal::unix::Signal)>, streams: Vec<SigStream>,
} }
type SigStream = Box<dyn Stream<Item = Signal, Error = io::Error>>;
impl Signals { impl Signals {
pub(crate) fn start(srv: Server) -> io::Result<()> { pub(crate) fn start(srv: Server) {
actix_rt::spawn(lazy(|_| { let fut = {
#[cfg(not(unix))] #[cfg(not(unix))]
{ {
actix_rt::spawn(Signals { tokio_signal::ctrl_c()
srv, .map_err(|_| ())
stream: Box::pin(actix_rt::signal::ctrl_c()), .and_then(move |stream| Signals {
}); srv,
stream: Box::new(stream.map(|_| Signal::Int)),
})
} }
#[cfg(unix)] #[cfg(unix)]
{ {
use actix_rt::signal::unix; use tokio_signal::unix;
let mut streams = Vec::new(); let mut sigs: Vec<Box<dyn Future<Item = SigStream, Error = io::Error>>> =
Vec::new();
let sig_map = [ sigs.push(Box::new(
(unix::SignalKind::interrupt(), Signal::Int), tokio_signal::unix::Signal::new(tokio_signal::unix::SIGINT).map(|stream| {
(unix::SignalKind::hangup(), Signal::Hup), let s: SigStream = Box::new(stream.map(|_| Signal::Int));
(unix::SignalKind::terminate(), Signal::Term), s
(unix::SignalKind::quit(), Signal::Quit), }),
]; ));
sigs.push(Box::new(
for (kind, sig) in sig_map.iter() { tokio_signal::unix::Signal::new(tokio_signal::unix::SIGHUP).map(
match unix::signal(*kind) { |stream: unix::Signal| {
Ok(stream) => streams.push((*sig, stream)), let s: SigStream = Box::new(stream.map(|_| Signal::Hup));
Err(e) => log::error!( s
"Can not initialize stream handler for {:?} err: {}", },
sig, ),
e ));
), sigs.push(Box::new(
} tokio_signal::unix::Signal::new(tokio_signal::unix::SIGTERM).map(
} |stream| {
let s: SigStream = Box::new(stream.map(|_| Signal::Term));
actix_rt::spawn(Signals { srv, streams }) s
},
),
));
sigs.push(Box::new(
tokio_signal::unix::Signal::new(tokio_signal::unix::SIGQUIT).map(
|stream| {
let s: SigStream = Box::new(stream.map(|_| Signal::Quit));
s
},
),
));
futures_unordered(sigs)
.collect()
.map_err(|_| ())
.and_then(move |streams| Signals { srv, streams })
} }
})); };
spawn(fut);
Ok(())
} }
} }
impl Future for Signals { impl Future for Signals {
type Output = (); type Item = ();
type Error = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
#[cfg(not(unix))] #[cfg(not(unix))]
match Pin::new(&mut self.stream).poll(cx) { loop {
Poll::Ready(_) => { match self.stream.poll() {
self.srv.signal(Signal::Int); Ok(Async::Ready(None)) | Err(_) => return Ok(Async::Ready(())),
Poll::Ready(()) Ok(Async::Ready(Some(sig))) => self.srv.signal(sig),
Ok(Async::NotReady) => return Ok(Async::NotReady),
} }
Poll::Pending => return Poll::Pending,
} }
#[cfg(unix)] #[cfg(unix)]
{ {
for idx in 0..self.streams.len() { for s in &mut self.streams {
loop { loop {
match self.streams[idx].1.poll_recv(cx) { match s.poll() {
Poll::Ready(None) => return Poll::Ready(()), Ok(Async::Ready(None)) | Err(_) => return Ok(Async::Ready(())),
Poll::Pending => break, Ok(Async::NotReady) => break,
Poll::Ready(Some(_)) => { Ok(Async::Ready(Some(sig))) => self.srv.signal(sig),
let sig = self.streams[idx].0;
self.srv.signal(sig);
}
} }
} }
} }
Poll::Pending Ok(Async::NotReady)
} }
} }
} }

View File

@@ -1,17 +1,18 @@
use std::{fmt, io, net}; use std::{fmt, io, net};
use actix_codec::{AsyncRead, AsyncWrite}; use tokio_io::{AsyncRead, AsyncWrite};
use actix_rt::net::TcpStream; use tokio_reactor::Handle;
use tokio_tcp::TcpStream;
pub(crate) enum StdListener { pub(crate) enum StdListener {
Tcp(net::TcpListener), Tcp(net::TcpListener),
#[cfg(all(unix))] #[cfg(all(unix, feature = "uds"))]
Uds(std::os::unix::net::UnixListener), Uds(std::os::unix::net::UnixListener),
} }
pub(crate) enum SocketAddr { pub(crate) enum SocketAddr {
Tcp(net::SocketAddr), Tcp(net::SocketAddr),
#[cfg(all(unix))] #[cfg(all(unix, feature = "uds"))]
Uds(std::os::unix::net::SocketAddr), Uds(std::os::unix::net::SocketAddr),
} }
@@ -19,7 +20,7 @@ impl fmt::Display for SocketAddr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self { match *self {
SocketAddr::Tcp(ref addr) => write!(f, "{}", addr), SocketAddr::Tcp(ref addr) => write!(f, "{}", addr),
#[cfg(all(unix))] #[cfg(all(unix, feature = "uds"))]
SocketAddr::Uds(ref addr) => write!(f, "{:?}", addr), SocketAddr::Uds(ref addr) => write!(f, "{:?}", addr),
} }
} }
@@ -29,7 +30,7 @@ impl fmt::Debug for SocketAddr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self { match *self {
SocketAddr::Tcp(ref addr) => write!(f, "{:?}", addr), SocketAddr::Tcp(ref addr) => write!(f, "{:?}", addr),
#[cfg(all(unix))] #[cfg(all(unix, feature = "uds"))]
SocketAddr::Uds(ref addr) => write!(f, "{:?}", addr), SocketAddr::Uds(ref addr) => write!(f, "{:?}", addr),
} }
} }
@@ -39,7 +40,7 @@ impl fmt::Display for StdListener {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self { match *self {
StdListener::Tcp(ref lst) => write!(f, "{}", lst.local_addr().ok().unwrap()), StdListener::Tcp(ref lst) => write!(f, "{}", lst.local_addr().ok().unwrap()),
#[cfg(all(unix))] #[cfg(all(unix, feature = "uds"))]
StdListener::Uds(ref lst) => write!(f, "{:?}", lst.local_addr().ok().unwrap()), StdListener::Uds(ref lst) => write!(f, "{:?}", lst.local_addr().ok().unwrap()),
} }
} }
@@ -49,7 +50,7 @@ impl StdListener {
pub(crate) fn local_addr(&self) -> SocketAddr { pub(crate) fn local_addr(&self) -> SocketAddr {
match self { match self {
StdListener::Tcp(lst) => SocketAddr::Tcp(lst.local_addr().unwrap()), StdListener::Tcp(lst) => SocketAddr::Tcp(lst.local_addr().unwrap()),
#[cfg(all(unix))] #[cfg(all(unix, feature = "uds"))]
StdListener::Uds(lst) => SocketAddr::Uds(lst.local_addr().unwrap()), StdListener::Uds(lst) => SocketAddr::Uds(lst.local_addr().unwrap()),
} }
} }
@@ -60,7 +61,7 @@ impl StdListener {
mio::net::TcpListener::from_std(lst) mio::net::TcpListener::from_std(lst)
.expect("Can not create mio::net::TcpListener"), .expect("Can not create mio::net::TcpListener"),
), ),
#[cfg(all(unix))] #[cfg(all(unix, feature = "uds"))]
StdListener::Uds(lst) => SocketListener::Uds( StdListener::Uds(lst) => SocketListener::Uds(
mio_uds::UnixListener::from_listener(lst) mio_uds::UnixListener::from_listener(lst)
.expect("Can not create mio_uds::UnixListener"), .expect("Can not create mio_uds::UnixListener"),
@@ -72,13 +73,13 @@ impl StdListener {
#[derive(Debug)] #[derive(Debug)]
pub enum StdStream { pub enum StdStream {
Tcp(std::net::TcpStream), Tcp(std::net::TcpStream),
#[cfg(all(unix))] #[cfg(all(unix, feature = "uds"))]
Uds(std::os::unix::net::UnixStream), Uds(std::os::unix::net::UnixStream),
} }
pub(crate) enum SocketListener { pub(crate) enum SocketListener {
Tcp(mio::net::TcpListener), Tcp(mio::net::TcpListener),
#[cfg(all(unix))] #[cfg(all(unix, feature = "uds"))]
Uds(mio_uds::UnixListener), Uds(mio_uds::UnixListener),
} }
@@ -88,7 +89,7 @@ impl SocketListener {
SocketListener::Tcp(ref lst) => lst SocketListener::Tcp(ref lst) => lst
.accept_std() .accept_std()
.map(|(stream, addr)| Some((StdStream::Tcp(stream), SocketAddr::Tcp(addr)))), .map(|(stream, addr)| Some((StdStream::Tcp(stream), SocketAddr::Tcp(addr)))),
#[cfg(all(unix))] #[cfg(all(unix, feature = "uds"))]
SocketListener::Uds(ref lst) => lst.accept_std().map(|res| { SocketListener::Uds(ref lst) => lst.accept_std().map(|res| {
res.map(|(stream, addr)| (StdStream::Uds(stream), SocketAddr::Uds(addr))) res.map(|(stream, addr)| (StdStream::Uds(stream), SocketAddr::Uds(addr)))
}), }),
@@ -106,7 +107,7 @@ impl mio::Evented for SocketListener {
) -> io::Result<()> { ) -> io::Result<()> {
match *self { match *self {
SocketListener::Tcp(ref lst) => lst.register(poll, token, interest, opts), SocketListener::Tcp(ref lst) => lst.register(poll, token, interest, opts),
#[cfg(all(unix))] #[cfg(all(unix, feature = "uds"))]
SocketListener::Uds(ref lst) => lst.register(poll, token, interest, opts), SocketListener::Uds(ref lst) => lst.register(poll, token, interest, opts),
} }
} }
@@ -120,14 +121,14 @@ impl mio::Evented for SocketListener {
) -> io::Result<()> { ) -> io::Result<()> {
match *self { match *self {
SocketListener::Tcp(ref lst) => lst.reregister(poll, token, interest, opts), SocketListener::Tcp(ref lst) => lst.reregister(poll, token, interest, opts),
#[cfg(all(unix))] #[cfg(all(unix, feature = "uds"))]
SocketListener::Uds(ref lst) => lst.reregister(poll, token, interest, opts), SocketListener::Uds(ref lst) => lst.reregister(poll, token, interest, opts),
} }
} }
fn deregister(&self, poll: &mio::Poll) -> io::Result<()> { fn deregister(&self, poll: &mio::Poll) -> io::Result<()> {
match *self { match *self {
SocketListener::Tcp(ref lst) => lst.deregister(poll), SocketListener::Tcp(ref lst) => lst.deregister(poll),
#[cfg(all(unix))] #[cfg(all(unix, feature = "uds"))]
SocketListener::Uds(ref lst) => { SocketListener::Uds(ref lst) => {
let res = lst.deregister(poll); let res = lst.deregister(poll);
@@ -150,8 +151,8 @@ pub trait FromStream: AsyncRead + AsyncWrite + Sized {
impl FromStream for TcpStream { impl FromStream for TcpStream {
fn from_stdstream(sock: StdStream) -> io::Result<Self> { fn from_stdstream(sock: StdStream) -> io::Result<Self> {
match sock { match sock {
StdStream::Tcp(stream) => TcpStream::from_std(stream), StdStream::Tcp(stream) => TcpStream::from_std(stream, &Handle::default()),
#[cfg(all(unix))] #[cfg(all(unix, feature = "uds"))]
StdStream::Uds(_) => { StdStream::Uds(_) => {
panic!("Should not happen, bug in server impl"); panic!("Should not happen, bug in server impl");
} }
@@ -159,12 +160,14 @@ impl FromStream for TcpStream {
} }
} }
#[cfg(all(unix))] #[cfg(all(unix, feature = "uds"))]
impl FromStream for actix_rt::net::UnixStream { impl FromStream for tokio_uds::UnixStream {
fn from_stdstream(sock: StdStream) -> io::Result<Self> { fn from_stdstream(sock: StdStream) -> io::Result<Self> {
match sock { match sock {
StdStream::Tcp(_) => panic!("Should not happen, bug in server impl"), StdStream::Tcp(_) => panic!("Should not happen, bug in server impl"),
StdStream::Uds(stream) => actix_rt::net::UnixStream::from_std(stream), StdStream::Uds(stream) => {
tokio_uds::UnixStream::from_std(stream, &Handle::default())
}
} }
} }
} }

View File

@@ -1,19 +1,22 @@
//! SSL Services //! SSL Services
#![deny(rust_2018_idioms, warnings)]
#![allow(clippy::type_complexity)]
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use actix_utils::counter::Counter; use crate::counter::Counter;
#[cfg(feature = "openssl")] #[cfg(feature = "ssl")]
pub mod openssl; mod openssl;
#[cfg(feature = "ssl")]
pub use self::openssl::OpensslAcceptor;
#[cfg(feature = "rustls")] #[cfg(feature = "tls")]
pub mod rustls; mod nativetls;
#[cfg(feature = "tls")]
pub use self::nativetls::{NativeTlsAcceptor, TlsStream};
#[cfg(feature = "nativetls")] #[cfg(feature = "rust-tls")]
pub mod nativetls; mod rustls;
#[cfg(feature = "rust-tls")]
pub use self::rustls::RustlsAcceptor;
/// Sets the maximum per-worker concurrent ssl connection establish process. /// Sets the maximum per-worker concurrent ssl connection establish process.
/// ///

View File

@@ -0,0 +1,182 @@
use std::io;
use std::marker::PhantomData;
use actix_service::{NewService, Service};
use futures::{future::ok, future::FutureResult, Async, Future, Poll};
use native_tls::{self, Error, HandshakeError, TlsAcceptor};
use tokio_io::{AsyncRead, AsyncWrite};
use crate::counter::{Counter, CounterGuard};
use crate::ssl::MAX_CONN_COUNTER;
use crate::{Io, Protocol, ServerConfig};
/// Support `SSL` connections via native-tls package
///
/// `tls` feature enables `NativeTlsAcceptor` type
pub struct NativeTlsAcceptor<T, P = ()> {
acceptor: TlsAcceptor,
io: PhantomData<(T, P)>,
}
impl<T: AsyncRead + AsyncWrite, P> NativeTlsAcceptor<T, P> {
/// Create `NativeTlsAcceptor` instance
pub fn new(acceptor: TlsAcceptor) -> Self {
NativeTlsAcceptor {
acceptor,
io: PhantomData,
}
}
}
impl<T: AsyncRead + AsyncWrite, P> Clone for NativeTlsAcceptor<T, P> {
fn clone(&self) -> Self {
Self {
acceptor: self.acceptor.clone(),
io: PhantomData,
}
}
}
impl<T: AsyncRead + AsyncWrite, P> NewService for NativeTlsAcceptor<T, P> {
type Request = Io<T, P>;
type Response = Io<TlsStream<T>, P>;
type Error = Error;
type Config = ServerConfig;
type Service = NativeTlsAcceptorService<T, P>;
type InitError = ();
type Future = FutureResult<Self::Service, Self::InitError>;
fn new_service(&self, cfg: &ServerConfig) -> Self::Future {
cfg.set_secure();
MAX_CONN_COUNTER.with(|conns| {
ok(NativeTlsAcceptorService {
acceptor: self.acceptor.clone(),
conns: conns.clone(),
io: PhantomData,
})
})
}
}
pub struct NativeTlsAcceptorService<T, P> {
acceptor: TlsAcceptor,
io: PhantomData<(T, P)>,
conns: Counter,
}
impl<T: AsyncRead + AsyncWrite, P> Service for NativeTlsAcceptorService<T, P> {
type Request = Io<T, P>;
type Response = Io<TlsStream<T>, P>;
type Error = Error;
type Future = Accept<T, P>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
if self.conns.available() {
Ok(Async::Ready(()))
} else {
Ok(Async::NotReady)
}
}
fn call(&mut self, req: Self::Request) -> Self::Future {
let (io, params, _) = req.into_parts();
Accept {
_guard: self.conns.get(),
inner: Some(self.acceptor.accept(io)),
params: Some(params),
}
}
}
/// A wrapper around an underlying raw stream which implements the TLS or SSL
/// protocol.
///
/// A `TlsStream<S>` represents a handshake that has been completed successfully
/// and both the server and the client are ready for receiving and sending
/// data. Bytes read from a `TlsStream` are decrypted from `S` and bytes written
/// to a `TlsStream` are encrypted when passing through to `S`.
#[derive(Debug)]
pub struct TlsStream<S> {
inner: native_tls::TlsStream<S>,
}
/// Future returned from `NativeTlsAcceptor::accept` which will resolve
/// once the accept handshake has finished.
pub struct Accept<S, P> {
inner: Option<Result<native_tls::TlsStream<S>, HandshakeError<S>>>,
params: Option<P>,
_guard: CounterGuard,
}
impl<T: AsyncRead + AsyncWrite, P> Future for Accept<T, P> {
type Item = Io<TlsStream<T>, P>;
type Error = Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self.inner.take().expect("cannot poll MidHandshake twice") {
Ok(stream) => Ok(Async::Ready(Io::from_parts(
TlsStream { inner: stream },
self.params.take().unwrap(),
Protocol::Unknown,
))),
Err(HandshakeError::Failure(e)) => Err(e),
Err(HandshakeError::WouldBlock(s)) => match s.handshake() {
Ok(stream) => Ok(Async::Ready(Io::from_parts(
TlsStream { inner: stream },
self.params.take().unwrap(),
Protocol::Unknown,
))),
Err(HandshakeError::Failure(e)) => Err(e),
Err(HandshakeError::WouldBlock(s)) => {
self.inner = Some(Err(HandshakeError::WouldBlock(s)));
Ok(Async::NotReady)
}
},
}
}
}
impl<S> TlsStream<S> {
/// Get access to the internal `native_tls::TlsStream` stream which also
/// transitively allows access to `S`.
pub fn get_ref(&self) -> &native_tls::TlsStream<S> {
&self.inner
}
/// Get mutable access to the internal `native_tls::TlsStream` stream which
/// also transitively allows mutable access to `S`.
pub fn get_mut(&mut self) -> &mut native_tls::TlsStream<S> {
&mut self.inner
}
}
impl<S: io::Read + io::Write> io::Read for TlsStream<S> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.inner.read(buf)
}
}
impl<S: io::Read + io::Write> io::Write for TlsStream<S> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.inner.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
impl<S: AsyncRead + AsyncWrite> AsyncRead for TlsStream<S> {}
impl<S: AsyncRead + AsyncWrite> AsyncWrite for TlsStream<S> {
fn shutdown(&mut self) -> Poll<(), io::Error> {
match self.inner.shutdown() {
Ok(_) => (),
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => (),
Err(e) => return Err(e),
}
self.inner.get_mut().shutdown()
}
}

View File

@@ -0,0 +1,130 @@
use std::marker::PhantomData;
use actix_service::{NewService, Service};
use futures::{future::ok, future::FutureResult, Async, Future, Poll};
use openssl::ssl::{HandshakeError, SslAcceptor};
use tokio_io::{AsyncRead, AsyncWrite};
use tokio_openssl::{AcceptAsync, SslAcceptorExt, SslStream};
use crate::counter::{Counter, CounterGuard};
use crate::ssl::MAX_CONN_COUNTER;
use crate::{Io, Protocol, ServerConfig};
/// Support `SSL` connections via openssl package
///
/// `ssl` feature enables `OpensslAcceptor` type
pub struct OpensslAcceptor<T: AsyncRead + AsyncWrite, P = ()> {
acceptor: SslAcceptor,
io: PhantomData<(T, P)>,
}
impl<T: AsyncRead + AsyncWrite, P> OpensslAcceptor<T, P> {
/// Create default `OpensslAcceptor`
pub fn new(acceptor: SslAcceptor) -> Self {
OpensslAcceptor {
acceptor,
io: PhantomData,
}
}
}
impl<T: AsyncRead + AsyncWrite, P> Clone for OpensslAcceptor<T, P> {
fn clone(&self) -> Self {
Self {
acceptor: self.acceptor.clone(),
io: PhantomData,
}
}
}
impl<T: AsyncRead + AsyncWrite, P> NewService for OpensslAcceptor<T, P> {
type Request = Io<T, P>;
type Response = Io<SslStream<T>, P>;
type Error = HandshakeError<T>;
type Config = ServerConfig;
type Service = OpensslAcceptorService<T, P>;
type InitError = ();
type Future = FutureResult<Self::Service, Self::InitError>;
fn new_service(&self, cfg: &ServerConfig) -> Self::Future {
cfg.set_secure();
MAX_CONN_COUNTER.with(|conns| {
ok(OpensslAcceptorService {
acceptor: self.acceptor.clone(),
conns: conns.clone(),
io: PhantomData,
})
})
}
}
pub struct OpensslAcceptorService<T, P> {
acceptor: SslAcceptor,
conns: Counter,
io: PhantomData<(T, P)>,
}
impl<T: AsyncRead + AsyncWrite, P> Service for OpensslAcceptorService<T, P> {
type Request = Io<T, P>;
type Response = Io<SslStream<T>, P>;
type Error = HandshakeError<T>;
type Future = OpensslAcceptorServiceFut<T, P>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
if self.conns.available() {
Ok(Async::Ready(()))
} else {
Ok(Async::NotReady)
}
}
fn call(&mut self, req: Self::Request) -> Self::Future {
let (io, params, _) = req.into_parts();
OpensslAcceptorServiceFut {
_guard: self.conns.get(),
fut: SslAcceptorExt::accept_async(&self.acceptor, io),
params: Some(params),
}
}
}
pub struct OpensslAcceptorServiceFut<T, P>
where
T: AsyncRead + AsyncWrite,
{
fut: AcceptAsync<T>,
params: Option<P>,
_guard: CounterGuard,
}
impl<T: AsyncRead + AsyncWrite, P> Future for OpensslAcceptorServiceFut<T, P> {
type Item = Io<SslStream<T>, P>;
type Error = HandshakeError<T>;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let io = futures::try_ready!(self.fut.poll());
let proto = if let Some(protos) = io.get_ref().ssl().selected_alpn_protocol() {
const H2: &[u8] = b"\x02h2";
const HTTP10: &[u8] = b"\x08http/1.0";
const HTTP11: &[u8] = b"\x08http/1.1";
if protos.windows(3).any(|window| window == H2) {
Protocol::Http2
} else if protos.windows(9).any(|window| window == HTTP11) {
Protocol::Http11
} else if protos.windows(9).any(|window| window == HTTP10) {
Protocol::Http10
} else {
Protocol::Unknown
}
} else {
Protocol::Unknown
};
Ok(Async::Ready(Io::from_parts(
io,
self.params.take().unwrap(),
proto,
)))
}
}

View File

@@ -0,0 +1,116 @@
use std::io;
use std::marker::PhantomData;
use std::sync::Arc;
use actix_service::{NewService, Service};
use futures::{future::ok, future::FutureResult, Async, Future, Poll};
use rustls::{ServerConfig, ServerSession};
use tokio_io::{AsyncRead, AsyncWrite};
use tokio_rustls::{Accept, TlsAcceptor, TlsStream};
use crate::counter::{Counter, CounterGuard};
use crate::ssl::MAX_CONN_COUNTER;
use crate::{Io, Protocol, ServerConfig as SrvConfig};
/// Support `SSL` connections via rustls package
///
/// `rust-tls` feature enables `RustlsAcceptor` type
pub struct RustlsAcceptor<T, P = ()> {
config: Arc<ServerConfig>,
io: PhantomData<(T, P)>,
}
impl<T: AsyncRead + AsyncWrite, P> RustlsAcceptor<T, P> {
/// Create `RustlsAcceptor` new service
pub fn new(config: ServerConfig) -> Self {
RustlsAcceptor {
config: Arc::new(config),
io: PhantomData,
}
}
}
impl<T, P> Clone for RustlsAcceptor<T, P> {
fn clone(&self) -> Self {
Self {
config: self.config.clone(),
io: PhantomData,
}
}
}
impl<T: AsyncRead + AsyncWrite, P> NewService for RustlsAcceptor<T, P> {
type Request = Io<T, P>;
type Response = Io<TlsStream<T, ServerSession>, P>;
type Error = io::Error;
type Config = SrvConfig;
type Service = RustlsAcceptorService<T, P>;
type InitError = ();
type Future = FutureResult<Self::Service, Self::InitError>;
fn new_service(&self, cfg: &SrvConfig) -> Self::Future {
cfg.set_secure();
MAX_CONN_COUNTER.with(|conns| {
ok(RustlsAcceptorService {
acceptor: self.config.clone().into(),
conns: conns.clone(),
io: PhantomData,
})
})
}
}
pub struct RustlsAcceptorService<T, P> {
acceptor: TlsAcceptor,
io: PhantomData<(T, P)>,
conns: Counter,
}
impl<T: AsyncRead + AsyncWrite, P> Service for RustlsAcceptorService<T, P> {
type Request = Io<T, P>;
type Response = Io<TlsStream<T, ServerSession>, P>;
type Error = io::Error;
type Future = RustlsAcceptorServiceFut<T, P>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
if self.conns.available() {
Ok(Async::Ready(()))
} else {
Ok(Async::NotReady)
}
}
fn call(&mut self, req: Self::Request) -> Self::Future {
let (io, params, _) = req.into_parts();
RustlsAcceptorServiceFut {
_guard: self.conns.get(),
fut: self.acceptor.accept(io),
params: Some(params),
}
}
}
pub struct RustlsAcceptorServiceFut<T, P>
where
T: AsyncRead + AsyncWrite,
{
fut: Accept<T>,
params: Option<P>,
_guard: CounterGuard,
}
impl<T: AsyncRead + AsyncWrite, P> Future for RustlsAcceptorServiceFut<T, P> {
type Item = Io<TlsStream<T, ServerSession>, P>;
type Error = io::Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let io = futures::try_ready!(self.fut.poll());
Ok(Async::Ready(Io::from_parts(
io,
self.params.take().unwrap(),
Protocol::Unknown,
)))
}
}

View File

@@ -1,20 +1,17 @@
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::{mem, time};
use std::time;
use actix_rt::time::{delay_until, Delay, Instant};
use actix_rt::{spawn, Arbiter}; use actix_rt::{spawn, Arbiter};
use actix_utils::counter::Counter; use futures::sync::mpsc::{UnboundedReceiver, UnboundedSender};
use futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}; use futures::sync::oneshot;
use futures_channel::oneshot; use futures::{future, Async, Future, Poll, Stream};
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_timer::{sleep, Delay};
use crate::accept::AcceptNotify; use crate::accept::AcceptNotify;
use crate::service::{BoxedServerService, InternalServiceFactory, ServerMessage}; use crate::counter::Counter;
use crate::services::{BoxedServerService, InternalServiceFactory, ServerMessage};
use crate::socket::{SocketAddr, StdStream}; use crate::socket::{SocketAddr, StdStream};
use crate::Token; use crate::Token;
@@ -128,7 +125,7 @@ impl WorkerAvailability {
pub(crate) struct Worker { pub(crate) struct Worker {
rx: UnboundedReceiver<WorkerCommand>, rx: UnboundedReceiver<WorkerCommand>,
rx2: UnboundedReceiver<StopCommand>, rx2: UnboundedReceiver<StopCommand>,
services: Vec<WorkerService>, services: Vec<Option<(usize, BoxedServerService)>>,
availability: WorkerAvailability, availability: WorkerAvailability,
conns: Counter, conns: Counter,
factories: Vec<Box<dyn InternalServiceFactory>>, factories: Vec<Box<dyn InternalServiceFactory>>,
@@ -136,155 +133,92 @@ pub(crate) struct Worker {
shutdown_timeout: time::Duration, shutdown_timeout: time::Duration,
} }
struct WorkerService {
factory: usize,
status: WorkerServiceStatus,
service: BoxedServerService,
}
impl WorkerService {
fn created(&mut self, service: BoxedServerService) {
self.service = service;
self.status = WorkerServiceStatus::Unavailable;
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum WorkerServiceStatus {
Available,
Unavailable,
Failed,
Restarting,
Stopping,
Stopped,
}
impl Worker { impl Worker {
pub(crate) fn start( pub(crate) fn start(
idx: usize, rx: UnboundedReceiver<WorkerCommand>,
rx2: UnboundedReceiver<StopCommand>,
factories: Vec<Box<dyn InternalServiceFactory>>, factories: Vec<Box<dyn InternalServiceFactory>>,
availability: WorkerAvailability, availability: WorkerAvailability,
shutdown_timeout: time::Duration, shutdown_timeout: time::Duration,
) -> WorkerClient { ) {
let (tx1, rx) = unbounded(); availability.set(false);
let (tx2, rx2) = unbounded(); let mut wrk = MAX_CONNS_COUNTER.with(|conns| Worker {
let avail = availability.clone(); rx,
rx2,
availability,
factories,
shutdown_timeout,
services: Vec::new(),
conns: conns.clone(),
state: WorkerState::Unavailable(Vec::new()),
});
Arbiter::new().send( let mut fut = Vec::new();
async move { for (idx, factory) in wrk.factories.iter().enumerate() {
availability.set(false); fut.push(factory.create().map(move |res| {
let mut wrk = MAX_CONNS_COUNTER.with(move |conns| Worker { res.into_iter()
rx, .map(|(t, s)| (idx, t, s))
rx2, .collect::<Vec<_>>()
availability, }));
factories, }
shutdown_timeout, spawn(
services: Vec::new(), future::join_all(fut)
conns: conns.clone(), .map_err(|e| {
state: WorkerState::Unavailable(Vec::new()), error!("Can not start worker: {:?}", e);
}); Arbiter::current().stop();
})
let mut fut: Vec<MapOk<LocalBoxFuture<'static, _>, _>> = Vec::new(); .and_then(move |services| {
for (idx, factory) in wrk.factories.iter().enumerate() { for item in services {
fut.push(factory.create().map_ok(move |r| { for (idx, token, service) in item {
r.into_iter() while token.0 >= wrk.services.len() {
.map(|(t, s): (Token, _)| (idx, t, s)) wrk.services.push(None);
.collect::<Vec<_>>()
}));
}
spawn(async move {
let res = join_all(fut).await;
let res: Result<Vec<_>, _> = res.into_iter().collect();
match res {
Ok(services) => {
for item in services {
for (factory, token, service) in item {
assert_eq!(token.0, wrk.services.len());
wrk.services.push(WorkerService {
factory,
service,
status: WorkerServiceStatus::Unavailable,
});
}
} }
} wrk.services[token.0] = Some((idx, service));
Err(e) => {
error!("Can not start worker: {:?}", e);
Arbiter::current().stop();
} }
} }
wrk.await wrk
}); }),
}
.boxed(),
); );
WorkerClient::new(idx, tx1, tx2, avail)
} }
fn shutdown(&mut self, force: bool) { fn shutdown(&mut self, force: bool) {
if force { if force {
self.services.iter_mut().for_each(|srv| { self.services.iter_mut().for_each(|h| {
if srv.status == WorkerServiceStatus::Available { if let Some(h) = h {
srv.status = WorkerServiceStatus::Stopped; let _ = h.1.call((None, ServerMessage::ForceShutdown));
actix_rt::spawn(
srv.service
.call((None, ServerMessage::ForceShutdown))
.map(|_| ()),
);
} }
}); });
} else { } else {
let timeout = self.shutdown_timeout; let timeout = self.shutdown_timeout;
self.services.iter_mut().for_each(move |srv| { self.services.iter_mut().for_each(move |h| {
if srv.status == WorkerServiceStatus::Available { if let Some(h) = h {
srv.status = WorkerServiceStatus::Stopping; let _ = h.1.call((None, ServerMessage::Shutdown(timeout)));
actix_rt::spawn(
srv.service
.call((None, ServerMessage::Shutdown(timeout)))
.map(|_| ()),
);
} }
}); });
} }
} }
fn check_readiness(&mut self, cx: &mut Context<'_>) -> Result<bool, (Token, usize)> { fn check_readiness(&mut self, trace: bool) -> Result<bool, (Token, usize)> {
let mut ready = self.conns.available(cx); let mut ready = self.conns.available();
let mut failed = None; let mut failed = None;
for (idx, srv) in &mut self.services.iter_mut().enumerate() { for (token, service) in &mut self.services.iter_mut().enumerate() {
if srv.status == WorkerServiceStatus::Available if let Some(service) = service {
|| srv.status == WorkerServiceStatus::Unavailable match service.1.poll_ready() {
{ Ok(Async::Ready(_)) => {
match srv.service.poll_ready(cx) { if trace {
Poll::Ready(Ok(_)) => {
if srv.status == WorkerServiceStatus::Unavailable {
trace!( trace!(
"Service {:?} is available", "Service {:?} is available",
self.factories[srv.factory].name(Token(idx)) self.factories[service.0].name(Token(token))
); );
srv.status = WorkerServiceStatus::Available;
} }
} }
Poll::Pending => { Ok(Async::NotReady) => ready = false,
ready = false; Err(_) => {
if srv.status == WorkerServiceStatus::Available {
trace!(
"Service {:?} is unavailable",
self.factories[srv.factory].name(Token(idx))
);
srv.status = WorkerServiceStatus::Unavailable;
}
}
Poll::Ready(Err(_)) => {
error!( error!(
"Service {:?} readiness check returned error, restarting", "Service {:?} readiness check returned error, restarting",
self.factories[srv.factory].name(Token(idx)) self.factories[service.0].name(Token(token))
); );
failed = Some((Token(idx), srv.factory)); failed = Some((Token(token), service.0));
srv.status = WorkerServiceStatus::Failed;
} }
} }
} }
@@ -298,162 +232,175 @@ impl Worker {
} }
enum WorkerState { enum WorkerState {
None,
Available, Available,
Unavailable(Vec<Conn>), Unavailable(Vec<Conn>),
Restarting( Restarting(
usize, usize,
Token, Token,
Pin<Box<dyn Future<Output = Result<Vec<(Token, BoxedServerService)>, ()>>>>, Box<dyn Future<Item = Vec<(Token, BoxedServerService)>, Error = ()>>,
),
Shutdown(
Pin<Box<Delay>>,
Pin<Box<Delay>>,
Option<oneshot::Sender<bool>>,
), ),
Shutdown(Delay, Delay, oneshot::Sender<bool>),
} }
impl Future for Worker { impl Future for Worker {
type Output = (); type Item = ();
type Error = ();
// FIXME: remove this attribute fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
#[allow(clippy::never_loop)]
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 Ok(Async::Ready(Some(StopCommand { graceful, result }))) = self.rx2.poll() {
Pin::new(&mut self.rx2).poll_next(cx)
{
self.availability.set(false); self.availability.set(false);
let num = num_connections(); let num = num_connections();
if num == 0 { if num == 0 {
info!("Shutting down worker, 0 connections"); info!("Shutting down worker, 0 connections");
let _ = result.send(true); let _ = result.send(true);
return Poll::Ready(()); return Ok(Async::Ready(()));
} else if graceful { } else if graceful {
self.shutdown(false); self.shutdown(false);
let num = num_connections(); let num = num_connections();
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))), sleep(time::Duration::from_secs(1)),
Box::pin(delay_until(Instant::now() + self.shutdown_timeout)), sleep(self.shutdown_timeout),
Some(result), result,
); );
} else { } else {
let _ = result.send(true); let _ = result.send(true);
return Poll::Ready(()); return Ok(Async::Ready(()));
} }
} else { } else {
info!("Force shutdown worker, {} connections", num); info!("Force shutdown worker, {} connections", num);
self.shutdown(true); self.shutdown(true);
let _ = result.send(false); let _ = result.send(false);
return Poll::Ready(()); return Ok(Async::Ready(()));
} }
} }
match self.state { let state = mem::replace(&mut self.state, WorkerState::None);
WorkerState::Unavailable(ref mut conns) => {
let conn = conns.pop(); match state {
match self.check_readiness(cx) { WorkerState::Unavailable(mut conns) => {
match self.check_readiness(true) {
Ok(true) => { Ok(true) => {
self.state = WorkerState::Available;
// process requests from wait queue // process requests from wait queue
if let Some(conn) = conn { while let Some(msg) = conns.pop() {
let guard = self.conns.get(); match self.check_readiness(false) {
let _ = self.services[conn.token.0] Ok(true) => {
.service let guard = self.conns.get();
.call((Some(guard), ServerMessage::Connect(conn.io))); let _ = self.services[msg.token.0]
} else { .as_mut()
self.state = WorkerState::Available; .expect("actix net bug")
self.availability.set(true); .1
} .call((Some(guard), ServerMessage::Connect(msg.io)));
self.poll(cx) }
} Ok(false) => {
Ok(false) => { trace!("Worker is unavailable");
// push connection back to queue self.state = WorkerState::Unavailable(conns);
if let Some(conn) = conn { return self.poll();
if let WorkerState::Unavailable(ref mut conns) = self.state { }
conns.push(conn); Err((token, idx)) => {
trace!(
"Service {:?} failed, restarting",
self.factories[idx].name(token)
);
self.state = WorkerState::Restarting(
idx,
token,
self.factories[idx].create(),
);
return self.poll();
}
} }
} }
Poll::Pending self.availability.set(true);
return self.poll();
}
Ok(false) => {
self.state = WorkerState::Unavailable(conns);
return Ok(Async::NotReady);
} }
Err((token, idx)) => { Err((token, idx)) => {
trace!( trace!(
"Service {:?} failed, restarting", "Service {:?} failed, restarting",
self.factories[idx].name(token) self.factories[idx].name(token)
); );
self.services[token.0].status = WorkerServiceStatus::Restarting;
self.state = self.state =
WorkerState::Restarting(idx, token, self.factories[idx].create()); WorkerState::Restarting(idx, token, self.factories[idx].create());
self.poll(cx) return self.poll();
} }
} }
} }
WorkerState::Restarting(idx, token, ref mut fut) => { WorkerState::Restarting(idx, token, mut fut) => {
match Pin::new(fut).poll(cx) { match fut.poll() {
Poll::Ready(Ok(item)) => { Ok(Async::Ready(item)) => {
for (token, service) in item { for (token, service) in item {
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] = Some((idx, service));
self.state = WorkerState::Unavailable(Vec::new()); self.state = WorkerState::Unavailable(Vec::new());
return self.poll(cx);
} }
} }
Poll::Ready(Err(_)) => { Ok(Async::NotReady) => {
self.state = WorkerState::Restarting(idx, token, fut);
return Ok(Async::NotReady);
}
Err(_) => {
panic!( panic!(
"Can not restart {:?} service", "Can not restart {:?} service",
self.factories[idx].name(token) self.factories[idx].name(token)
); );
} }
Poll::Pending => {
return Poll::Pending;
}
} }
self.poll(cx) return self.poll();
} }
WorkerState::Shutdown(ref mut t1, ref mut t2, ref mut tx) => { WorkerState::Shutdown(mut t1, mut t2, tx) => {
let num = num_connections(); let num = num_connections();
if num == 0 { if num == 0 {
let _ = tx.take().unwrap().send(true); let _ = tx.send(true);
Arbiter::current().stop(); Arbiter::current().stop();
return Poll::Ready(()); return Ok(Async::Ready(()));
} }
// check graceful timeout // check graceful timeout
match t2.as_mut().poll(cx) { match t2.poll().unwrap() {
Poll::Pending => (), Async::NotReady => (),
Poll::Ready(_) => { Async::Ready(_) => {
let _ = tx.take().unwrap().send(false);
self.shutdown(true); self.shutdown(true);
let _ = tx.send(false);
Arbiter::current().stop(); Arbiter::current().stop();
return Poll::Ready(()); return Ok(Async::Ready(()));
} }
} }
// sleep for 1 second and then check again // sleep for 1 second and then check again
match t1.as_mut().poll(cx) { match t1.poll().unwrap() {
Poll::Pending => (), Async::NotReady => (),
Poll::Ready(_) => { Async::Ready(_) => {
*t1 = Box::pin(delay_until( t1 = sleep(time::Duration::from_secs(1));
Instant::now() + time::Duration::from_secs(1), let _ = t1.poll();
));
let _ = t1.as_mut().poll(cx);
} }
} }
Poll::Pending self.state = WorkerState::Shutdown(t1, t2, tx);
return Ok(Async::NotReady);
} }
WorkerState::Available => { WorkerState::Available => {
loop { loop {
match Pin::new(&mut self.rx).poll_next(cx) { match self.rx.poll() {
// handle incoming io stream // handle incoming tcp stream
Poll::Ready(Some(WorkerCommand(msg))) => { Ok(Async::Ready(Some(WorkerCommand(msg)))) => {
match self.check_readiness(cx) { match self.check_readiness(false) {
Ok(true) => { Ok(true) => {
let guard = self.conns.get(); let guard = self.conns.get();
let _ = self.services[msg.token.0] let _ = self.services[msg.token.0]
.service .as_mut()
.expect("actix-server bug")
.1
.call((Some(guard), ServerMessage::Connect(msg.io))); .call((Some(guard), ServerMessage::Connect(msg.io)));
continue; continue;
} }
@@ -468,8 +415,6 @@ impl Future for Worker {
self.factories[idx].name(token) self.factories[idx].name(token)
); );
self.availability.set(false); self.availability.set(false);
self.services[token.0].status =
WorkerServiceStatus::Restarting;
self.state = WorkerState::Restarting( self.state = WorkerState::Restarting(
idx, idx,
token, token,
@@ -477,16 +422,17 @@ impl Future for Worker {
); );
} }
} }
return self.poll(cx); return self.poll();
} }
Poll::Pending => { Ok(Async::NotReady) => {
self.state = WorkerState::Available; self.state = WorkerState::Available;
return Poll::Pending; return Ok(Async::NotReady);
} }
Poll::Ready(None) => return Poll::Ready(()), Ok(Async::Ready(None)) | Err(_) => return Ok(Async::Ready(())),
} }
} }
} }
} WorkerState::None => panic!(),
};
} }
} }

View File

@@ -1,18 +1,21 @@
use std::sync::atomic::{AtomicUsize, Ordering::Relaxed}; use std::io::Read;
use std::sync::{mpsc, Arc}; use std::sync::mpsc;
use std::{net, thread, time}; use std::{net, thread, time};
use actix_server::Server; use actix_codec::{BytesCodec, Framed};
use actix_service::fn_service; use actix_server::{Io, Server, ServerConfig};
use futures_util::future::{lazy, ok}; use actix_service::{new_service_cfg, service_fn, IntoService};
use socket2::{Domain, Protocol, Socket, Type}; use bytes::Bytes;
use futures::{Future, Sink};
use net2::TcpBuilder;
use tokio_tcp::TcpStream;
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 = TcpBuilder::new_v4().unwrap();
socket.bind(&addr.into()).unwrap(); socket.bind(&addr).unwrap();
socket.set_reuse_address(true).unwrap(); socket.reuse_address(true).unwrap();
let tcp = socket.into_tcp_listener(); let tcp = socket.to_tcp_listener().unwrap();
tcp.local_addr().unwrap() tcp.local_addr().unwrap()
} }
@@ -24,9 +27,12 @@ fn test_bind() {
let h = thread::spawn(move || { let h = thread::spawn(move || {
let sys = actix_rt::System::new("test"); let sys = actix_rt::System::new("test");
let srv = Server::build() let srv = Server::build()
.workers(1) .bind("test", addr, move || {
.disable_signals() new_service_cfg(move |cfg: &ServerConfig| {
.bind("test", addr, move || fn_service(|_| ok::<_, ()>(()))) assert_eq!(cfg.local_addr(), addr);
Ok::<_, ()>((|_| Ok::<_, ()>(())).into_service())
})
})
.unwrap() .unwrap()
.start(); .start();
let _ = tx.send((srv, actix_rt::System::current())); let _ = tx.send((srv, actix_rt::System::current()));
@@ -36,7 +42,27 @@ fn test_bind() {
thread::sleep(time::Duration::from_millis(500)); thread::sleep(time::Duration::from_millis(500));
assert!(net::TcpStream::connect(addr).is_ok()); assert!(net::TcpStream::connect(addr).is_ok());
sys.stop(); let _ = sys.stop();
let _ = h.join();
}
#[test]
fn test_bind_no_config() {
let addr = unused_addr();
let (tx, rx) = mpsc::channel();
let h = thread::spawn(move || {
let sys = actix_rt::System::new("test");
let srv = Server::build()
.bind("test", addr, move || service_fn(|_| Ok::<_, ()>(())))
.unwrap()
.start();
let _ = tx.send((srv, actix_rt::System::current()));
let _ = sys.run();
});
let (_, sys) = rx.recv().unwrap();
assert!(net::TcpStream::connect(addr).is_ok());
let _ = sys.stop();
let _ = h.join(); let _ = h.join();
} }
@@ -48,45 +74,47 @@ fn test_listen() {
let h = thread::spawn(move || { let h = thread::spawn(move || {
let 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();
Server::build() let srv = Server::build()
.disable_signals() .listen("test", lst, move || {
.workers(1) new_service_cfg(move |cfg: &ServerConfig| {
.listen("test", lst, move || fn_service(|_| ok::<_, ()>(()))) assert_eq!(cfg.local_addr(), addr);
Ok::<_, ()>((|_| Ok::<_, ()>(())).into_service())
})
})
.unwrap() .unwrap()
.start(); .start();
let _ = tx.send(actix_rt::System::current()); let _ = tx.send((srv, actix_rt::System::current()));
let _ = sys.run(); let _ = sys.run();
}); });
let sys = rx.recv().unwrap(); let (_, sys) = rx.recv().unwrap();
thread::sleep(time::Duration::from_millis(500)); thread::sleep(time::Duration::from_millis(500));
assert!(net::TcpStream::connect(addr).is_ok()); assert!(net::TcpStream::connect(addr).is_ok());
sys.stop(); let _ = sys.stop();
let _ = h.join(); let _ = h.join();
} }
#[test] #[test]
#[cfg(unix)] #[cfg(unix)]
fn test_start() { fn test_start() {
use actix_codec::{BytesCodec, Framed};
use actix_rt::net::TcpStream;
use bytes::Bytes;
use futures_util::sink::SinkExt;
use std::io::Read;
let addr = unused_addr(); let addr = unused_addr();
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
let h = thread::spawn(move || { let h = thread::spawn(move || {
let sys = actix_rt::System::new("test"); let sys = actix_rt::System::new("test");
let srv: Server = Server::build() let srv = Server::build()
.backlog(100) .backlog(100)
.disable_signals()
.bind("test", addr, move || { .bind("test", addr, move || {
fn_service(|io: TcpStream| async move { new_service_cfg(move |cfg: &ServerConfig| {
let mut f = Framed::new(io, BytesCodec); assert_eq!(cfg.local_addr(), addr);
f.send(Bytes::from_static(b"test")).await.unwrap(); Ok::<_, ()>(
Ok::<_, ()>(()) (|io: Io<TcpStream>| {
Framed::new(io.into_parts().0, BytesCodec)
.send(Bytes::from_static(b"test"))
.then(|_| Ok::<_, ()>(()))
})
.into_service(),
)
}) })
}) })
.unwrap() .unwrap()
@@ -97,7 +125,7 @@ fn test_start() {
}); });
let (srv, sys) = rx.recv().unwrap(); let (srv, sys) = rx.recv().unwrap();
let mut buf = [1u8; 4]; let mut buf = [0u8; 4];
let mut conn = net::TcpStream::connect(addr).unwrap(); let mut conn = net::TcpStream::connect(addr).unwrap();
let _ = conn.read_exact(&mut buf); let _ = conn.read_exact(&mut buf);
assert_eq!(buf, b"test"[..]); assert_eq!(buf, b"test"[..]);
@@ -129,54 +157,6 @@ fn test_start() {
assert!(net::TcpStream::connect(addr).is_err()); assert!(net::TcpStream::connect(addr).is_err());
thread::sleep(time::Duration::from_millis(100)); thread::sleep(time::Duration::from_millis(100));
sys.stop(); let _ = sys.stop();
let _ = h.join();
}
#[test]
fn test_configure() {
let addr1 = unused_addr();
let addr2 = unused_addr();
let addr3 = unused_addr();
let (tx, rx) = mpsc::channel();
let num = Arc::new(AtomicUsize::new(0));
let num2 = num.clone();
let h = thread::spawn(move || {
let num = num2.clone();
let sys = actix_rt::System::new("test");
let srv = Server::build()
.disable_signals()
.configure(move |cfg| {
let num = num.clone();
let lst = net::TcpListener::bind(addr3).unwrap();
cfg.bind("addr1", addr1)
.unwrap()
.bind("addr2", addr2)
.unwrap()
.listen("addr3", lst)
.apply(move |rt| {
let num = num.clone();
rt.service("addr1", fn_service(|_| ok::<_, ()>(())));
rt.service("addr3", fn_service(|_| ok::<_, ()>(())));
rt.on_start(lazy(move |_| {
let _ = num.fetch_add(1, Relaxed);
}))
})
})
.unwrap()
.workers(1)
.start();
let _ = tx.send((srv, actix_rt::System::current()));
let _ = sys.run();
});
let (_, sys) = rx.recv().unwrap();
thread::sleep(time::Duration::from_millis(500));
assert!(net::TcpStream::connect(addr1).is_ok());
assert!(net::TcpStream::connect(addr2).is_ok());
assert!(net::TcpStream::connect(addr3).is_ok());
assert_eq!(num.load(Relaxed), 1);
sys.stop();
let _ = h.join(); let _ = h.join();
} }

View File

@@ -1,104 +1,5 @@
# Changes # Changes
## Unreleased - 2020-xx-xx
## 1.0.6 - 2020-08-09
### Fixed
* Removed unsound custom Cell implementation that allowed obtaining several mutable references to the same data, which is undefined behavior in Rust and could lead to violations of memory safety. External code could obtain several mutable references to the same data through service combinators. Attempts to acquire several mutable references to the same data will instead result in a panic.
## [1.0.5] - 2020-01-16
### Fixed
* Fixed unsoundness in .and_then()/.then() service combinators
## [1.0.4] - 2020-01-15
### Fixed
* Revert 1.0.3 change
## [1.0.3] - 2020-01-15
### Fixed
* Fixed unsoundness in `AndThenService` impl
## [1.0.2] - 2020-01-08
### Added
* Add `into_service` helper function
## [1.0.1] - 2019-12-22
### Changed
* `map_config()` and `unit_config()` accepts `IntoServiceFactory` type
## [1.0.0] - 2019-12-11
### Added
* Add Clone impl for Apply service
## [1.0.0-alpha.4] - 2019-12-08
### Changed
* Renamed `service_fn` to `fn_service`
* Renamed `factory_fn` to `fn_factory`
* Renamed `factory_fn_cfg` to `fn_factory_with_config`
## [1.0.0-alpha.3] - 2019-12-06
### Changed
* Add missing Clone impls
* Restore `Transform::map_init_err()` combinator
* Restore `Service/Factory::apply_fn()` in form of `Pipeline/Factory::and_then_apply_fn()`
* Optimize service combinators and futures memory layout
## [1.0.0-alpha.2] - 2019-12-02
### Changed
* Use owned config value for service factory
* Renamed BoxedNewService/BoxedService to BoxServiceFactory/BoxService
## [1.0.0-alpha.1] - 2019-11-25
### Changed
* Migraded to `std::future`
* `NewService` renamed to `ServiceFactory`
* Added `pipeline` and `pipeline_factory` function
## [0.4.2] - 2019-08-27
### Fixed
* Check service readiness for `new_apply_cfg` combinator
## [0.4.1] - 2019-06-06 ## [0.4.1] - 2019-06-06
### Added ### Added

View File

@@ -1,33 +1,29 @@
[package] [package]
name = "actix-service" name = "actix-service"
version = "1.0.6" version = "0.4.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Service trait and combinators for representing asynchronous request/response operations." description = "Actix Service"
keywords = ["network", "framework", "async", "futures", "service"] 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"
documentation = "https://docs.rs/actix-service" documentation = "https://docs.rs/actix-service/"
readme = "README.md"
categories = ["network-programming", "asynchronous"] categories = ["network-programming", "asynchronous"]
license = "MIT OR Apache-2.0" license = "MIT/Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
edition = "2018" edition = "2018"
workspace = ".."
[badges]
travis-ci = { repository = "actix/actix-service", branch = "master" }
appveyor = { repository = "actix/actix-net" }
codecov = { repository = "actix/actix-service", branch = "master", service = "github" }
[lib] [lib]
name = "actix_service" name = "actix_service"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
futures-util = "0.3.1" futures = "0.1.25"
pin-project = "0.4.17"
[dev-dependencies] [dev-dependencies]
actix-rt = "1.0.0" actix-rt = "0.2"
criterion = "0.3"
[[bench]]
name = "unsafecell_vs_refcell"
harness = false
[[bench]]
name = "and_then"
harness = false

View File

@@ -1,7 +0,0 @@
# actix-service
> Service trait and combinators for representing asynchronous request/response operations.
See documentation for detailed explanations these components: [https://docs.rs/actix-service](docs).
[docs]: https://docs.rs/actix-service

View File

@@ -1,332 +0,0 @@
use actix_service::boxed::BoxFuture;
use actix_service::IntoService;
use actix_service::Service;
/// Benchmark various implementations of and_then
use criterion::{criterion_main, Criterion};
use futures_util::future::join_all;
use futures_util::future::TryFutureExt;
use std::cell::{RefCell, UnsafeCell};
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
/*
* Test services A,B for AndThen service implementations
*/
async fn svc1(_: ()) -> Result<usize, ()> {
Ok(1)
}
async fn svc2(req: usize) -> Result<usize, ()> {
Ok(req + 1)
}
/*
* AndThenUC - original AndThen service based on UnsafeCell
* Cut down version of actix_service::AndThenService based on actix-service::Cell
*/
struct AndThenUC<A, B>(Rc<UnsafeCell<(A, B)>>);
impl<A, B> AndThenUC<A, B> {
fn new(a: A, b: B) -> Self
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
Self(Rc::new(UnsafeCell::new((a, b))))
}
}
impl<A, B> Clone for AndThenUC<A, B> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<A, B> Service for AndThenUC<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
type Request = A::Request;
type Response = B::Response;
type Error = A::Error;
type Future = AndThenServiceResponse<A, B>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: A::Request) -> Self::Future {
let fut = unsafe { &mut *(*self.0).get() }.0.call(req);
AndThenServiceResponse {
state: State::A(fut, Some(self.0.clone())),
}
}
}
#[pin_project::pin_project]
pub(crate) struct AndThenServiceResponse<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
#[pin]
state: State<A, B>,
}
#[pin_project::pin_project(project = StateProj)]
enum State<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
A(#[pin] A::Future, Option<Rc<UnsafeCell<(A, B)>>>),
B(#[pin] B::Future),
Empty,
}
impl<A, B> Future for AndThenServiceResponse<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
type Output = Result<B::Response, A::Error>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.as_mut().project();
match this.state.as_mut().project() {
StateProj::A(fut, b) => match fut.poll(cx)? {
Poll::Ready(res) => {
let b = b.take().unwrap();
this.state.set(State::Empty); // drop fut A
let fut = unsafe { &mut (*b.get()).1 }.call(res);
this.state.set(State::B(fut));
self.poll(cx)
}
Poll::Pending => Poll::Pending,
},
StateProj::B(fut) => fut.poll(cx).map(|r| {
this.state.set(State::Empty);
r
}),
StateProj::Empty => {
panic!("future must not be polled after it returned `Poll::Ready`")
}
}
}
}
/*
* AndThenRC - AndThen service based on RefCell
*/
struct AndThenRC<A, B>(Rc<RefCell<(A, B)>>);
impl<A, B> AndThenRC<A, B> {
fn new(a: A, b: B) -> Self
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
Self(Rc::new(RefCell::new((a, b))))
}
}
impl<A, B> Clone for AndThenRC<A, B> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<A, B> Service for AndThenRC<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
type Request = A::Request;
type Response = B::Response;
type Error = A::Error;
type Future = AndThenServiceResponseRC<A, B>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: A::Request) -> Self::Future {
let fut = self.0.borrow_mut().0.call(req);
AndThenServiceResponseRC {
state: StateRC::A(fut, Some(self.0.clone())),
}
}
}
#[pin_project::pin_project]
pub(crate) struct AndThenServiceResponseRC<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
#[pin]
state: StateRC<A, B>,
}
#[pin_project::pin_project(project = StateRCProj)]
enum StateRC<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
A(#[pin] A::Future, Option<Rc<RefCell<(A, B)>>>),
B(#[pin] B::Future),
Empty,
}
impl<A, B> Future for AndThenServiceResponseRC<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
type Output = Result<B::Response, A::Error>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.as_mut().project();
match this.state.as_mut().project() {
StateRCProj::A(fut, b) => match fut.poll(cx)? {
Poll::Ready(res) => {
let b = b.take().unwrap();
this.state.set(StateRC::Empty); // drop fut A
let fut = b.borrow_mut().1.call(res);
this.state.set(StateRC::B(fut));
self.poll(cx)
}
Poll::Pending => Poll::Pending,
},
StateRCProj::B(fut) => fut.poll(cx).map(|r| {
this.state.set(StateRC::Empty);
r
}),
StateRCProj::Empty => {
panic!("future must not be polled after it returned `Poll::Ready`")
}
}
}
}
/*
* AndThenRCFuture - AndThen service based on RefCell
* and standard futures::future::and_then combinator in a Box
*/
struct AndThenRCFuture<A, B>(Rc<RefCell<(A, B)>>);
impl<A, B> AndThenRCFuture<A, B> {
fn new(a: A, b: B) -> Self
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
Self(Rc::new(RefCell::new((a, b))))
}
}
impl<A, B> Clone for AndThenRCFuture<A, B> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<A, B> Service for AndThenRCFuture<A, B>
where
A: Service + 'static,
A::Future: 'static,
B: Service<Request = A::Response, Error = A::Error> + 'static,
B::Future: 'static,
{
type Request = A::Request;
type Response = B::Response;
type Error = A::Error;
type Future = BoxFuture<Self::Response, Self::Error>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: A::Request) -> Self::Future {
let fut = self.0.borrow_mut().0.call(req);
let core = self.0.clone();
let fut2 = move |res| (*core).borrow_mut().1.call(res);
Box::pin(fut.and_then(fut2))
}
}
/// Criterion Benchmark for async Service
/// Should be used from within criterion group:
/// ```rust,ignore
/// let mut criterion: ::criterion::Criterion<_> =
/// ::criterion::Criterion::default().configure_from_args();
/// bench_async_service(&mut criterion, ok_service(), "async_service_direct");
/// ```
///
/// Usable for benching Service wrappers:
/// Using minimum service code implementation we first measure
/// time to run minimum service, then measure time with wrapper.
///
/// Sample output
/// async_service_direct time: [1.0908 us 1.1656 us 1.2613 us]
pub fn bench_async_service<S>(c: &mut Criterion, srv: S, name: &str)
where
S: Service<Request = (), Response = usize, Error = ()> + Clone + 'static,
{
let mut rt = actix_rt::System::new("test");
// start benchmark loops
c.bench_function(name, move |b| {
b.iter_custom(|iters| {
let mut srvs: Vec<_> = (1..iters).map(|_| srv.clone()).collect();
// exclude request generation, it appears it takes significant time vs call (3us vs 1us)
let start = std::time::Instant::now();
// benchmark body
rt.block_on(async move { join_all(srvs.iter_mut().map(|srv| srv.call(()))).await });
// check that at least first request succeeded
start.elapsed()
})
});
}
pub fn service_benches() {
let mut criterion: ::criterion::Criterion<_> =
::criterion::Criterion::default().configure_from_args();
bench_async_service(
&mut criterion,
AndThenUC::new(svc1.into_service(), svc2.into_service()),
"AndThen with UnsafeCell",
);
bench_async_service(
&mut criterion,
AndThenRC::new(svc1.into_service(), svc2.into_service()),
"AndThen with RefCell",
);
bench_async_service(
&mut criterion,
AndThenUC::new(svc1.into_service(), svc2.into_service()),
"AndThen with UnsafeCell",
);
bench_async_service(
&mut criterion,
AndThenRC::new(svc1.into_service(), svc2.into_service()),
"AndThen with RefCell",
);
bench_async_service(
&mut criterion,
AndThenRCFuture::new(svc1.into_service(), svc2.into_service()),
"AndThen with RefCell via future::and_then",
);
}
criterion_main!(service_benches);

View File

@@ -1,112 +0,0 @@
use actix_service::Service;
use criterion::{criterion_main, Criterion};
use futures_util::future::join_all;
use futures_util::future::{ok, Ready};
use std::cell::{RefCell, UnsafeCell};
use std::rc::Rc;
use std::task::{Context, Poll};
struct SrvUC(Rc<UnsafeCell<usize>>);
impl Default for SrvUC {
fn default() -> Self {
Self(Rc::new(UnsafeCell::new(0)))
}
}
impl Clone for SrvUC {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl Service for SrvUC {
type Request = ();
type Response = usize;
type Error = ();
type Future = Ready<Result<Self::Response, ()>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, _: ()) -> Self::Future {
unsafe { *(*self.0).get() = *(*self.0).get() + 1 };
ok(unsafe { *self.0.get() })
}
}
struct SrvRC(Rc<RefCell<usize>>);
impl Default for SrvRC {
fn default() -> Self {
Self(Rc::new(RefCell::new(0)))
}
}
impl Clone for SrvRC {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl Service for SrvRC {
type Request = ();
type Response = usize;
type Error = ();
type Future = Ready<Result<Self::Response, ()>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, _: ()) -> Self::Future {
let prev = *self.0.borrow();
*(*self.0).borrow_mut() = prev + 1;
ok(*self.0.borrow())
}
}
/// Criterion Benchmark for async Service
/// Should be used from within criterion group:
/// ```rust,ignore
/// let mut criterion: ::criterion::Criterion<_> =
/// ::criterion::Criterion::default().configure_from_args();
/// bench_async_service(&mut criterion, ok_service(), "async_service_direct");
/// ```
///
/// Usable for benching Service wrappers:
/// Using minimum service code implementation we first measure
/// time to run minimum service, then measure time with wrapper.
///
/// Sample output
/// async_service_direct time: [1.0908 us 1.1656 us 1.2613 us]
pub fn bench_async_service<S>(c: &mut Criterion, srv: S, name: &str)
where
S: Service<Request = (), Response = usize, Error = ()> + Clone + 'static,
{
let mut rt = actix_rt::System::new("test");
// start benchmark loops
c.bench_function(name, move |b| {
b.iter_custom(|iters| {
let mut srvs: Vec<_> = (1..iters).map(|_| srv.clone()).collect();
// exclude request generation, it appears it takes significant time vs call (3us vs 1us)
let start = std::time::Instant::now();
// benchmark body
rt.block_on(async move { join_all(srvs.iter_mut().map(|srv| srv.call(()))).await });
// check that at least first request succeeded
start.elapsed()
})
});
}
pub fn service_benches() {
let mut criterion: ::criterion::Criterion<_> =
::criterion::Criterion::default().configure_from_args();
bench_async_service(&mut criterion, SrvUC::default(), "Service with UnsafeCell");
bench_async_service(&mut criterion, SrvRC::default(), "Service with RefCell");
bench_async_service(&mut criterion, SrvUC::default(), "Service with UnsafeCell");
bench_async_service(&mut criterion, SrvRC::default(), "Service with RefCell");
}
criterion_main!(service_benches);

View File

@@ -1,35 +1,41 @@
use std::cell::RefCell; use futures::{Async, Future, Poll};
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use super::{Service, ServiceFactory}; use super::{IntoNewService, NewService, Service};
use crate::cell::Cell;
/// Service for the `and_then` combinator, chaining a computation onto the end /// Service for the `and_then` combinator, chaining a computation onto the end
/// of another service which completes successfully. /// of another service which completes successfully.
/// ///
/// This is created by the `ServiceExt::and_then` method. /// This is created by the `ServiceExt::and_then` method.
pub(crate) struct AndThenService<A, B>(Rc<RefCell<(A, B)>>); pub struct AndThen<A, B> {
a: A,
b: Cell<B>,
}
impl<A, B> AndThenService<A, B> { impl<A, B> AndThen<A, B> {
/// Create new `AndThen` combinator /// Create new `AndThen` combinator
pub(crate) fn new(a: A, b: B) -> Self pub fn new(a: A, b: B) -> Self
where where
A: Service, A: Service,
B: Service<Request = A::Response, Error = A::Error>, B: Service<Request = A::Response, Error = A::Error>,
{ {
Self(Rc::new(RefCell::new((a, b)))) Self { a, b: Cell::new(b) }
} }
} }
impl<A, B> Clone for AndThenService<A, B> { impl<A, B> Clone for AndThen<A, B>
where
A: Clone,
{
fn clone(&self) -> Self { fn clone(&self) -> Self {
AndThenService(self.0.clone()) AndThen {
a: self.a.clone(),
b: self.b.clone(),
}
} }
} }
impl<A, B> Service for AndThenService<A, B> impl<A, B> Service for AndThen<A, B>
where where
A: Service, A: Service,
B: Service<Request = A::Response, Error = A::Error>, B: Service<Request = A::Response, Error = A::Error>,
@@ -37,117 +43,104 @@ where
type Request = A::Request; type Request = A::Request;
type Response = B::Response; type Response = B::Response;
type Error = A::Error; type Error = A::Error;
type Future = AndThenServiceResponse<A, B>; type Future = AndThenFuture<A, B>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self) -> Poll<(), Self::Error> {
let mut srv = self.0.borrow_mut(); let not_ready = self.a.poll_ready()?.is_not_ready();
let not_ready = !srv.0.poll_ready(cx)?.is_ready(); if self.b.get_mut().poll_ready()?.is_not_ready() || not_ready {
if !srv.1.poll_ready(cx)?.is_ready() || not_ready { Ok(Async::NotReady)
Poll::Pending
} else { } else {
Poll::Ready(Ok(())) Ok(Async::Ready(()))
} }
} }
fn call(&mut self, req: A::Request) -> Self::Future { fn call(&mut self, req: A::Request) -> Self::Future {
AndThenServiceResponse { AndThenFuture::new(self.a.call(req), self.b.clone())
state: State::A(self.0.borrow_mut().0.call(req), Some(self.0.clone())), }
}
pub struct AndThenFuture<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
b: Cell<B>,
fut_b: Option<B::Future>,
fut_a: Option<A::Future>,
}
impl<A, B> AndThenFuture<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
fn new(a: A::Future, b: Cell<B>) -> Self {
AndThenFuture {
b,
fut_a: Some(a),
fut_b: None,
} }
} }
} }
#[pin_project::pin_project] impl<A, B> Future for AndThenFuture<A, B>
pub(crate) struct AndThenServiceResponse<A, B>
where where
A: Service, A: Service,
B: Service<Request = A::Response, Error = A::Error>, B: Service<Request = A::Response, Error = A::Error>,
{ {
#[pin] type Item = B::Response;
state: State<A, B>, type Error = A::Error;
}
#[pin_project::pin_project(project = StateProj)] fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
enum State<A, B> if let Some(ref mut fut) = self.fut_b {
where return fut.poll();
A: Service, }
B: Service<Request = A::Response, Error = A::Error>,
{
A(#[pin] A::Future, Option<Rc<RefCell<(A, B)>>>),
B(#[pin] B::Future),
Empty,
}
impl<A, B> Future for AndThenServiceResponse<A, B> match self.fut_a.as_mut().expect("Bug in actix-service").poll() {
where Ok(Async::Ready(resp)) => {
A: Service, let _ = self.fut_a.take();
B: Service<Request = A::Response, Error = A::Error>, self.fut_b = Some(self.b.get_mut().call(resp));
{ self.poll()
type Output = Result<B::Response, A::Error>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.as_mut().project();
match this.state.as_mut().project() {
StateProj::A(fut, b) => match fut.poll(cx)? {
Poll::Ready(res) => {
let b = b.take().unwrap();
this.state.set(State::Empty); // drop fut A
let fut = b.borrow_mut().1.call(res);
this.state.set(State::B(fut));
self.poll(cx)
}
Poll::Pending => Poll::Pending,
},
StateProj::B(fut) => fut.poll(cx).map(|r| {
this.state.set(State::Empty);
r
}),
StateProj::Empty => {
panic!("future must not be polled after it returned `Poll::Ready`")
} }
Ok(Async::NotReady) => Ok(Async::NotReady),
Err(err) => Err(err),
} }
} }
} }
/// `.and_then()` service factory combinator /// `AndThenNewService` new service combinator
pub(crate) struct AndThenServiceFactory<A, B> pub struct AndThenNewService<A, B>
where where
A: ServiceFactory, A: NewService,
A::Config: Clone, B: NewService,
B: ServiceFactory<
Config = A::Config,
Request = A::Response,
Error = A::Error,
InitError = A::InitError,
>,
{ {
inner: Rc<(A, B)>, a: A,
b: B,
} }
impl<A, B> AndThenServiceFactory<A, B> impl<A, B> AndThenNewService<A, B>
where where
A: ServiceFactory, A: NewService,
A::Config: Clone, B: NewService<
B: ServiceFactory<
Config = A::Config, Config = A::Config,
Request = A::Response, Request = A::Response,
Error = A::Error, Error = A::Error,
InitError = A::InitError, InitError = A::InitError,
>, >,
{ {
/// Create new `AndThenFactory` combinator /// Create new `AndThen` combinator
pub(crate) fn new(a: A, b: B) -> Self { pub fn new<F: IntoNewService<B>>(a: A, f: F) -> Self {
Self { Self {
inner: Rc::new((a, b)), a,
b: f.into_new_service(),
} }
} }
} }
impl<A, B> ServiceFactory for AndThenServiceFactory<A, B> impl<A, B> NewService for AndThenNewService<A, B>
where where
A: ServiceFactory, A: NewService,
A::Config: Clone, B: NewService<
B: ServiceFactory<
Config = A::Config, Config = A::Config,
Request = A::Response, Request = A::Response,
Error = A::Error, Error = A::Error,
@@ -159,59 +152,46 @@ where
type Error = A::Error; type Error = A::Error;
type Config = A::Config; type Config = A::Config;
type Service = AndThenService<A::Service, B::Service>; type Service = AndThen<A::Service, B::Service>;
type InitError = A::InitError; type InitError = A::InitError;
type Future = AndThenServiceFactoryResponse<A, B>; type Future = AndThenNewServiceFuture<A, B>;
fn new_service(&self, cfg: A::Config) -> Self::Future { fn new_service(&self, cfg: &A::Config) -> Self::Future {
let inner = &*self.inner; AndThenNewServiceFuture::new(self.a.new_service(cfg), self.b.new_service(cfg))
AndThenServiceFactoryResponse::new(
inner.0.new_service(cfg.clone()),
inner.1.new_service(cfg),
)
} }
} }
impl<A, B> Clone for AndThenServiceFactory<A, B> impl<A, B> Clone for AndThenNewService<A, B>
where where
A: ServiceFactory, A: NewService + Clone,
A::Config: Clone, B: NewService + Clone,
B: ServiceFactory<
Config = A::Config,
Request = A::Response,
Error = A::Error,
InitError = A::InitError,
>,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
inner: self.inner.clone(), a: self.a.clone(),
b: self.b.clone(),
} }
} }
} }
#[pin_project::pin_project] pub struct AndThenNewServiceFuture<A, B>
pub(crate) struct AndThenServiceFactoryResponse<A, B>
where where
A: ServiceFactory, A: NewService,
B: ServiceFactory<Request = A::Response>, B: NewService<Request = A::Response>,
{ {
#[pin]
fut_a: A::Future,
#[pin]
fut_b: B::Future, fut_b: B::Future,
fut_a: A::Future,
a: Option<A::Service>, a: Option<A::Service>,
b: Option<B::Service>, b: Option<B::Service>,
} }
impl<A, B> AndThenServiceFactoryResponse<A, B> impl<A, B> AndThenNewServiceFuture<A, B>
where where
A: ServiceFactory, A: NewService,
B: ServiceFactory<Request = A::Response>, B: NewService<Request = A::Response>,
{ {
fn new(fut_a: A::Future, fut_b: B::Future) -> Self { fn new(fut_a: A::Future, fut_b: B::Future) -> Self {
AndThenServiceFactoryResponse { AndThenNewServiceFuture {
fut_a, fut_a,
fut_b, fut_b,
a: None, a: None,
@@ -220,58 +200,58 @@ where
} }
} }
impl<A, B> Future for AndThenServiceFactoryResponse<A, B> impl<A, B> Future for AndThenNewServiceFuture<A, B>
where where
A: ServiceFactory, A: NewService,
B: ServiceFactory<Request = A::Response, Error = A::Error, InitError = A::InitError>, B: NewService<Request = A::Response, Error = A::Error, InitError = A::InitError>,
{ {
type Output = Result<AndThenService<A::Service, B::Service>, A::InitError>; type Item = AndThen<A::Service, B::Service>;
type Error = A::InitError;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let this = self.project(); if self.a.is_none() {
if let Async::Ready(service) = self.fut_a.poll()? {
if this.a.is_none() { self.a = Some(service);
if let Poll::Ready(service) = this.fut_a.poll(cx)? {
*this.a = Some(service);
} }
} }
if this.b.is_none() {
if let Poll::Ready(service) = this.fut_b.poll(cx)? { if self.b.is_none() {
*this.b = Some(service); if let Async::Ready(service) = self.fut_b.poll()? {
self.b = Some(service);
} }
} }
if this.a.is_some() && this.b.is_some() {
Poll::Ready(Ok(AndThenService::new( if self.a.is_some() && self.b.is_some() {
this.a.take().unwrap(), Ok(Async::Ready(AndThen::new(
this.b.take().unwrap(), self.a.take().unwrap(),
self.b.take().unwrap(),
))) )))
} else { } else {
Poll::Pending Ok(Async::NotReady)
} }
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use futures::future::{ok, FutureResult};
use futures::{Async, Poll};
use std::cell::Cell; use std::cell::Cell;
use std::rc::Rc; use std::rc::Rc;
use std::task::{Context, Poll};
use futures_util::future::{lazy, ok, ready, Ready}; use super::*;
use crate::{NewService, Service, ServiceExt};
use crate::{fn_factory, pipeline, pipeline_factory, Service, ServiceFactory};
struct Srv1(Rc<Cell<usize>>); struct Srv1(Rc<Cell<usize>>);
impl Service for Srv1 { impl Service for Srv1 {
type Request = &'static str; type Request = &'static str;
type Response = &'static str; type Response = &'static str;
type Error = (); type Error = ();
type Future = Ready<Result<Self::Response, ()>>; type Future = FutureResult<Self::Response, ()>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.0.set(self.0.get() + 1); self.0.set(self.0.get() + 1);
Poll::Ready(Ok(())) Ok(Async::Ready(()))
} }
fn call(&mut self, req: &'static str) -> Self::Future { fn call(&mut self, req: &'static str) -> Self::Future {
@@ -286,11 +266,11 @@ mod tests {
type Request = &'static str; type Request = &'static str;
type Response = (&'static str, &'static str); type Response = (&'static str, &'static str);
type Error = (); type Error = ();
type Future = Ready<Result<Self::Response, ()>>; type Future = FutureResult<Self::Response, ()>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.0.set(self.0.get() + 1); self.0.set(self.0.get() + 1);
Poll::Ready(Ok(())) Ok(Async::Ready(()))
} }
fn call(&mut self, req: &'static str) -> Self::Future { fn call(&mut self, req: &'static str) -> Self::Future {
@@ -298,35 +278,39 @@ mod tests {
} }
} }
#[actix_rt::test] #[test]
async fn test_poll_ready() { fn test_poll_ready() {
let cnt = Rc::new(Cell::new(0)); let cnt = Rc::new(Cell::new(0));
let mut srv = pipeline(Srv1(cnt.clone())).and_then(Srv2(cnt.clone())); let mut srv = Srv1(cnt.clone()).and_then(Srv2(cnt.clone()));
let res = lazy(|cx| srv.poll_ready(cx)).await; let res = srv.poll_ready();
assert_eq!(res, Poll::Ready(Ok(()))); assert!(res.is_ok());
assert_eq!(res.unwrap(), Async::Ready(()));
assert_eq!(cnt.get(), 2); assert_eq!(cnt.get(), 2);
} }
#[actix_rt::test] #[test]
async fn test_call() { fn test_call() {
let cnt = Rc::new(Cell::new(0)); let cnt = Rc::new(Cell::new(0));
let mut srv = pipeline(Srv1(cnt.clone())).and_then(Srv2(cnt)); let mut srv = Srv1(cnt.clone()).and_then(Srv2(cnt));
let res = srv.call("srv1").await; let res = srv.call("srv1").poll();
assert!(res.is_ok()); assert!(res.is_ok());
assert_eq!(res.unwrap(), ("srv1", "srv2")); assert_eq!(res.unwrap(), Async::Ready(("srv1", "srv2")));
} }
#[actix_rt::test] #[test]
async fn test_new_service() { fn test_new_service() {
let cnt = Rc::new(Cell::new(0)); let cnt = Rc::new(Cell::new(0));
let cnt2 = cnt.clone(); let cnt2 = cnt.clone();
let new_srv = let blank = move || Ok::<_, ()>(Srv1(cnt2.clone()));
pipeline_factory(fn_factory(move || ready(Ok::<_, ()>(Srv1(cnt2.clone()))))) let new_srv = blank
.and_then(move || ready(Ok(Srv2(cnt.clone())))); .into_new_service()
.and_then(move || Ok(Srv2(cnt.clone())));
let mut srv = new_srv.new_service(()).await.unwrap(); if let Async::Ready(mut srv) = new_srv.new_service(&()).poll().unwrap() {
let res = srv.call("srv1").await; let res = srv.call("srv1").poll();
assert!(res.is_ok()); assert!(res.is_ok());
assert_eq!(res.unwrap(), ("srv1", "srv2")); assert_eq!(res.unwrap(), Async::Ready(("srv1", "srv2")));
} else {
panic!()
}
} }
} }

View File

@@ -0,0 +1,186 @@
use std::rc::Rc;
use futures::{Async, Future, Poll};
use crate::and_then::AndThen;
use crate::from_err::FromErr;
use crate::{NewService, Transform};
/// `Apply` new service combinator
pub struct AndThenTransform<T, A, B> {
a: A,
b: B,
t: Rc<T>,
}
impl<T, A, B> AndThenTransform<T, A, B>
where
A: NewService,
B: NewService<Config = A::Config, InitError = A::InitError>,
T: Transform<B::Service, Request = A::Response, InitError = A::InitError>,
T::Error: From<A::Error>,
{
/// Create new `ApplyNewService` new service instance
pub fn new(t: T, a: A, b: B) -> Self {
Self {
a,
b,
t: Rc::new(t),
}
}
}
impl<T, A, B> Clone for AndThenTransform<T, A, B>
where
A: Clone,
B: Clone,
{
fn clone(&self) -> Self {
Self {
a: self.a.clone(),
b: self.b.clone(),
t: self.t.clone(),
}
}
}
impl<T, A, B> NewService for AndThenTransform<T, A, B>
where
A: NewService,
B: NewService<Config = A::Config, InitError = A::InitError>,
T: Transform<B::Service, Request = A::Response, InitError = A::InitError>,
T::Error: From<A::Error>,
{
type Request = A::Request;
type Response = T::Response;
type Error = T::Error;
type Config = A::Config;
type InitError = T::InitError;
type Service = AndThen<FromErr<A::Service, T::Error>, T::Transform>;
type Future = AndThenTransformFuture<T, A, B>;
fn new_service(&self, cfg: &A::Config) -> Self::Future {
AndThenTransformFuture {
a: None,
t: None,
t_cell: self.t.clone(),
fut_a: self.a.new_service(cfg),
fut_b: self.b.new_service(cfg),
fut_t: None,
}
}
}
pub struct AndThenTransformFuture<T, A, B>
where
A: NewService,
B: NewService<InitError = A::InitError>,
T: Transform<B::Service, Request = A::Response, InitError = A::InitError>,
T::Error: From<A::Error>,
{
fut_a: A::Future,
fut_b: B::Future,
fut_t: Option<T::Future>,
a: Option<A::Service>,
t: Option<T::Transform>,
t_cell: Rc<T>,
}
impl<T, A, B> Future for AndThenTransformFuture<T, A, B>
where
A: NewService,
B: NewService<InitError = A::InitError>,
T: Transform<B::Service, Request = A::Response, InitError = A::InitError>,
T::Error: From<A::Error>,
{
type Item = AndThen<FromErr<A::Service, T::Error>, T::Transform>;
type Error = T::InitError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if self.fut_t.is_none() {
if let Async::Ready(service) = self.fut_b.poll()? {
self.fut_t = Some(self.t_cell.new_transform(service));
}
}
if self.a.is_none() {
if let Async::Ready(service) = self.fut_a.poll()? {
self.a = Some(service);
}
}
if let Some(ref mut fut) = self.fut_t {
if let Async::Ready(transform) = fut.poll()? {
self.t = Some(transform);
}
}
if self.a.is_some() && self.t.is_some() {
Ok(Async::Ready(AndThen::new(
FromErr::new(self.a.take().unwrap()),
self.t.take().unwrap(),
)))
} else {
Ok(Async::NotReady)
}
}
}
#[cfg(test)]
mod tests {
use futures::future::{ok, FutureResult};
use futures::{Async, Future, Poll};
use crate::{IntoNewService, IntoService, NewService, Service, ServiceExt};
#[derive(Clone)]
struct Srv;
impl Service for Srv {
type Request = ();
type Response = ();
type Error = ();
type Future = FutureResult<(), ()>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Ok(Async::Ready(()))
}
fn call(&mut self, _: ()) -> Self::Future {
ok(())
}
}
#[test]
fn test_apply() {
let blank = |req| Ok(req);
let mut srv = blank
.into_service()
.apply_fn(Srv, |req: &'static str, srv: &mut Srv| {
srv.call(()).map(move |res| (req, res))
});
assert!(srv.poll_ready().is_ok());
let res = srv.call("srv").poll();
assert!(res.is_ok());
assert_eq!(res.unwrap(), Async::Ready(("srv", ())));
}
#[test]
fn test_new_service() {
let blank = || Ok::<_, ()>((|req| Ok(req)).into_service());
let new_srv = blank.into_new_service().apply(
|req: &'static str, srv: &mut Srv| srv.call(()).map(move |res| (req, res)),
|| Ok(Srv),
);
if let Async::Ready(mut srv) = new_srv.new_service(&()).poll().unwrap() {
assert!(srv.poll_ready().is_ok());
let res = srv.call("srv").poll();
assert!(res.is_ok());
assert_eq!(res.unwrap(), Async::Ready(("srv", ())));
} else {
panic!()
}
}
}

View File

@@ -1,282 +1,265 @@
use std::cell::RefCell;
use std::future::Future;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use crate::{Service, ServiceFactory}; use futures::{Async, Future, IntoFuture, Poll};
use super::{IntoNewService, IntoService, NewService, Service};
use crate::cell::Cell;
/// `Apply` service combinator /// `Apply` service combinator
pub(crate) struct AndThenApplyFn<A, B, F, Fut, Res, Err> pub struct AndThenApply<A, B, F, Out>
where where
A: Service, A: Service,
B: Service, B: Service<Error = A::Error>,
F: FnMut(A::Response, &mut B) -> Fut, F: FnMut(A::Response, &mut B) -> Out,
Fut: Future<Output = Result<Res, Err>>, Out: IntoFuture,
Err: From<A::Error> + From<B::Error>, Out::Error: Into<A::Error>,
{ {
srv: Rc<RefCell<(A, B, F)>>, a: A,
r: PhantomData<(Fut, Res, Err)>, b: Cell<B>,
f: Cell<F>,
r: PhantomData<(Out,)>,
} }
impl<A, B, F, Fut, Res, Err> AndThenApplyFn<A, B, F, Fut, Res, Err> impl<A, B, F, Out> AndThenApply<A, B, F, Out>
where where
A: Service, A: Service,
B: Service, B: Service<Error = A::Error>,
F: FnMut(A::Response, &mut B) -> Fut, F: FnMut(A::Response, &mut B) -> Out,
Fut: Future<Output = Result<Res, Err>>, Out: IntoFuture,
Err: From<A::Error> + From<B::Error>, Out::Error: Into<A::Error>,
{ {
/// Create new `Apply` combinator /// Create new `Apply` combinator
pub(crate) fn new(a: A, b: B, f: F) -> Self { pub fn new<A1: IntoService<A>, B1: IntoService<B>>(a: A1, b: B1, f: F) -> Self {
Self { Self {
srv: Rc::new(RefCell::new((a, b, f))), f: Cell::new(f),
a: a.into_service(),
b: Cell::new(b.into_service()),
r: PhantomData, r: PhantomData,
} }
} }
} }
impl<A, B, F, Fut, Res, Err> Clone for AndThenApplyFn<A, B, F, Fut, Res, Err> impl<A, B, F, Out> Clone for AndThenApply<A, B, F, Out>
where where
A: Service, A: Service + Clone,
B: Service, B: Service<Error = A::Error>,
F: FnMut(A::Response, &mut B) -> Fut, F: FnMut(A::Response, &mut B) -> Out,
Fut: Future<Output = Result<Res, Err>>, Out: IntoFuture,
Err: From<A::Error> + From<B::Error>, Out::Error: Into<A::Error>,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
AndThenApplyFn { AndThenApply {
srv: self.srv.clone(), a: self.a.clone(),
b: self.b.clone(),
f: self.f.clone(),
r: PhantomData, r: PhantomData,
} }
} }
} }
impl<A, B, F, Fut, Res, Err> Service for AndThenApplyFn<A, B, F, Fut, Res, Err> impl<A, B, F, Out> Service for AndThenApply<A, B, F, Out>
where where
A: Service, A: Service,
B: Service, B: Service<Error = A::Error>,
F: FnMut(A::Response, &mut B) -> Fut, F: FnMut(A::Response, &mut B) -> Out,
Fut: Future<Output = Result<Res, Err>>, Out: IntoFuture,
Err: From<A::Error> + From<B::Error>, Out::Error: Into<A::Error>,
{ {
type Request = A::Request; type Request = A::Request;
type Response = Res; type Response = Out::Item;
type Error = Err; type Error = A::Error;
type Future = AndThenApplyFnFuture<A, B, F, Fut, Res, Err>; type Future = AndThenApplyFuture<A, B, F, Out>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self) -> Poll<(), Self::Error> {
let mut inner = self.srv.borrow_mut(); let not_ready = self.a.poll_ready()?.is_not_ready();
let not_ready = inner.0.poll_ready(cx)?.is_pending(); if self.b.get_mut().poll_ready()?.is_not_ready() || not_ready {
if inner.1.poll_ready(cx)?.is_pending() || not_ready { Ok(Async::NotReady)
Poll::Pending
} else { } else {
Poll::Ready(Ok(())) Ok(Async::Ready(()))
} }
} }
fn call(&mut self, req: A::Request) -> Self::Future { fn call(&mut self, req: A::Request) -> Self::Future {
let fut = self.srv.borrow_mut().0.call(req); AndThenApplyFuture {
AndThenApplyFnFuture { b: self.b.clone(),
state: State::A(fut, Some(self.srv.clone())), f: self.f.clone(),
fut_b: None,
fut_a: Some(self.a.call(req)),
} }
} }
} }
#[pin_project::pin_project] pub struct AndThenApplyFuture<A, B, F, Out>
pub(crate) struct AndThenApplyFnFuture<A, B, F, Fut, Res, Err>
where where
A: Service, A: Service,
B: Service, B: Service<Error = A::Error>,
F: FnMut(A::Response, &mut B) -> Fut, F: FnMut(A::Response, &mut B) -> Out,
Fut: Future<Output = Result<Res, Err>>, Out: IntoFuture,
Err: From<A::Error>, Out::Error: Into<A::Error>,
Err: From<B::Error>,
{ {
#[pin] b: Cell<B>,
state: State<A, B, F, Fut, Res, Err>, f: Cell<F>,
fut_a: Option<A::Future>,
fut_b: Option<Out::Future>,
} }
#[pin_project::pin_project(project = StateProj)] impl<A, B, F, Out> Future for AndThenApplyFuture<A, B, F, Out>
enum State<A, B, F, Fut, Res, Err>
where where
A: Service, A: Service,
B: Service, B: Service<Error = A::Error>,
F: FnMut(A::Response, &mut B) -> Fut, F: FnMut(A::Response, &mut B) -> Out,
Fut: Future<Output = Result<Res, Err>>, Out: IntoFuture,
Err: From<A::Error>, Out::Error: Into<A::Error>,
Err: From<B::Error>,
{ {
A(#[pin] A::Future, Option<Rc<RefCell<(A, B, F)>>>), type Item = Out::Item;
B(#[pin] Fut), type Error = A::Error;
Empty,
}
impl<A, B, F, Fut, Res, Err> Future for AndThenApplyFnFuture<A, B, F, Fut, Res, Err> fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
where if let Some(ref mut fut) = self.fut_b {
A: Service, return fut.poll().map_err(|e| e.into());
B: Service, }
F: FnMut(A::Response, &mut B) -> Fut,
Fut: Future<Output = Result<Res, Err>>,
Err: From<A::Error> + From<B::Error>,
{
type Output = Result<Res, Err>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { match self.fut_a.as_mut().expect("Bug in actix-service").poll() {
let mut this = self.as_mut().project(); Ok(Async::Ready(resp)) => {
let _ = self.fut_a.take();
match this.state.as_mut().project() { self.fut_b =
StateProj::A(fut, b) => match fut.poll(cx)? { Some((&mut *self.f.get_mut())(resp, self.b.get_mut()).into_future());
Poll::Ready(res) => { self.poll()
let b = b.take().unwrap();
this.state.set(State::Empty);
let (_, b, f) = &mut *b.borrow_mut();
let fut = f(res, b);
this.state.set(State::B(fut));
self.poll(cx)
}
Poll::Pending => Poll::Pending,
},
StateProj::B(fut) => fut.poll(cx).map(|r| {
this.state.set(State::Empty);
r
}),
StateProj::Empty => {
panic!("future must not be polled after it returned `Poll::Ready`")
} }
Ok(Async::NotReady) => Ok(Async::NotReady),
Err(err) => Err(err),
} }
} }
} }
/// `AndThenApplyFn` service factory /// `ApplyNewService` new service combinator
pub(crate) struct AndThenApplyFnFactory<A, B, F, Fut, Res, Err> { pub struct AndThenApplyNewService<A, B, F, Out> {
srv: Rc<(A, B, F)>, a: A,
r: PhantomData<(Fut, Res, Err)>, b: B,
f: Cell<F>,
r: PhantomData<Out>,
} }
impl<A, B, F, Fut, Res, Err> AndThenApplyFnFactory<A, B, F, Fut, Res, Err> impl<A, B, F, Out> AndThenApplyNewService<A, B, F, Out>
where where
A: ServiceFactory, A: NewService,
B: ServiceFactory<Config = A::Config, InitError = A::InitError>, B: NewService<Config = A::Config, Error = A::Error, InitError = A::InitError>,
F: FnMut(A::Response, &mut B::Service) -> Fut + Clone, F: FnMut(A::Response, &mut B::Service) -> Out,
Fut: Future<Output = Result<Res, Err>>, Out: IntoFuture,
Err: From<A::Error> + From<B::Error>, Out::Error: Into<A::Error>,
{ {
/// Create new `ApplyNewService` new service instance /// Create new `ApplyNewService` new service instance
pub(crate) fn new(a: A, b: B, f: F) -> Self { pub fn new<A1: IntoNewService<A>, B1: IntoNewService<B>>(a: A1, b: B1, f: F) -> Self {
Self { Self {
srv: Rc::new((a, b, f)), f: Cell::new(f),
a: a.into_new_service(),
b: b.into_new_service(),
r: PhantomData, r: PhantomData,
} }
} }
} }
impl<A, B, F, Fut, Res, Err> Clone for AndThenApplyFnFactory<A, B, F, Fut, Res, Err> { impl<A, B, F, Out> Clone for AndThenApplyNewService<A, B, F, Out>
where
A: Clone,
B: Clone,
{
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
srv: self.srv.clone(), a: self.a.clone(),
b: self.b.clone(),
f: self.f.clone(),
r: PhantomData, r: PhantomData,
} }
} }
} }
impl<A, B, F, Fut, Res, Err> ServiceFactory for AndThenApplyFnFactory<A, B, F, Fut, Res, Err> impl<A, B, F, Out> NewService for AndThenApplyNewService<A, B, F, Out>
where where
A: ServiceFactory, A: NewService,
A::Config: Clone, B: NewService<Config = A::Config, Error = A::Error, InitError = A::InitError>,
B: ServiceFactory<Config = A::Config, InitError = A::InitError>, F: FnMut(A::Response, &mut B::Service) -> Out,
F: FnMut(A::Response, &mut B::Service) -> Fut + Clone, Out: IntoFuture,
Fut: Future<Output = Result<Res, Err>>, Out::Error: Into<A::Error>,
Err: From<A::Error> + From<B::Error>,
{ {
type Request = A::Request; type Request = A::Request;
type Response = Res; type Response = Out::Item;
type Error = Err; type Error = A::Error;
type Service = AndThenApplyFn<A::Service, B::Service, F, Fut, Res, Err>; type Service = AndThenApply<A::Service, B::Service, F, Out>;
type Config = A::Config; type Config = A::Config;
type InitError = A::InitError; type InitError = A::InitError;
type Future = AndThenApplyFnFactoryResponse<A, B, F, Fut, Res, Err>; type Future = AndThenApplyNewServiceFuture<A, B, F, Out>;
fn new_service(&self, cfg: A::Config) -> Self::Future { fn new_service(&self, cfg: &A::Config) -> Self::Future {
let srv = &*self.srv; AndThenApplyNewServiceFuture {
AndThenApplyFnFactoryResponse {
a: None, a: None,
b: None, b: None,
f: srv.2.clone(), f: self.f.clone(),
fut_a: srv.0.new_service(cfg.clone()), fut_a: self.a.new_service(cfg).into_future(),
fut_b: srv.1.new_service(cfg), fut_b: self.b.new_service(cfg).into_future(),
} }
} }
} }
#[pin_project::pin_project] pub struct AndThenApplyNewServiceFuture<A, B, F, Out>
pub(crate) struct AndThenApplyFnFactoryResponse<A, B, F, Fut, Res, Err>
where where
A: ServiceFactory, A: NewService,
B: ServiceFactory<Config = A::Config, InitError = A::InitError>, B: NewService<Error = A::Error, InitError = A::InitError>,
F: FnMut(A::Response, &mut B::Service) -> Fut + Clone, F: FnMut(A::Response, &mut B::Service) -> Out,
Fut: Future<Output = Result<Res, Err>>, Out: IntoFuture,
Err: From<A::Error>, Out::Error: Into<A::Error>,
Err: From<B::Error>,
{ {
#[pin]
fut_b: B::Future, fut_b: B::Future,
#[pin]
fut_a: A::Future, fut_a: A::Future,
f: F, f: Cell<F>,
a: Option<A::Service>, a: Option<A::Service>,
b: Option<B::Service>, b: Option<B::Service>,
} }
impl<A, B, F, Fut, Res, Err> Future for AndThenApplyFnFactoryResponse<A, B, F, Fut, Res, Err> impl<A, B, F, Out> Future for AndThenApplyNewServiceFuture<A, B, F, Out>
where where
A: ServiceFactory, A: NewService,
B: ServiceFactory<Config = A::Config, InitError = A::InitError>, B: NewService<Error = A::Error, InitError = A::InitError>,
F: FnMut(A::Response, &mut B::Service) -> Fut + Clone, F: FnMut(A::Response, &mut B::Service) -> Out,
Fut: Future<Output = Result<Res, Err>>, Out: IntoFuture,
Err: From<A::Error> + From<B::Error>, Out::Error: Into<A::Error>,
{ {
type Output = type Item = AndThenApply<A::Service, B::Service, F, Out>;
Result<AndThenApplyFn<A::Service, B::Service, F, Fut, Res, Err>, A::InitError>; type Error = A::InitError;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let this = self.project(); if self.a.is_none() {
if let Async::Ready(service) = self.fut_a.poll()? {
if this.a.is_none() { self.a = Some(service);
if let Poll::Ready(service) = this.fut_a.poll(cx)? {
*this.a = Some(service);
} }
} }
if this.b.is_none() { if self.b.is_none() {
if let Poll::Ready(service) = this.fut_b.poll(cx)? { if let Async::Ready(service) = self.fut_b.poll()? {
*this.b = Some(service); self.b = Some(service);
} }
} }
if this.a.is_some() && this.b.is_some() { if self.a.is_some() && self.b.is_some() {
Poll::Ready(Ok(AndThenApplyFn { Ok(Async::Ready(AndThenApply {
srv: Rc::new(RefCell::new(( f: self.f.clone(),
this.a.take().unwrap(), a: self.a.take().unwrap(),
this.b.take().unwrap(), b: Cell::new(self.b.take().unwrap()),
this.f.clone(),
))),
r: PhantomData, r: PhantomData,
})) }))
} else { } else {
Poll::Pending Ok(Async::NotReady)
} }
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use futures::future::{ok, FutureResult};
use futures::{Async, Future, Poll};
use futures_util::future::{lazy, ok, Ready, TryFutureExt}; use crate::blank::{Blank, BlankNewService};
use crate::{NewService, Service, ServiceExt};
use crate::{fn_service, pipeline, pipeline_factory, Service, ServiceFactory};
#[derive(Clone)] #[derive(Clone)]
struct Srv; struct Srv;
@@ -284,43 +267,41 @@ mod tests {
type Request = (); type Request = ();
type Response = (); type Response = ();
type Error = (); type Error = ();
type Future = Ready<Result<(), ()>>; type Future = FutureResult<(), ()>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Poll::Ready(Ok(())) Ok(Async::Ready(()))
} }
#[allow(clippy::unit_arg)] fn call(&mut self, _: ()) -> Self::Future {
fn call(&mut self, req: Self::Request) -> Self::Future { ok(())
ok(req)
} }
} }
#[actix_rt::test] #[test]
async fn test_service() { fn test_call() {
let mut srv = pipeline(ok).and_then_apply_fn(Srv, |req: &'static str, s| { let mut srv = Blank::new().apply_fn(Srv, |req: &'static str, srv| {
s.call(()).map_ok(move |res| (req, res)) srv.call(()).map(move |res| (req, res))
}); });
let res = lazy(|cx| srv.poll_ready(cx)).await; assert!(srv.poll_ready().is_ok());
assert_eq!(res, Poll::Ready(Ok(()))); let res = srv.call("srv").poll();
let res = srv.call("srv").await;
assert!(res.is_ok()); assert!(res.is_ok());
assert_eq!(res.unwrap(), ("srv", ())); assert_eq!(res.unwrap(), Async::Ready(("srv", ())));
} }
#[actix_rt::test] #[test]
async fn test_service_factory() { fn test_new_service() {
let new_srv = pipeline_factory(|| ok::<_, ()>(fn_service(ok))).and_then_apply_fn( let new_srv = BlankNewService::new_unit().apply_fn(
|| ok(Srv), || Ok(Srv),
|req: &'static str, s| s.call(()).map_ok(move |res| (req, res)), |req: &'static str, srv| srv.call(()).map(move |res| (req, res)),
); );
let mut srv = new_srv.new_service(()).await.unwrap(); if let Async::Ready(mut srv) = new_srv.new_service(&()).poll().unwrap() {
let res = lazy(|cx| srv.poll_ready(cx)).await; assert!(srv.poll_ready().is_ok());
assert_eq!(res, Poll::Ready(Ok(()))); let res = srv.call("srv").poll();
assert!(res.is_ok());
let res = srv.call("srv").await; assert_eq!(res.unwrap(), Async::Ready(("srv", ())));
assert!(res.is_ok()); } else {
assert_eq!(res.unwrap(), ("srv", ())); panic!()
}
} }
} }

Some files were not shown because too many files have changed in this diff Show More