mirror of
https://github.com/fafhrd91/actix-net
synced 2025-08-15 06:45:56 +02:00
Compare commits
1 Commits
utils-v3.0
...
v0.2.5
Author | SHA1 | Date | |
---|---|---|---|
|
298727dcbd |
41
.appveyor.yml
Normal file
41
.appveyor.yml
Normal 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
|
24
.github/PULL_REQUEST_TEMPLATE.md
vendored
24
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -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 -->
|
34
.github/workflows/clippy-fmt.yml
vendored
34
.github/workflows/clippy-fmt.yml
vendored
@@ -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: --workspace --tests
|
82
.github/workflows/linux.yml
vendored
82
.github/workflows/linux.yml
vendored
@@ -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.46.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: --workspace --bins --examples --tests
|
||||
|
||||
- name: tests
|
||||
uses: actions-rs/cargo@v1
|
||||
timeout-minutes: 40
|
||||
with:
|
||||
command: test
|
||||
args: --workspace --exclude=actix-tls --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
|
||||
|
||||
- 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
|
43
.github/workflows/macos.yml
vendored
43
.github/workflows/macos.yml
vendored
@@ -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: --workspace --bins --examples --tests
|
||||
|
||||
- name: tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --workspace --exclude=actix-tls --no-fail-fast -- --nocapture
|
45
.github/workflows/windows-mingw.yml
vendored
45
.github/workflows/windows-mingw.yml
vendored
@@ -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: --workspace --bins --examples --tests
|
69
.github/workflows/windows.yml
vendored
69
.github/workflows/windows.yml
vendored
@@ -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: --workspace --bins --examples --tests
|
||||
|
||||
- name: tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --workspace --exclude=actix-tls --no-fail-fast -- --nocapture
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -12,5 +12,3 @@ guide/build/
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
.idea
|
||||
|
54
.travis.yml
Normal file
54
.travis.yml
Normal file
@@ -0,0 +1,54 @@
|
||||
language: rust
|
||||
sudo: required
|
||||
dist: trusty
|
||||
|
||||
cache:
|
||||
cargo: true
|
||||
apt: true
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- rust: stable
|
||||
- rust: beta
|
||||
- rust: nightly
|
||||
allow_failures:
|
||||
- rust: nightly
|
||||
|
||||
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
|
||||
|
||||
# Add clippy
|
||||
before_script:
|
||||
- export PATH=$PATH:~/.cargo/bin
|
||||
|
||||
script:
|
||||
- |
|
||||
if [[ "$TRAVIS_RUST_VERSION" != "nightly" ]]; then
|
||||
cargo clean
|
||||
cargo test --features="ssl,tls,rust-tls" -- --nocapture
|
||||
fi
|
||||
- |
|
||||
if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then
|
||||
RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install -f cargo-tarpaulin
|
||||
cargo tarpaulin --features="ssl,tls,rust-tls" --out Xml
|
||||
bash <(curl -s https://codecov.io/bash)
|
||||
echo "Uploaded code coverage"
|
||||
fi
|
||||
|
||||
# Upload docs
|
||||
after_success:
|
||||
- |
|
||||
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "beta" ]]; then
|
||||
cargo doc --features "ssl,tls,rust-tls" --no-deps &&
|
||||
echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html &&
|
||||
git clone https://github.com/davisp/ghp-import.git &&
|
||||
./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc &&
|
||||
echo "Uploaded documentation"
|
||||
fi
|
70
CHANGES.md
Normal file
70
CHANGES.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# Changes
|
||||
|
||||
## [0.2.5] - 2018-12-12
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix back-pressure for concurrent ssl handshakes
|
||||
|
||||
* Drop completed future for .then and .and_then combinators
|
||||
|
||||
|
||||
## [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
|
@@ -34,13 +34,10 @@ This Code of Conduct applies both within project spaces and in public spaces whe
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at robjtede@icloud.com ([@robjtede]) or huyuumi@neet.club ([@JohnTitor]). The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at fafhrd91@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
[@robjtede]: https://github.com/robjtede
|
||||
[@JohnTitor]: https://github.com/JohnTitor
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
112
Cargo.toml
112
Cargo.toml
@@ -1,31 +1,83 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"actix-codec",
|
||||
"actix-connect",
|
||||
"actix-rt",
|
||||
"actix-macros",
|
||||
"actix-service",
|
||||
"actix-server",
|
||||
"actix-testing",
|
||||
"actix-threadpool",
|
||||
"actix-tls",
|
||||
"actix-tracing",
|
||||
"actix-utils",
|
||||
"router",
|
||||
"string",
|
||||
]
|
||||
[package]
|
||||
name = "actix-net"
|
||||
version = "0.2.5"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix net - framework for the compisible network services for Rust (experimental)"
|
||||
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"]
|
||||
|
||||
[patch.crates-io]
|
||||
actix-codec = { git = "https://github.com/actix/actix-net.git", rev = "ba44ea7d0bafaf5fccb9a34003d503e1910943ee" }
|
||||
actix-connect = { path = "actix-connect" }
|
||||
actix-rt = { git = "https://github.com/actix/actix-net.git", rev = "ba44ea7d0bafaf5fccb9a34003d503e1910943ee" }
|
||||
actix-macros = { path = "actix-macros" }
|
||||
actix-server = { path = "actix-server" }
|
||||
actix-service = { git = "https://github.com/actix/actix-net.git", rev = "ba44ea7d0bafaf5fccb9a34003d503e1910943ee" }
|
||||
actix-testing = { path = "actix-testing" }
|
||||
actix-threadpool = { path = "actix-threadpool" }
|
||||
actix-tls = { path = "actix-tls" }
|
||||
actix-tracing = { path = "actix-tracing" }
|
||||
actix-utils = { git = "https://github.com/actix/actix-net.git", rev = "ba44ea7d0bafaf5fccb9a34003d503e1910943ee" }
|
||||
actix-router = { path = "router" }
|
||||
bytestring = { path = "string" }
|
||||
[package.metadata.docs.rs]
|
||||
features = ["ssl", "tls", "rust-tls"]
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "actix/actix-net", branch = "master" }
|
||||
# appveyor = { repository = "fafhrd91/actix-web-hdy9d" }
|
||||
codecov = { repository = "actix/actix-net", branch = "master", service = "github" }
|
||||
|
||||
[lib]
|
||||
name = "actix_net"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
# tls
|
||||
tls = ["native-tls"]
|
||||
|
||||
# openssl
|
||||
ssl = ["openssl", "tokio-openssl"]
|
||||
|
||||
# rustls
|
||||
rust-tls = ["rustls", "tokio-rustls", "webpki", "webpki-roots"]
|
||||
|
||||
cell = []
|
||||
|
||||
[dependencies]
|
||||
actix = "0.7.6"
|
||||
|
||||
log = "0.4"
|
||||
num_cpus = "1.0"
|
||||
|
||||
# io
|
||||
mio = "^0.6.13"
|
||||
net2 = "0.2"
|
||||
bytes = "0.4"
|
||||
futures = "0.1"
|
||||
slab = "0.4"
|
||||
tokio = "0.1"
|
||||
tokio-codec = "0.1"
|
||||
tokio-io = "0.1"
|
||||
tokio-tcp = "0.1"
|
||||
tokio-timer = "0.2"
|
||||
tokio-reactor = "0.1"
|
||||
tokio-current-thread = "0.1"
|
||||
tower-service = "0.1"
|
||||
trust-dns-proto = "^0.5.0"
|
||||
trust-dns-resolver = "^0.10.0"
|
||||
|
||||
# native-tls
|
||||
native-tls = { version="0.2", optional = true }
|
||||
|
||||
# openssl
|
||||
openssl = { version="0.10", optional = true }
|
||||
tokio-openssl = { version="0.2", optional = true }
|
||||
|
||||
#rustls
|
||||
rustls = { version = "^0.14", optional = true }
|
||||
tokio-rustls = { version = "^0.8", optional = true }
|
||||
webpki = { version = "0.18", optional = true }
|
||||
webpki-roots = { version = "0.15", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.5"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
opt-level = 3
|
||||
codegen-units = 1
|
||||
|
68
README.md
68
README.md
@@ -1,25 +1,20 @@
|
||||
# Actix net [](https://codecov.io/gh/actix/actix-net) [](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
# Actix net [](https://travis-ci.org/actix/actix-net) [](https://codecov.io/gh/actix/actix-net) [](https://crates.io/crates/actix-net) [](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
|
||||
|
||||
## Build statuses
|
||||
|
||||
| Platform | Build Status |
|
||||
| ---------------- | ------------ |
|
||||
| Linux | [](https://github.com/actix/actix-net/actions?query=workflow%3A"CI+(Linux)") |
|
||||
| macOS | [](https://github.com/actix/actix-net/actions?query=workflow%3A"CI+(macOS)") |
|
||||
| Windows | [](https://github.com/actix/actix-net/actions?query=workflow%3A"CI+(Windows)") |
|
||||
| Windows (MinGW) | [](https://github.com/actix/actix-net/actions?query=workflow%3A"CI+(Windows-mingw)") |
|
||||
Actix net - framework for composable network services (experimental)
|
||||
|
||||
## Documentation & community resources
|
||||
|
||||
* [Chat on Gitter](https://gitter.im/actix/actix)
|
||||
* Minimum supported Rust version: 1.46 or later
|
||||
* [API Documentation (Development)](https://actix.rs/actix-net/actix_net/)
|
||||
* [Chat on gitter](https://gitter.im/actix/actix)
|
||||
* Cargo package: [actix-net](https://crates.io/crates/actix-net)
|
||||
* Minimum supported Rust version: 1.26 or later
|
||||
|
||||
## Example
|
||||
|
||||
```rust
|
||||
fn main() -> io::Result<()> {
|
||||
fn main() {
|
||||
let sys = actix::System::new("test");
|
||||
|
||||
// load ssl keys
|
||||
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
|
||||
builder.set_private_key_file("./examples/key.pem", SslFiletype::PEM).unwrap();
|
||||
@@ -31,7 +26,7 @@ fn main() -> io::Result<()> {
|
||||
// bind socket address and start workers. By default server uses number of
|
||||
// available logical cpu as threads count. actix net start separate
|
||||
// instances of service pipeline in each worker.
|
||||
Server::build()
|
||||
Server::default()
|
||||
.bind(
|
||||
// configure service pipeline
|
||||
"basic", "0.0.0.0:8443",
|
||||
@@ -39,26 +34,29 @@ fn main() -> io::Result<()> {
|
||||
let num = num.clone();
|
||||
let acceptor = acceptor.clone();
|
||||
|
||||
// construct transformation pipeline
|
||||
pipeline(
|
||||
// service for converting incoming TcpStream to a SslStream<TcpStream>
|
||||
fn_service(move |stream: actix_rt::net::TcpStream| async move {
|
||||
SslAcceptorExt::accept_async(&acceptor, stream.into_parts().0).await
|
||||
.map_err(|e| println!("Openssl error: {}", e))
|
||||
}))
|
||||
// .and_then() combinator chains result of previos service call to argument
|
||||
/// for next service calll. in this case, on success we chain
|
||||
/// ssl stream to the `logger` service.
|
||||
.and_then(fn_service(logger))
|
||||
// Next service counts number of connections
|
||||
.and_then(move |_| {
|
||||
let num = num.fetch_add(1, Ordering::Relaxed);
|
||||
println!("got ssl connection {:?}", num);
|
||||
future::ok(())
|
||||
})
|
||||
},
|
||||
)?
|
||||
.run()
|
||||
// service for converting incoming TcpStream to a SslStream<TcpStream>
|
||||
(move |stream| {
|
||||
SslAcceptorExt::accept_async(&acceptor, stream)
|
||||
.map_err(|e| println!("Openssl error: {}", e))
|
||||
})
|
||||
// convert closure to a `NewService`
|
||||
.into_new_service()
|
||||
|
||||
// .and_then() combinator uses other service to convert incoming `Request` to a `Response`
|
||||
// and then uses that response as an input for next service.
|
||||
// in this case, on success we use `logger` service
|
||||
.and_then(logger)
|
||||
|
||||
// Next service counts number of connections
|
||||
.and_then(move |req| {
|
||||
let num = num.fetch_add(1, Ordering::Relaxed);
|
||||
println!("processed {:?} connections", num);
|
||||
future::ok(())
|
||||
})
|
||||
}).unwrap()
|
||||
.start();
|
||||
|
||||
sys.run();
|
||||
}
|
||||
```
|
||||
|
||||
|
@@ -1,59 +0,0 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2020-xx-xx
|
||||
|
||||
|
||||
## 0.4.0-beta.1 - 2020-12-28
|
||||
* Replace `pin-project` with `pin-project-lite`. [#237]
|
||||
* Upgrade `tokio` dependency to `1`. [#237]
|
||||
* Upgrade `tokio-util` dependency to `0.6`. [#237]
|
||||
* Upgrade `bytes` dependency to `1`. [#237]
|
||||
|
||||
[#237]: https://github.com/actix/actix-net/pull/237
|
||||
|
||||
|
||||
## 0.3.0 - 2020-08-23
|
||||
* 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
|
||||
* Added `Framed::map_io()` method.
|
||||
|
||||
|
||||
## 0.1.1 - 2019-03-06
|
||||
* Added `FramedParts::with_read_buffer()` method.
|
||||
|
||||
|
||||
## 0.1.0 - 2018-12-09
|
||||
* Move codec to separate crate
|
@@ -1,26 +0,0 @@
|
||||
[package]
|
||||
name = "actix-codec"
|
||||
version = "0.4.0-beta.1"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Codec utilities for working with framed protocols"
|
||||
keywords = ["network", "framework", "async", "futures"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-net.git"
|
||||
documentation = "https://docs.rs/actix-codec/"
|
||||
categories = ["network-programming", "asynchronous"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
name = "actix_codec"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
bitflags = "1.2.1"
|
||||
bytes = "1"
|
||||
futures-core = { version = "0.3.7", default-features = false }
|
||||
futures-sink = { version = "0.3.7", default-features = false }
|
||||
log = "0.4"
|
||||
pin-project-lite = "0.2"
|
||||
tokio = "1"
|
||||
tokio-util = { version = "0.6", features = ["codec", "io"] }
|
@@ -1 +0,0 @@
|
||||
../LICENSE-APACHE
|
@@ -1 +0,0 @@
|
||||
../LICENSE-MIT
|
@@ -1,420 +0,0 @@
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use std::{fmt, io};
|
||||
|
||||
use bytes::{Buf, BytesMut};
|
||||
use futures_core::{ready, Stream};
|
||||
use futures_sink::Sink;
|
||||
|
||||
use crate::{AsyncRead, AsyncWrite, Decoder, Encoder};
|
||||
|
||||
/// Low-water mark
|
||||
const LW: usize = 1024;
|
||||
/// High-water mark
|
||||
const HW: usize = 8 * 1024;
|
||||
|
||||
bitflags::bitflags! {
|
||||
struct Flags: u8 {
|
||||
const EOF = 0b0001;
|
||||
const READABLE = 0b0010;
|
||||
}
|
||||
}
|
||||
|
||||
pin_project_lite::pin_project! {
|
||||
/// A unified `Stream` and `Sink` interface to an underlying I/O object, using
|
||||
/// the `Encoder` and `Decoder` traits to encode and decode frames.
|
||||
///
|
||||
/// Raw I/O objects work with byte sequences, but higher-level code usually
|
||||
/// wants to batch these into meaningful chunks, called "frames". This
|
||||
/// 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.
|
||||
pub struct Framed<T, U> {
|
||||
#[pin]
|
||||
io: T,
|
||||
codec: U,
|
||||
flags: Flags,
|
||||
read_buf: BytesMut,
|
||||
write_buf: BytesMut,
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Framed<T, U>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
U: Decoder,
|
||||
{
|
||||
/// 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.
|
||||
pub fn new(io: T, codec: U) -> Framed<T, U> {
|
||||
Framed {
|
||||
io,
|
||||
codec,
|
||||
flags: Flags::empty(),
|
||||
read_buf: BytesMut::with_capacity(HW),
|
||||
write_buf: BytesMut::with_capacity(HW),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Framed<T, U> {
|
||||
/// Returns a reference to the underlying codec.
|
||||
pub fn codec_ref(&self) -> &U {
|
||||
&self.codec
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying codec.
|
||||
pub fn codec_mut(&mut self) -> &mut U {
|
||||
&mut self.codec
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying I/O stream wrapped by
|
||||
/// `Frame`.
|
||||
///
|
||||
/// 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 io_ref(&self) -> &T {
|
||||
&self.io
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the 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 io_mut(&mut self) -> &mut T {
|
||||
&mut self.io
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub fn is_write_buf_empty(&self) -> bool {
|
||||
self.write_buf.is_empty()
|
||||
}
|
||||
|
||||
/// Check if write buffer is full.
|
||||
pub fn is_write_buf_full(&self) -> bool {
|
||||
self.write_buf.len() >= HW
|
||||
}
|
||||
|
||||
/// Check if framed is able to write more data.
|
||||
///
|
||||
/// `Framed` object considers ready if there is free space in write buffer.
|
||||
pub fn is_write_ready(&self) -> bool {
|
||||
self.write_buf.len() < HW
|
||||
}
|
||||
|
||||
/// Consume the `Frame`, returning `Frame` with different codec.
|
||||
pub fn replace_codec<U2>(self, codec: U2) -> Framed<T, U2> {
|
||||
Framed {
|
||||
codec,
|
||||
io: self.io,
|
||||
flags: self.flags,
|
||||
read_buf: self.read_buf,
|
||||
write_buf: self.write_buf,
|
||||
}
|
||||
}
|
||||
|
||||
/// Consume the `Frame`, returning `Frame` with different io.
|
||||
pub fn into_map_io<F, T2>(self, f: F) -> Framed<T2, U>
|
||||
where
|
||||
F: Fn(T) -> T2,
|
||||
{
|
||||
Framed {
|
||||
io: f(self.io),
|
||||
codec: self.codec,
|
||||
flags: self.flags,
|
||||
read_buf: self.read_buf,
|
||||
write_buf: self.write_buf,
|
||||
}
|
||||
}
|
||||
|
||||
/// Consume the `Frame`, returning `Frame` with different codec.
|
||||
pub fn into_map_codec<F, U2>(self, f: F) -> Framed<T, U2>
|
||||
where
|
||||
F: Fn(U) -> U2,
|
||||
{
|
||||
Framed {
|
||||
io: self.io,
|
||||
codec: f(self.codec),
|
||||
flags: self.flags,
|
||||
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 tokio_util::io::poll_read_buf(this.io, cx, 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,
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes the `Frame`, returning its underlying I/O stream, the buffer
|
||||
/// with unprocessed data, and the codec.
|
||||
///
|
||||
/// 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_parts(self) -> FramedParts<T, U> {
|
||||
FramedParts {
|
||||
io: self.io,
|
||||
codec: self.codec,
|
||||
flags: self.flags,
|
||||
read_buf: self.read_buf,
|
||||
write_buf: self.write_buf,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `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 contains all current buffers and the inner transport.
|
||||
#[derive(Debug)]
|
||||
pub struct FramedParts<T, U> {
|
||||
/// The inner transport used to read bytes to and write bytes to
|
||||
pub io: T,
|
||||
|
||||
/// The codec
|
||||
pub codec: U,
|
||||
|
||||
/// The buffer with read but unprocessed data.
|
||||
pub read_buf: BytesMut,
|
||||
|
||||
/// A buffer with unprocessed data which are not written yet.
|
||||
pub write_buf: BytesMut,
|
||||
|
||||
flags: Flags,
|
||||
}
|
||||
|
||||
impl<T, U> FramedParts<T, U> {
|
||||
/// Create a new, default, `FramedParts`
|
||||
pub fn new(io: T, codec: U) -> FramedParts<T, U> {
|
||||
FramedParts {
|
||||
io,
|
||||
codec,
|
||||
flags: Flags::empty(),
|
||||
read_buf: BytesMut::new(),
|
||||
write_buf: BytesMut::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `FramedParts` with read buffer
|
||||
pub fn with_read_buf(io: T, codec: U, read_buf: BytesMut) -> FramedParts<T, U> {
|
||||
FramedParts {
|
||||
io,
|
||||
codec,
|
||||
read_buf,
|
||||
flags: Flags::empty(),
|
||||
write_buf: BytesMut::new(),
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,23 +0,0 @@
|
||||
//! Codec utilities for working with framed protocols.
|
||||
//!
|
||||
//! Contains adapters to go from streams of bytes, [`AsyncRead`] and
|
||||
//! [`AsyncWrite`], to framed streams implementing [`Sink`] and [`Stream`].
|
||||
//! Framed streams are also known as `transports`.
|
||||
//!
|
||||
//! [`Sink`]: futures_sink::Sink
|
||||
//! [`Stream`]: futures_core::Stream
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(missing_docs)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
|
||||
mod bcodec;
|
||||
mod framed;
|
||||
|
||||
pub use self::bcodec::BytesCodec;
|
||||
pub use self::framed::{Framed, FramedParts};
|
||||
|
||||
pub use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
pub use tokio_util::codec::{Decoder, Encoder};
|
||||
pub use tokio_util::io::poll_read_buf;
|
@@ -1,154 +0,0 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2020-xx-xx
|
||||
|
||||
|
||||
## 2.0.0 - 2020-09-02
|
||||
- No significant changes from `2.0.0-alpha.4`.
|
||||
|
||||
## 2.0.0-alpha.4 - 2020-08-17
|
||||
|
||||
### Changed
|
||||
|
||||
* Update `rustls` dependency to 0.18
|
||||
* Update `tokio-rustls` dependency to 0.14
|
||||
|
||||
|
||||
## [2.0.0-alpha.3] - 2020-05-08
|
||||
|
||||
### Fixed
|
||||
|
||||
* Corrected spelling of `ConnectError::Unresolverd` to `ConnectError::Unresolved`
|
||||
|
||||
## [2.0.0-alpha.2] - 2020-03-08
|
||||
|
||||
### Changed
|
||||
|
||||
* Update `trust-dns-proto` dependency to 0.19. [#116]
|
||||
* Update `trust-dns-resolver` dependency to 0.19. [#116]
|
||||
* `Address` trait is now required to have static lifetime. [#116]
|
||||
* `start_resolver` and `start_default_resolver` are now `async` and may return a `ConnectError`. [#116]
|
||||
|
||||
[#116]: https://github.com/actix/actix-net/pull/116
|
||||
|
||||
## [2.0.0-alpha.1] - 2020-03-03
|
||||
|
||||
### Changed
|
||||
|
||||
* Update `rustls` dependency to 0.17
|
||||
* Update `tokio-rustls` dependency to 0.13
|
||||
|
||||
## [1.0.2] - 2020-01-15
|
||||
|
||||
* Fix actix-service 1.0.3 compatibility
|
||||
|
||||
## [1.0.1] - 2019-12-15
|
||||
|
||||
* Fix trust-dns-resolver compilation
|
||||
|
||||
## [1.0.0] - 2019-12-11
|
||||
|
||||
* Release
|
||||
|
||||
## [1.0.0-alpha.3] - 2019-12-07
|
||||
|
||||
### Changed
|
||||
|
||||
* Migrate to tokio 0.2
|
||||
|
||||
|
||||
## [1.0.0-alpha.2] - 2019-12-02
|
||||
|
||||
### Changed
|
||||
|
||||
* Migrated to `std::future`
|
||||
|
||||
|
||||
## [0.3.0] - 2019-10-03
|
||||
|
||||
### Changed
|
||||
|
||||
* Update `rustls` to 0.16
|
||||
* Minimum required Rust version upped to 1.37.0
|
||||
|
||||
## [0.2.5] - 2019-09-05
|
||||
|
||||
* Add `TcpConnectService`
|
||||
|
||||
## [0.2.4] - 2019-09-02
|
||||
|
||||
* Use arbiter's storage for default async resolver
|
||||
|
||||
## [0.2.3] - 2019-08-05
|
||||
|
||||
* Add `ConnectService` and `OpensslConnectService`
|
||||
|
||||
## [0.2.2] - 2019-07-24
|
||||
|
||||
* Add `rustls` support
|
||||
|
||||
## [0.2.1] - 2019-07-17
|
||||
|
||||
### Added
|
||||
|
||||
* Expose Connect addrs #30
|
||||
|
||||
### Changed
|
||||
|
||||
* Update `derive_more` to 0.15
|
||||
|
||||
|
||||
## [0.2.0] - 2019-05-12
|
||||
|
||||
### Changed
|
||||
|
||||
* Upgrade to actix-service 0.4
|
||||
|
||||
|
||||
## [0.1.5] - 2019-04-19
|
||||
|
||||
### Added
|
||||
|
||||
* `Connect::set_addr()`
|
||||
|
||||
### Changed
|
||||
|
||||
* Use trust-dns-resolver 0.11.0
|
||||
|
||||
|
||||
## [0.1.4] - 2019-04-12
|
||||
|
||||
### Changed
|
||||
|
||||
* Do not start default resolver immediately for default connector.
|
||||
|
||||
|
||||
## [0.1.3] - 2019-04-11
|
||||
|
||||
### Changed
|
||||
|
||||
* Start trust-dns default resolver on first use
|
||||
|
||||
## [0.1.2] - 2019-04-04
|
||||
|
||||
### Added
|
||||
|
||||
* Log error if dns system config could not be loaded.
|
||||
|
||||
### Changed
|
||||
|
||||
* Rename connect Connector to TcpConnector #10
|
||||
|
||||
|
||||
## [0.1.1] - 2019-03-15
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix error handling for single address
|
||||
|
||||
|
||||
## [0.1.0] - 2019-03-14
|
||||
|
||||
* Refactor resolver and connector services
|
||||
|
||||
* Rename crate
|
@@ -1,58 +0,0 @@
|
||||
[package]
|
||||
name = "actix-connect"
|
||||
version = "2.0.0"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "TCP connector service for Actix ecosystem."
|
||||
keywords = ["network", "framework", "async", "futures"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-net.git"
|
||||
documentation = "https://docs.rs/actix-connect/"
|
||||
categories = ["network-programming", "asynchronous"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["openssl", "rustls", "uri"]
|
||||
|
||||
[lib]
|
||||
name = "actix_connect"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = ["uri"]
|
||||
|
||||
# openssl
|
||||
openssl = ["open-ssl", "tokio-openssl"]
|
||||
|
||||
# rustls
|
||||
rustls = ["rust-tls", "tokio-rustls", "webpki"]
|
||||
|
||||
# support http::Uri as connect address
|
||||
uri = ["http"]
|
||||
|
||||
[dependencies]
|
||||
actix-service = "1.0.6"
|
||||
actix-codec = "0.3.0"
|
||||
actix-utils = "2.0.0"
|
||||
actix-rt = "1.1.1"
|
||||
|
||||
derive_more = "0.99.2"
|
||||
either = "1.5.3"
|
||||
futures-util = { version = "0.3.4", default-features = false }
|
||||
http = { version = "0.2.0", optional = true }
|
||||
log = "0.4"
|
||||
trust-dns-proto = { version = "0.19", default-features = false, features = ["tokio-runtime"] }
|
||||
trust-dns-resolver = { version = "0.19", default-features = false, features = ["tokio-runtime", "system-config"] }
|
||||
|
||||
# openssl
|
||||
open-ssl = { package = "openssl", version = "0.10", optional = true }
|
||||
tokio-openssl = { version = "0.4.0", optional = true }
|
||||
|
||||
# rustls
|
||||
rust-tls = { package = "rustls", version = "0.18.0", optional = true }
|
||||
tokio-rustls = { version = "0.14.0", optional = true }
|
||||
webpki = { version = "0.21", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
bytes = "0.5.3"
|
||||
actix-testing = "1.0.0"
|
@@ -1 +0,0 @@
|
||||
../LICENSE-APACHE
|
@@ -1 +0,0 @@
|
||||
../LICENSE-MIT
|
@@ -1,282 +0,0 @@
|
||||
use std::collections::{vec_deque, VecDeque};
|
||||
use std::fmt;
|
||||
use std::iter::{FromIterator, FusedIterator};
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use either::Either;
|
||||
|
||||
/// Connect request
|
||||
pub trait Address: Unpin + 'static {
|
||||
/// Host name of the request
|
||||
fn host(&self) -> &str;
|
||||
|
||||
/// Port of the request
|
||||
fn port(&self) -> Option<u16>;
|
||||
}
|
||||
|
||||
impl Address for String {
|
||||
fn host(&self) -> &str {
|
||||
&self
|
||||
}
|
||||
|
||||
fn port(&self) -> Option<u16> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Address for &'static str {
|
||||
fn host(&self) -> &str {
|
||||
self
|
||||
}
|
||||
|
||||
fn port(&self) -> Option<u16> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Connect request
|
||||
#[derive(Eq, PartialEq, Debug, Hash)]
|
||||
pub struct Connect<T> {
|
||||
pub(crate) req: T,
|
||||
pub(crate) port: u16,
|
||||
pub(crate) addr: Option<Either<SocketAddr, VecDeque<SocketAddr>>>,
|
||||
}
|
||||
|
||||
impl<T: Address> Connect<T> {
|
||||
/// Create `Connect` instance by splitting the string by ':' and convert the second part to u16
|
||||
pub fn new(req: T) -> Connect<T> {
|
||||
let (_, port) = parse(req.host());
|
||||
Connect {
|
||||
req,
|
||||
port: port.unwrap_or(0),
|
||||
addr: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create new `Connect` instance from host and address. Connector skips name resolution stage
|
||||
/// for such connect messages.
|
||||
pub fn with(req: T, addr: SocketAddr) -> Connect<T> {
|
||||
Connect {
|
||||
req,
|
||||
port: 0,
|
||||
addr: Some(Either::Left(addr)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Use port if address does not provide one.
|
||||
///
|
||||
/// By default it set to 0
|
||||
pub fn set_port(mut self, port: u16) -> Self {
|
||||
self.port = port;
|
||||
self
|
||||
}
|
||||
|
||||
/// Use address.
|
||||
pub fn set_addr(mut self, addr: Option<SocketAddr>) -> Self {
|
||||
if let Some(addr) = addr {
|
||||
self.addr = Some(Either::Left(addr));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Use addresses.
|
||||
pub fn set_addrs<I>(mut self, addrs: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = SocketAddr>,
|
||||
{
|
||||
let mut addrs = VecDeque::from_iter(addrs);
|
||||
self.addr = if addrs.len() < 2 {
|
||||
addrs.pop_front().map(Either::Left)
|
||||
} else {
|
||||
Some(Either::Right(addrs))
|
||||
};
|
||||
self
|
||||
}
|
||||
|
||||
/// Host name
|
||||
pub fn host(&self) -> &str {
|
||||
self.req.host()
|
||||
}
|
||||
|
||||
/// Port of the request
|
||||
pub fn port(&self) -> u16 {
|
||||
self.req.port().unwrap_or(self.port)
|
||||
}
|
||||
|
||||
/// Pre-resolved addresses of the request.
|
||||
pub fn addrs(&self) -> ConnectAddrsIter<'_> {
|
||||
let inner = match self.addr {
|
||||
None => Either::Left(None),
|
||||
Some(Either::Left(addr)) => Either::Left(Some(addr)),
|
||||
Some(Either::Right(ref addrs)) => Either::Right(addrs.iter()),
|
||||
};
|
||||
|
||||
ConnectAddrsIter { inner }
|
||||
}
|
||||
|
||||
/// Takes pre-resolved addresses of the request.
|
||||
pub fn take_addrs(&mut self) -> ConnectTakeAddrsIter {
|
||||
let inner = match self.addr.take() {
|
||||
None => Either::Left(None),
|
||||
Some(Either::Left(addr)) => Either::Left(Some(addr)),
|
||||
Some(Either::Right(addrs)) => Either::Right(addrs.into_iter()),
|
||||
};
|
||||
|
||||
ConnectTakeAddrsIter { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Address> From<T> for Connect<T> {
|
||||
fn from(addr: T) -> Self {
|
||||
Connect::new(addr)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Address> fmt::Display for Connect<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}:{}", self.host(), self.port())
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator over addresses in a [`Connect`] request.
|
||||
#[derive(Clone)]
|
||||
pub struct ConnectAddrsIter<'a> {
|
||||
inner: Either<Option<SocketAddr>, vec_deque::Iter<'a, SocketAddr>>,
|
||||
}
|
||||
|
||||
impl Iterator for ConnectAddrsIter<'_> {
|
||||
type Item = SocketAddr;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self.inner {
|
||||
Either::Left(ref mut opt) => opt.take(),
|
||||
Either::Right(ref mut iter) => iter.next().copied(),
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
match self.inner {
|
||||
Either::Left(Some(_)) => (1, Some(1)),
|
||||
Either::Left(None) => (0, Some(0)),
|
||||
Either::Right(ref iter) => iter.size_hint(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ConnectAddrsIter<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_list().entries(self.clone()).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl ExactSizeIterator for ConnectAddrsIter<'_> {}
|
||||
|
||||
impl FusedIterator for ConnectAddrsIter<'_> {}
|
||||
|
||||
/// Owned iterator over addresses in a [`Connect`] request.
|
||||
#[derive(Debug)]
|
||||
pub struct ConnectTakeAddrsIter {
|
||||
inner: Either<Option<SocketAddr>, vec_deque::IntoIter<SocketAddr>>,
|
||||
}
|
||||
|
||||
impl Iterator for ConnectTakeAddrsIter {
|
||||
type Item = SocketAddr;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self.inner {
|
||||
Either::Left(ref mut opt) => opt.take(),
|
||||
Either::Right(ref mut iter) => iter.next(),
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
match self.inner {
|
||||
Either::Left(Some(_)) => (1, Some(1)),
|
||||
Either::Left(None) => (0, Some(0)),
|
||||
Either::Right(ref iter) => iter.size_hint(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExactSizeIterator for ConnectTakeAddrsIter {}
|
||||
|
||||
impl FusedIterator for ConnectTakeAddrsIter {}
|
||||
|
||||
fn parse(host: &str) -> (&str, Option<u16>) {
|
||||
let mut parts_iter = host.splitn(2, ':');
|
||||
if let Some(host) = parts_iter.next() {
|
||||
let port_str = parts_iter.next().unwrap_or("");
|
||||
if let Ok(port) = port_str.parse::<u16>() {
|
||||
(host, Some(port))
|
||||
} else {
|
||||
(host, None)
|
||||
}
|
||||
} else {
|
||||
(host, None)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Connection<T, U> {
|
||||
io: U,
|
||||
req: T,
|
||||
}
|
||||
|
||||
impl<T, U> Connection<T, U> {
|
||||
pub fn new(io: U, req: T) -> Self {
|
||||
Self { io, req }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Connection<T, U> {
|
||||
/// Reconstruct from a parts.
|
||||
pub fn from_parts(io: U, req: T) -> Self {
|
||||
Self { io, req }
|
||||
}
|
||||
|
||||
/// Deconstruct into a parts.
|
||||
pub fn into_parts(self) -> (U, T) {
|
||||
(self.io, self.req)
|
||||
}
|
||||
|
||||
/// Replace inclosed object, return new Stream and old object
|
||||
pub fn replace<Y>(self, io: Y) -> (U, Connection<T, Y>) {
|
||||
(self.io, Connection { io, req: self.req })
|
||||
}
|
||||
|
||||
/// Returns a shared reference to the underlying stream.
|
||||
pub fn get_ref(&self) -> &U {
|
||||
&self.io
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying stream.
|
||||
pub fn get_mut(&mut self) -> &mut U {
|
||||
&mut self.io
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Address, U> Connection<T, U> {
|
||||
/// Get request
|
||||
pub fn host(&self) -> &str {
|
||||
&self.req.host()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> std::ops::Deref for Connection<T, U> {
|
||||
type Target = U;
|
||||
|
||||
fn deref(&self) -> &U {
|
||||
&self.io
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> std::ops::DerefMut for Connection<T, U> {
|
||||
fn deref_mut(&mut self) -> &mut U {
|
||||
&mut self.io
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U: fmt::Debug> fmt::Debug for Connection<T, U> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Stream {{{:?}}}", self.io)
|
||||
}
|
||||
}
|
@@ -1,168 +0,0 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::future::Future;
|
||||
use std::io;
|
||||
use std::marker::PhantomData;
|
||||
use std::net::SocketAddr;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use actix_rt::net::TcpStream;
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use futures_util::future::{err, ok, BoxFuture, Either, FutureExt, Ready};
|
||||
|
||||
use super::connect::{Address, Connect, Connection};
|
||||
use super::error::ConnectError;
|
||||
|
||||
/// TCP connector service factory
|
||||
#[derive(Debug)]
|
||||
pub struct TcpConnectorFactory<T>(PhantomData<T>);
|
||||
|
||||
impl<T> TcpConnectorFactory<T> {
|
||||
pub fn new() -> Self {
|
||||
TcpConnectorFactory(PhantomData)
|
||||
}
|
||||
|
||||
/// Create TCP connector service
|
||||
pub fn service(&self) -> TcpConnector<T> {
|
||||
TcpConnector(PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for TcpConnectorFactory<T> {
|
||||
fn default() -> Self {
|
||||
TcpConnectorFactory(PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for TcpConnectorFactory<T> {
|
||||
fn clone(&self) -> Self {
|
||||
TcpConnectorFactory(PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Address> ServiceFactory<Connect<T>> for TcpConnectorFactory<T> {
|
||||
type Response = Connection<T, TcpStream>;
|
||||
type Error = ConnectError;
|
||||
type Config = ();
|
||||
type Service = TcpConnector<T>;
|
||||
type InitError = ();
|
||||
type Future = Ready<Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
ok(self.service())
|
||||
}
|
||||
}
|
||||
|
||||
/// TCP connector service
|
||||
#[derive(Default, Debug)]
|
||||
pub struct TcpConnector<T>(PhantomData<T>);
|
||||
|
||||
impl<T> TcpConnector<T> {
|
||||
pub fn new() -> Self {
|
||||
TcpConnector(PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for TcpConnector<T> {
|
||||
fn clone(&self) -> Self {
|
||||
TcpConnector(PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Address> Service<Connect<T>> for TcpConnector<T> {
|
||||
type Response = Connection<T, TcpStream>;
|
||||
type Error = ConnectError;
|
||||
#[allow(clippy::type_complexity)]
|
||||
type Future = Either<TcpConnectorResponse<T>, Ready<Result<Self::Response, Self::Error>>>;
|
||||
|
||||
actix_service::always_ready!();
|
||||
|
||||
fn call(&mut self, req: Connect<T>) -> Self::Future {
|
||||
let port = req.port();
|
||||
let Connect { req, addr, .. } = req;
|
||||
|
||||
if let Some(addr) = addr {
|
||||
Either::Left(TcpConnectorResponse::new(req, port, addr))
|
||||
} else {
|
||||
error!("TCP connector: got unresolved address");
|
||||
Either::Right(err(ConnectError::Unresolved))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// TCP stream connector response future
|
||||
pub struct TcpConnectorResponse<T> {
|
||||
req: Option<T>,
|
||||
port: u16,
|
||||
addrs: Option<VecDeque<SocketAddr>>,
|
||||
stream: Option<BoxFuture<'static, Result<TcpStream, io::Error>>>,
|
||||
}
|
||||
|
||||
impl<T: Address> TcpConnectorResponse<T> {
|
||||
pub fn new(
|
||||
req: T,
|
||||
port: u16,
|
||||
addr: either::Either<SocketAddr, VecDeque<SocketAddr>>,
|
||||
) -> TcpConnectorResponse<T> {
|
||||
trace!(
|
||||
"TCP connector - connecting to {:?} port:{}",
|
||||
req.host(),
|
||||
port
|
||||
);
|
||||
|
||||
match addr {
|
||||
either::Either::Left(addr) => TcpConnectorResponse {
|
||||
req: Some(req),
|
||||
port,
|
||||
addrs: None,
|
||||
stream: Some(TcpStream::connect(addr).boxed()),
|
||||
},
|
||||
either::Either::Right(addrs) => TcpConnectorResponse {
|
||||
req: Some(req),
|
||||
port,
|
||||
addrs: Some(addrs),
|
||||
stream: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Address> Future for TcpConnectorResponse<T> {
|
||||
type Output = Result<Connection<T, TcpStream>, ConnectError>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.get_mut();
|
||||
|
||||
// connect
|
||||
loop {
|
||||
if let Some(new) = this.stream.as_mut() {
|
||||
match new.as_mut().poll(cx) {
|
||||
Poll::Ready(Ok(sock)) => {
|
||||
let req = this.req.take().unwrap();
|
||||
trace!(
|
||||
"TCP connector - successfully connected to connecting to {:?} - {:?}",
|
||||
req.host(), sock.peer_addr()
|
||||
);
|
||||
return Poll::Ready(Ok(Connection::new(sock, req)));
|
||||
}
|
||||
Poll::Pending => return Poll::Pending,
|
||||
Poll::Ready(Err(err)) => {
|
||||
trace!(
|
||||
"TCP connector - failed to connect to connecting to {:?} port: {}",
|
||||
this.req.as_ref().unwrap().host(),
|
||||
this.port,
|
||||
);
|
||||
if this.addrs.is_none() || this.addrs.as_ref().unwrap().is_empty() {
|
||||
return Poll::Ready(Err(err.into()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// try to connect
|
||||
let addr = this.addrs.as_mut().unwrap().pop_front().unwrap();
|
||||
this.stream = Some(TcpStream::connect(addr).boxed());
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,26 +0,0 @@
|
||||
use std::io;
|
||||
|
||||
use derive_more::{Display, From};
|
||||
use trust_dns_resolver::error::ResolveError;
|
||||
|
||||
#[derive(Debug, From, Display)]
|
||||
pub enum ConnectError {
|
||||
/// Failed to resolve the hostname
|
||||
#[display(fmt = "Failed resolving hostname: {}", _0)]
|
||||
Resolver(ResolveError),
|
||||
|
||||
/// No dns records
|
||||
#[display(fmt = "No dns records found for the input")]
|
||||
NoRecords,
|
||||
|
||||
/// Invalid input
|
||||
InvalidInput,
|
||||
|
||||
/// Unresolved host name
|
||||
#[display(fmt = "Connector received `Connect` method with unresolved host")]
|
||||
Unresolved,
|
||||
|
||||
/// Connection IO error
|
||||
#[display(fmt = "{}", _0)]
|
||||
Io(io::Error),
|
||||
}
|
@@ -1,113 +0,0 @@
|
||||
//! TCP connector service for Actix ecosystem.
|
||||
//!
|
||||
//! ## Package feature
|
||||
//!
|
||||
//! * `openssl` - enables TLS support via `openssl` crate
|
||||
//! * `rustls` - enables TLS support via `rustls` crate
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![recursion_limit = "128"]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
mod connect;
|
||||
mod connector;
|
||||
mod error;
|
||||
mod resolve;
|
||||
mod service;
|
||||
pub mod ssl;
|
||||
|
||||
#[cfg(feature = "uri")]
|
||||
mod uri;
|
||||
|
||||
use actix_rt::{net::TcpStream, Arbiter};
|
||||
use actix_service::{pipeline, pipeline_factory, Service, ServiceFactory};
|
||||
use trust_dns_resolver::config::{ResolverConfig, ResolverOpts};
|
||||
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::connector::{TcpConnector, TcpConnectorFactory};
|
||||
pub use self::error::ConnectError;
|
||||
pub use self::resolve::{Resolver, ResolverFactory};
|
||||
pub use self::service::{ConnectService, ConnectServiceFactory, TcpConnectService};
|
||||
|
||||
pub async fn start_resolver(
|
||||
cfg: ResolverConfig,
|
||||
opts: ResolverOpts,
|
||||
) -> Result<AsyncResolver, ConnectError> {
|
||||
Ok(AsyncResolver::tokio(cfg, opts).await?)
|
||||
}
|
||||
|
||||
struct DefaultResolver(AsyncResolver);
|
||||
|
||||
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() {
|
||||
Ok((cfg, opts)) => (cfg, opts),
|
||||
Err(e) => {
|
||||
log::error!("TRust-DNS can not load system config: {}", e);
|
||||
(ResolverConfig::default(), ResolverOpts::default())
|
||||
}
|
||||
};
|
||||
|
||||
let resolver = AsyncResolver::tokio(cfg, opts).await?;
|
||||
|
||||
Arbiter::set_item(DefaultResolver(resolver.clone()));
|
||||
Ok(resolver)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn start_default_resolver() -> Result<AsyncResolver, ConnectError> {
|
||||
get_default_resolver().await
|
||||
}
|
||||
|
||||
/// Create TCP connector service.
|
||||
pub fn new_connector<T: Address + 'static>(
|
||||
resolver: AsyncResolver,
|
||||
) -> impl Service<Connect<T>, Response = Connection<T, TcpStream>, Error = ConnectError> + Clone
|
||||
{
|
||||
pipeline(Resolver::new(resolver)).and_then(TcpConnector::new())
|
||||
}
|
||||
|
||||
/// Create TCP connector service factory.
|
||||
pub fn new_connector_factory<T: Address + 'static>(
|
||||
resolver: AsyncResolver,
|
||||
) -> impl ServiceFactory<
|
||||
Connect<T>,
|
||||
Config = (),
|
||||
Response = Connection<T, TcpStream>,
|
||||
Error = ConnectError,
|
||||
InitError = (),
|
||||
> + Clone {
|
||||
pipeline_factory(ResolverFactory::new(resolver)).and_then(TcpConnectorFactory::new())
|
||||
}
|
||||
|
||||
/// Create connector service with default parameters.
|
||||
pub fn default_connector<T: Address + 'static>(
|
||||
) -> impl Service<Connect<T>, Response = Connection<T, TcpStream>, Error = ConnectError> + Clone
|
||||
{
|
||||
pipeline(Resolver::default()).and_then(TcpConnector::new())
|
||||
}
|
||||
|
||||
/// Create connector service factory with default parameters.
|
||||
pub fn default_connector_factory<T: Address + 'static>() -> impl ServiceFactory<
|
||||
Connect<T>,
|
||||
Config = (),
|
||||
Response = Connection<T, TcpStream>,
|
||||
Error = ConnectError,
|
||||
InitError = (),
|
||||
> + Clone {
|
||||
pipeline_factory(ResolverFactory::default()).and_then(TcpConnectorFactory::new())
|
||||
}
|
@@ -1,204 +0,0 @@
|
||||
use std::future::Future;
|
||||
use std::marker::PhantomData;
|
||||
use std::net::SocketAddr;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use futures_util::future::{ok, Either, Ready};
|
||||
use trust_dns_resolver::TokioAsyncResolver as AsyncResolver;
|
||||
use trust_dns_resolver::{error::ResolveError, lookup_ip::LookupIp};
|
||||
|
||||
use crate::connect::{Address, Connect};
|
||||
use crate::error::ConnectError;
|
||||
use crate::get_default_resolver;
|
||||
|
||||
/// DNS Resolver Service factory
|
||||
pub struct ResolverFactory<T> {
|
||||
resolver: Option<AsyncResolver>,
|
||||
_t: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> ResolverFactory<T> {
|
||||
/// Create new resolver instance with custom configuration and options.
|
||||
pub fn new(resolver: AsyncResolver) -> Self {
|
||||
ResolverFactory {
|
||||
resolver: Some(resolver),
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn service(&self) -> Resolver<T> {
|
||||
Resolver {
|
||||
resolver: self.resolver.clone(),
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for ResolverFactory<T> {
|
||||
fn default() -> Self {
|
||||
ResolverFactory {
|
||||
resolver: None,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for ResolverFactory<T> {
|
||||
fn clone(&self) -> Self {
|
||||
ResolverFactory {
|
||||
resolver: self.resolver.clone(),
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Address> ServiceFactory<Connect<T>> for ResolverFactory<T> {
|
||||
type Response = Connect<T>;
|
||||
type Error = ConnectError;
|
||||
type Config = ();
|
||||
type Service = Resolver<T>;
|
||||
type InitError = ();
|
||||
type Future = Ready<Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
ok(self.service())
|
||||
}
|
||||
}
|
||||
|
||||
/// DNS Resolver Service
|
||||
pub struct Resolver<T> {
|
||||
resolver: Option<AsyncResolver>,
|
||||
_t: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> Resolver<T> {
|
||||
/// Create new resolver instance with custom configuration and options.
|
||||
pub fn new(resolver: AsyncResolver) -> Self {
|
||||
Resolver {
|
||||
resolver: Some(resolver),
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for Resolver<T> {
|
||||
fn default() -> Self {
|
||||
Resolver {
|
||||
resolver: None,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for Resolver<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Resolver {
|
||||
resolver: self.resolver.clone(),
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Address> Service<Connect<T>> for Resolver<T> {
|
||||
type Response = Connect<T>;
|
||||
type Error = ConnectError;
|
||||
#[allow(clippy::type_complexity)]
|
||||
type Future = Either<
|
||||
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>,
|
||||
Ready<Result<Connect<T>, Self::Error>>,
|
||||
>;
|
||||
|
||||
actix_service::always_ready!();
|
||||
|
||||
fn call(&mut self, mut req: Connect<T>) -> Self::Future {
|
||||
if req.addr.is_some() {
|
||||
Either::Right(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 {
|
||||
let resolver = self.resolver.as_ref().map(AsyncResolver::clone);
|
||||
Either::Left(Box::pin(async move {
|
||||
trace!("DNS resolver: resolving host {:?}", req.host());
|
||||
let resolver = if let Some(resolver) = resolver {
|
||||
resolver
|
||||
} else {
|
||||
get_default_resolver()
|
||||
.await
|
||||
.expect("Failed to get default resolver")
|
||||
};
|
||||
ResolverFuture::new(req, &resolver).await
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type LookupIpFuture = Pin<Box<dyn Future<Output = Result<LookupIp, ResolveError>>>>;
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Resolver future
|
||||
pub struct ResolverFuture<T: Address> {
|
||||
req: Option<Connect<T>>,
|
||||
lookup: LookupIpFuture,
|
||||
}
|
||||
|
||||
impl<T: Address> ResolverFuture<T> {
|
||||
pub fn new(req: Connect<T>, resolver: &AsyncResolver) -> Self {
|
||||
let host = if let Some(host) = req.host().splitn(2, ':').next() {
|
||||
host
|
||||
} else {
|
||||
req.host()
|
||||
};
|
||||
|
||||
// Clone data to be moved to the lookup future
|
||||
let host_clone = host.to_owned();
|
||||
let resolver_clone = resolver.clone();
|
||||
|
||||
ResolverFuture {
|
||||
lookup: Box::pin(async move {
|
||||
let resolver = resolver_clone;
|
||||
resolver.lookup_ip(host_clone).await
|
||||
}),
|
||||
req: Some(req),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Address> Future for ResolverFuture<T> {
|
||||
type Output = Result<Connect<T>, ConnectError>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.get_mut();
|
||||
|
||||
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 req = req.set_addrs(ips.iter().map(|ip| SocketAddr::new(ip, port)));
|
||||
|
||||
trace!(
|
||||
"DNS resolver: host {:?} resolved to {:?}",
|
||||
req.host(),
|
||||
req.addrs()
|
||||
);
|
||||
|
||||
if req.addr.is_none() {
|
||||
Poll::Ready(Err(ConnectError::NoRecords))
|
||||
} else {
|
||||
Poll::Ready(Ok(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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,225 +0,0 @@
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use actix_rt::net::TcpStream;
|
||||
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::connector::{TcpConnector, TcpConnectorFactory};
|
||||
use crate::error::ConnectError;
|
||||
use crate::resolve::{Resolver, ResolverFactory};
|
||||
|
||||
pub struct ConnectServiceFactory<T> {
|
||||
tcp: TcpConnectorFactory<T>,
|
||||
resolver: ResolverFactory<T>,
|
||||
}
|
||||
|
||||
impl<T> ConnectServiceFactory<T> {
|
||||
/// Construct new ConnectService factory
|
||||
pub fn new() -> Self {
|
||||
ConnectServiceFactory {
|
||||
tcp: TcpConnectorFactory::default(),
|
||||
resolver: ResolverFactory::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct new connect service with custom dns resolver
|
||||
pub fn with_resolver(resolver: AsyncResolver) -> Self {
|
||||
ConnectServiceFactory {
|
||||
tcp: TcpConnectorFactory::default(),
|
||||
resolver: ResolverFactory::new(resolver),
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct new service
|
||||
pub fn service(&self) -> ConnectService<T> {
|
||||
ConnectService {
|
||||
tcp: self.tcp.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> {
|
||||
fn default() -> Self {
|
||||
ConnectServiceFactory {
|
||||
tcp: TcpConnectorFactory::default(),
|
||||
resolver: ResolverFactory::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for ConnectServiceFactory<T> {
|
||||
fn clone(&self) -> Self {
|
||||
ConnectServiceFactory {
|
||||
tcp: self.tcp.clone(),
|
||||
resolver: self.resolver.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Address> ServiceFactory<Connect<T>> for ConnectServiceFactory<T> {
|
||||
type Response = Connection<T, TcpStream>;
|
||||
type Error = ConnectError;
|
||||
type Config = ();
|
||||
type Service = ConnectService<T>;
|
||||
type InitError = ();
|
||||
type Future = Ready<Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
ok(self.service())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ConnectService<T> {
|
||||
tcp: TcpConnector<T>,
|
||||
resolver: Resolver<T>,
|
||||
}
|
||||
|
||||
impl<T: Address> Service<Connect<T>> for ConnectService<T> {
|
||||
type Response = Connection<T, TcpStream>;
|
||||
type Error = ConnectError;
|
||||
type Future = ConnectServiceResponse<T>;
|
||||
|
||||
actix_service::always_ready!();
|
||||
|
||||
fn call(&mut self, req: Connect<T>) -> Self::Future {
|
||||
ConnectServiceResponse {
|
||||
state: ConnectState::Resolve(self.resolver.call(req)),
|
||||
tcp: self.tcp.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ConnectState<T: Address> {
|
||||
Resolve(<Resolver<T> as Service<Connect<T>>>::Future),
|
||||
Connect(<TcpConnector<T> as Service<Connect<T>>>::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> {
|
||||
state: ConnectState<T>,
|
||||
tcp: TcpConnector<T>,
|
||||
}
|
||||
|
||||
impl<T: Address> Future for ConnectServiceResponse<T> {
|
||||
type Output = Result<Connection<T, TcpStream>, ConnectError>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
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<Connect<T>> for TcpConnectService<T> {
|
||||
type Response = TcpStream;
|
||||
type Error = ConnectError;
|
||||
type Future = TcpConnectServiceResponse<T>;
|
||||
|
||||
actix_service::always_ready!();
|
||||
|
||||
fn call(&mut self, req: Connect<T>) -> Self::Future {
|
||||
TcpConnectServiceResponse {
|
||||
state: TcpConnectState::Resolve(self.resolver.call(req)),
|
||||
tcp: self.tcp.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum TcpConnectState<T: Address> {
|
||||
Resolve(<Resolver<T> as Service<Connect<T>>>::Future),
|
||||
Connect(<TcpConnector<T> as Service<Connect<T>>>::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!(),
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,7 +0,0 @@
|
||||
//! SSL Services
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
pub mod openssl;
|
||||
|
||||
#[cfg(feature = "rustls")]
|
||||
pub mod rustls;
|
@@ -1,264 +0,0 @@
|
||||
use std::future::Future;
|
||||
use std::marker::PhantomData;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
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_rt::net::TcpStream;
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use futures_util::future::{err, ok, Either, FutureExt, LocalBoxFuture, Ready};
|
||||
use trust_dns_resolver::TokioAsyncResolver as AsyncResolver;
|
||||
|
||||
use crate::{
|
||||
Address, Connect, ConnectError, ConnectService, ConnectServiceFactory, Connection,
|
||||
};
|
||||
|
||||
/// OpenSSL connector factory
|
||||
pub struct OpensslConnector<T, U> {
|
||||
connector: SslConnector,
|
||||
_t: PhantomData<(T, U)>,
|
||||
}
|
||||
|
||||
impl<T, U> OpensslConnector<T, U> {
|
||||
pub fn new(connector: SslConnector) -> Self {
|
||||
OpensslConnector {
|
||||
connector,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> OpensslConnector<T, U>
|
||||
where
|
||||
T: Address + 'static,
|
||||
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static,
|
||||
{
|
||||
pub fn service(connector: SslConnector) -> OpensslConnectorService<T, U> {
|
||||
OpensslConnectorService {
|
||||
connector,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Clone for OpensslConnector<T, U> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
connector: self.connector.clone(),
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> ServiceFactory for OpensslConnector<T, U>
|
||||
where
|
||||
T: Address + 'static,
|
||||
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static,
|
||||
{
|
||||
type Request = Connection<T, U>;
|
||||
type Response = Connection<T, SslStream<U>>;
|
||||
type Error = io::Error;
|
||||
type Config = ();
|
||||
type Service = OpensslConnectorService<T, U>;
|
||||
type InitError = ();
|
||||
type Future = Ready<Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
ok(OpensslConnectorService {
|
||||
connector: self.connector.clone(),
|
||||
_t: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OpensslConnectorService<T, U> {
|
||||
connector: SslConnector,
|
||||
_t: PhantomData<(T, U)>,
|
||||
}
|
||||
|
||||
impl<T, U> Clone for OpensslConnectorService<T, U> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
connector: self.connector.clone(),
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Service for OpensslConnectorService<T, U>
|
||||
where
|
||||
T: Address + 'static,
|
||||
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static,
|
||||
{
|
||||
type Request = Connection<T, U>;
|
||||
type Response = Connection<T, SslStream<U>>;
|
||||
type Error = io::Error;
|
||||
#[allow(clippy::type_complexity)]
|
||||
type Future = Either<ConnectAsyncExt<T, U>, Ready<Result<Self::Response, Self::Error>>>;
|
||||
|
||||
actix_service::always_ready!();
|
||||
|
||||
fn call(&mut self, stream: Connection<T, U>) -> Self::Future {
|
||||
trace!("SSL Handshake start for: {:?}", stream.host());
|
||||
let (io, stream) = stream.replace(());
|
||||
let host = stream.host().to_string();
|
||||
|
||||
match self.connector.configure() {
|
||||
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> {
|
||||
fut: LocalBoxFuture<'static, Result<SslStream<U>, HandshakeError<U>>>,
|
||||
stream: Option<Connection<T, ()>>,
|
||||
_t: PhantomData<U>,
|
||||
}
|
||||
|
||||
impl<T: Address, U> Future for ConnectAsyncExt<T, U>
|
||||
where
|
||||
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static,
|
||||
{
|
||||
type Output = Result<Connection<T, SslStream<U>>, io::Error>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.get_mut();
|
||||
|
||||
match Pin::new(&mut this.fut).poll(cx) {
|
||||
Poll::Ready(Ok(stream)) => {
|
||||
let s = this.stream.take().unwrap();
|
||||
trace!("SSL Handshake success: {:?}", s.host());
|
||||
Poll::Ready(Ok(s.replace(stream).1))
|
||||
}
|
||||
Poll::Ready(Err(e)) => {
|
||||
trace!("SSL Handshake error: {:?}", e);
|
||||
Poll::Ready(Err(io::Error::new(io::ErrorKind::Other, format!("{}", e))))
|
||||
}
|
||||
Poll::Pending => Poll::Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OpensslConnectServiceFactory<T> {
|
||||
tcp: ConnectServiceFactory<T>,
|
||||
openssl: OpensslConnector<T, TcpStream>,
|
||||
}
|
||||
|
||||
impl<T> OpensslConnectServiceFactory<T> {
|
||||
/// Construct new OpensslConnectService factory
|
||||
pub fn new(connector: SslConnector) -> Self {
|
||||
OpensslConnectServiceFactory {
|
||||
tcp: ConnectServiceFactory::default(),
|
||||
openssl: OpensslConnector::new(connector),
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct new connect service with custom DNS resolver
|
||||
pub fn with_resolver(connector: SslConnector, resolver: AsyncResolver) -> Self {
|
||||
OpensslConnectServiceFactory {
|
||||
tcp: ConnectServiceFactory::with_resolver(resolver),
|
||||
openssl: OpensslConnector::new(connector),
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct OpenSSL connect service
|
||||
pub fn service(&self) -> OpensslConnectService<T> {
|
||||
OpensslConnectService {
|
||||
tcp: self.tcp.service(),
|
||||
openssl: OpensslConnectorService {
|
||||
connector: self.openssl.connector.clone(),
|
||||
_t: PhantomData,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for OpensslConnectServiceFactory<T> {
|
||||
fn clone(&self) -> Self {
|
||||
OpensslConnectServiceFactory {
|
||||
tcp: self.tcp.clone(),
|
||||
openssl: self.openssl.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Address + 'static> ServiceFactory for OpensslConnectServiceFactory<T> {
|
||||
type Request = Connect<T>;
|
||||
type Response = SslStream<TcpStream>;
|
||||
type Error = ConnectError;
|
||||
type Config = ();
|
||||
type Service = OpensslConnectService<T>;
|
||||
type InitError = ();
|
||||
type Future = Ready<Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
ok(self.service())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OpensslConnectService<T> {
|
||||
tcp: ConnectService<T>,
|
||||
openssl: OpensslConnectorService<T, TcpStream>,
|
||||
}
|
||||
|
||||
impl<T: Address + 'static> Service for OpensslConnectService<T> {
|
||||
type Request = Connect<T>;
|
||||
type Response = SslStream<TcpStream>;
|
||||
type Error = ConnectError;
|
||||
type Future = OpensslConnectServiceResponse<T>;
|
||||
|
||||
actix_service::always_ready!();
|
||||
|
||||
fn call(&mut self, req: Connect<T>) -> Self::Future {
|
||||
OpensslConnectServiceResponse {
|
||||
fut1: Some(self.tcp.call(req)),
|
||||
fut2: None,
|
||||
openssl: self.openssl.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OpensslConnectServiceResponse<T: Address + 'static> {
|
||||
fut1: Option<<ConnectService<T> as Service>::Future>,
|
||||
fut2: Option<<OpensslConnectorService<T, TcpStream> as Service>::Future>,
|
||||
openssl: OpensslConnectorService<T, TcpStream>,
|
||||
}
|
||||
|
||||
impl<T: Address> Future for OpensslConnectServiceResponse<T> {
|
||||
type Output = Result<SslStream<TcpStream>, ConnectError>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
if let Some(ref mut fut) = self.fut1 {
|
||||
match futures_util::ready!(Pin::new(fut).poll(cx)) {
|
||||
Ok(res) => {
|
||||
let _ = self.fut1.take();
|
||||
self.fut2 = Some(self.openssl.call(res));
|
||||
}
|
||||
Err(e) => return Poll::Ready(Err(e)),
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref mut fut) = self.fut2 {
|
||||
match futures_util::ready!(Pin::new(fut).poll(cx)) {
|
||||
Ok(connect) => Poll::Ready(Ok(connect.into_parts().0)),
|
||||
Err(e) => Poll::Ready(Err(ConnectError::Io(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
e,
|
||||
)))),
|
||||
}
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,134 +0,0 @@
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
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_service::{Service, ServiceFactory};
|
||||
use futures_util::future::{ok, Ready};
|
||||
use tokio_rustls::{Connect, TlsConnector};
|
||||
use webpki::DNSNameRef;
|
||||
|
||||
use crate::{Address, Connection};
|
||||
|
||||
/// Rustls connector factory
|
||||
pub struct RustlsConnector<T, U> {
|
||||
connector: Arc<ClientConfig>,
|
||||
_t: PhantomData<(T, U)>,
|
||||
}
|
||||
|
||||
impl<T, U> RustlsConnector<T, U> {
|
||||
pub fn new(connector: Arc<ClientConfig>) -> Self {
|
||||
RustlsConnector {
|
||||
connector,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> RustlsConnector<T, U>
|
||||
where
|
||||
T: Address,
|
||||
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug,
|
||||
{
|
||||
pub fn service(connector: Arc<ClientConfig>) -> RustlsConnectorService<T, U> {
|
||||
RustlsConnectorService {
|
||||
connector,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Clone for RustlsConnector<T, U> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
connector: self.connector.clone(),
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Address, U> ServiceFactory for RustlsConnector<T, U>
|
||||
where
|
||||
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug,
|
||||
{
|
||||
type Request = Connection<T, U>;
|
||||
type Response = Connection<T, TlsStream<U>>;
|
||||
type Error = std::io::Error;
|
||||
type Config = ();
|
||||
type Service = RustlsConnectorService<T, U>;
|
||||
type InitError = ();
|
||||
type Future = Ready<Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
ok(RustlsConnectorService {
|
||||
connector: self.connector.clone(),
|
||||
_t: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RustlsConnectorService<T, U> {
|
||||
connector: Arc<ClientConfig>,
|
||||
_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>
|
||||
where
|
||||
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug,
|
||||
{
|
||||
type Request = Connection<T, U>;
|
||||
type Response = Connection<T, TlsStream<U>>;
|
||||
type Error = std::io::Error;
|
||||
type Future = ConnectAsyncExt<T, U>;
|
||||
|
||||
actix_service::always_ready!();
|
||||
|
||||
fn call(&mut self, stream: Connection<T, U>) -> Self::Future {
|
||||
trace!("SSL Handshake start for: {:?}", stream.host());
|
||||
let (io, stream) = stream.replace(());
|
||||
let host = DNSNameRef::try_from_ascii_str(stream.host())
|
||||
.expect("rustls currently only handles hostname-based connections. See https://github.com/briansmith/webpki/issues/54");
|
||||
ConnectAsyncExt {
|
||||
fut: TlsConnector::from(self.connector.clone()).connect(host, io),
|
||||
stream: Some(stream),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConnectAsyncExt<T, U> {
|
||||
fut: Connect<U>,
|
||||
stream: Option<Connection<T, ()>>,
|
||||
}
|
||||
|
||||
impl<T: Address, U> Future for ConnectAsyncExt<T, U>
|
||||
where
|
||||
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug,
|
||||
{
|
||||
type Output = Result<Connection<T, TlsStream<U>>, std::io::Error>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.get_mut();
|
||||
Poll::Ready(
|
||||
futures_util::ready!(Pin::new(&mut this.fut).poll(cx)).map(|stream| {
|
||||
let s = this.stream.take().unwrap();
|
||||
trace!("SSL Handshake success: {:?}", s.host());
|
||||
s.replace(stream).1
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
@@ -1,37 +0,0 @@
|
||||
use http::Uri;
|
||||
|
||||
use crate::Address;
|
||||
|
||||
impl Address for Uri {
|
||||
fn host(&self) -> &str {
|
||||
self.host().unwrap_or("")
|
||||
}
|
||||
|
||||
fn port(&self) -> Option<u16> {
|
||||
if let Some(port) = self.port_u16() {
|
||||
Some(port)
|
||||
} else {
|
||||
port(self.scheme_str())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: load data from file
|
||||
fn port(scheme: Option<&str>) -> Option<u16> {
|
||||
if let Some(scheme) = scheme {
|
||||
match scheme {
|
||||
"http" => Some(80),
|
||||
"https" => Some(443),
|
||||
"ws" => Some(80),
|
||||
"wss" => Some(443),
|
||||
"amqp" => Some(5672),
|
||||
"amqps" => Some(5671),
|
||||
"sb" => Some(5671),
|
||||
"mqtt" => Some(1883),
|
||||
"mqtts" => Some(8883),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
@@ -1,127 +0,0 @@
|
||||
use std::io;
|
||||
|
||||
use actix_codec::{BytesCodec, Framed};
|
||||
use actix_rt::net::TcpStream;
|
||||
use actix_service::{fn_service, Service, ServiceFactory};
|
||||
use actix_testing::TestServer;
|
||||
use bytes::Bytes;
|
||||
use futures_util::sink::SinkExt;
|
||||
|
||||
use actix_connect::resolver::{ResolverConfig, ResolverOpts};
|
||||
use actix_connect::Connect;
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
#[actix_rt::test]
|
||||
async fn test_string() {
|
||||
let srv = TestServer::with(|| {
|
||||
fn_service(|io: TcpStream| async {
|
||||
let mut framed = Framed::new(io, BytesCodec);
|
||||
framed.send(Bytes::from_static(b"test")).await?;
|
||||
Ok::<_, io::Error>(())
|
||||
})
|
||||
});
|
||||
|
||||
let mut conn = actix_connect::default_connector();
|
||||
let addr = format!("localhost:{}", srv.port());
|
||||
let con = conn.call(addr.into()).await.unwrap();
|
||||
assert_eq!(con.peer_addr().unwrap(), srv.addr());
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls")]
|
||||
#[actix_rt::test]
|
||||
async fn test_rustls_string() {
|
||||
let srv = TestServer::with(|| {
|
||||
fn_service(|io: TcpStream| async {
|
||||
let mut framed = Framed::new(io, BytesCodec);
|
||||
framed.send(Bytes::from_static(b"test")).await?;
|
||||
Ok::<_, io::Error>(())
|
||||
})
|
||||
});
|
||||
|
||||
let mut conn = actix_connect::default_connector();
|
||||
let addr = format!("localhost:{}", srv.port());
|
||||
let con = conn.call(addr.into()).await.unwrap();
|
||||
assert_eq!(con.peer_addr().unwrap(), srv.addr());
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_static_str() {
|
||||
let srv = TestServer::with(|| {
|
||||
fn_service(|io: TcpStream| async {
|
||||
let mut framed = Framed::new(io, BytesCodec);
|
||||
framed.send(Bytes::from_static(b"test")).await?;
|
||||
Ok::<_, io::Error>(())
|
||||
})
|
||||
});
|
||||
|
||||
let resolver = actix_connect::start_default_resolver().await.unwrap();
|
||||
let mut conn = actix_connect::new_connector(resolver.clone());
|
||||
|
||||
let con = conn.call(Connect::with("10", srv.addr())).await.unwrap();
|
||||
assert_eq!(con.peer_addr().unwrap(), srv.addr());
|
||||
|
||||
let connect = Connect::new(srv.host().to_owned());
|
||||
let mut conn = actix_connect::new_connector(resolver);
|
||||
let con = conn.call(connect).await;
|
||||
assert!(con.is_err());
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_new_service() {
|
||||
let srv = TestServer::with(|| {
|
||||
fn_service(|io: TcpStream| async {
|
||||
let mut framed = Framed::new(io, BytesCodec);
|
||||
framed.send(Bytes::from_static(b"test")).await?;
|
||||
Ok::<_, io::Error>(())
|
||||
})
|
||||
});
|
||||
|
||||
let resolver =
|
||||
actix_connect::start_resolver(ResolverConfig::default(), ResolverOpts::default())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let factory = actix_connect::new_connector_factory(resolver);
|
||||
|
||||
let mut conn = factory.new_service(()).await.unwrap();
|
||||
let con = conn.call(Connect::with("10", srv.addr())).await.unwrap();
|
||||
assert_eq!(con.peer_addr().unwrap(), srv.addr());
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "openssl", feature = "uri"))]
|
||||
#[actix_rt::test]
|
||||
async fn test_openssl_uri() {
|
||||
use std::convert::TryFrom;
|
||||
|
||||
let srv = TestServer::with(|| {
|
||||
fn_service(|io: TcpStream| async {
|
||||
let mut framed = Framed::new(io, BytesCodec);
|
||||
framed.send(Bytes::from_static(b"test")).await?;
|
||||
Ok::<_, io::Error>(())
|
||||
})
|
||||
});
|
||||
|
||||
let mut conn = actix_connect::default_connector();
|
||||
let addr = http::Uri::try_from(format!("https://localhost:{}", srv.port())).unwrap();
|
||||
let con = conn.call(addr.into()).await.unwrap();
|
||||
assert_eq!(con.peer_addr().unwrap(), srv.addr());
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "rustls", feature = "uri"))]
|
||||
#[actix_rt::test]
|
||||
async fn test_rustls_uri() {
|
||||
use std::convert::TryFrom;
|
||||
|
||||
let srv = TestServer::with(|| {
|
||||
fn_service(|io: TcpStream| async {
|
||||
let mut framed = Framed::new(io, BytesCodec);
|
||||
framed.send(Bytes::from_static(b"test")).await?;
|
||||
Ok::<_, io::Error>(())
|
||||
})
|
||||
});
|
||||
|
||||
let mut conn = actix_connect::default_connector();
|
||||
let addr = http::Uri::try_from(format!("https://localhost:{}", srv.port())).unwrap();
|
||||
let con = conn.call(addr.into()).await.unwrap();
|
||||
assert_eq!(con.peer_addr().unwrap(), srv.addr());
|
||||
}
|
1
actix-macros/.gitignore
vendored
1
actix-macros/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
/wip
|
@@ -1,13 +0,0 @@
|
||||
# CHANGES
|
||||
|
||||
## 0.1.3 - 2020-12-3
|
||||
|
||||
* Add `actix-reexport` feature
|
||||
|
||||
## 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
|
@@ -1,26 +0,0 @@
|
||||
[package]
|
||||
name = "actix-macros"
|
||||
version = "0.1.3"
|
||||
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"] }
|
||||
|
||||
[features]
|
||||
actix-reexport = []
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "1.0"
|
||||
|
||||
futures-util = { version = "0.3", default-features = false }
|
||||
trybuild = "1"
|
@@ -1 +0,0 @@
|
||||
../LICENSE-APACHE
|
@@ -1 +0,0 @@
|
||||
../LICENSE-MIT
|
@@ -1,116 +0,0 @@
|
||||
//! Macros for use with Tokio
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
|
||||
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;
|
||||
|
||||
if cfg!(feature = "actix-reexport") {
|
||||
(quote! {
|
||||
#(#attrs)*
|
||||
#vis #sig {
|
||||
actix::System::new(stringify!(#name))
|
||||
.block_on(async move { #body })
|
||||
}
|
||||
})
|
||||
.into()
|
||||
} else {
|
||||
(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()
|
||||
}
|
@@ -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");
|
||||
}
|
@@ -1,4 +0,0 @@
|
||||
#[actix_rt::main]
|
||||
async fn main() {
|
||||
println!("Hello world");
|
||||
}
|
@@ -1,4 +0,0 @@
|
||||
#[actix_rt::main]
|
||||
fn main() {
|
||||
futures_util::future::ready(()).await
|
||||
}
|
@@ -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`
|
@@ -1,6 +0,0 @@
|
||||
#[actix_rt::test]
|
||||
async fn my_test() {
|
||||
assert!(true);
|
||||
}
|
||||
|
||||
fn main() {}
|
@@ -1,7 +0,0 @@
|
||||
#[actix_rt::test]
|
||||
#[should_panic]
|
||||
async fn my_test() {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn main() {}
|
@@ -1,140 +0,0 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2020-xx-xx
|
||||
|
||||
|
||||
## 2.0.0-beta.1 - 2020-12-28
|
||||
### Added
|
||||
* Add `System::attach_to_tokio` method. [#173]
|
||||
|
||||
### Changed
|
||||
* Update `tokio` dependency to `1.0`. [#236]
|
||||
* Rename `time` module `delay_for` to `sleep`, `delay_until` to `sleep_until`, `Delay` to `Sleep`
|
||||
to stay aligned with Tokio's naming. [#236]
|
||||
* Remove `'static` lifetime requirement for `Runtime::block_on` and `SystemRunner::block_on`.
|
||||
* These methods now accept `&self` when calling. [#236]
|
||||
* Remove `'static` lifetime requirement for `System::run` and `Builder::run`. [#236]
|
||||
* `Arbiter::spawn` now panics when `System` is not in scope. [#207]
|
||||
|
||||
### Fixed
|
||||
* Fix work load issue by removing `PENDING` thread local. [#207]
|
||||
|
||||
[#207]: https://github.com/actix/actix-net/pull/207
|
||||
[#236]: https://github.com/actix/actix-net/pull/236
|
||||
|
||||
## [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
|
||||
|
||||
### Changed
|
||||
|
||||
* Avoid a copy of the Future when initializing the Box. #29
|
||||
|
||||
|
||||
## [0.2.3] - 2019-06-22
|
||||
|
||||
### Added
|
||||
|
||||
* Allow to start System using exsiting CurrentThread Handle #22
|
||||
|
||||
|
||||
## [0.2.2] - 2019-03-28
|
||||
|
||||
### Changed
|
||||
|
||||
* Moved `blocking` module to `actix-threadpool` crate
|
||||
|
||||
|
||||
## [0.2.1] - 2019-03-11
|
||||
|
||||
### Added
|
||||
|
||||
* Added `blocking` module
|
||||
|
||||
* Arbiter::exec_fn - execute fn on the arbiter's thread
|
||||
|
||||
* Arbiter::exec - execute fn on the arbiter's thread and wait result
|
||||
|
||||
|
||||
## [0.2.0] - 2019-03-06
|
||||
|
||||
* `run` method returns `io::Result<()>`
|
||||
|
||||
* Removed `Handle`
|
||||
|
||||
|
||||
## [0.1.0] - 2018-12-09
|
||||
|
||||
* Initial release
|
@@ -1,21 +0,0 @@
|
||||
[package]
|
||||
name = "actix-rt"
|
||||
version = "2.0.0-beta.1"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Tokio-based single-thread async runtime for the Actix ecosystem"
|
||||
keywords = ["network", "framework", "async", "futures"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-net.git"
|
||||
documentation = "https://docs.rs/actix-rt/"
|
||||
categories = ["network-programming", "asynchronous"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
name = "actix_rt"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
actix-macros = "0.1.0"
|
||||
|
||||
tokio = { version = "1", features = ["rt", "net", "signal", "sync", "time"] }
|
@@ -1 +0,0 @@
|
||||
../LICENSE-APACHE
|
@@ -1 +0,0 @@
|
||||
../LICENSE-MIT
|
@@ -1,431 +0,0 @@
|
||||
use std::any::{Any, TypeId};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::task::{Context, Poll};
|
||||
use std::{fmt, thread};
|
||||
|
||||
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
|
||||
use tokio::sync::oneshot::{channel, error::RecvError as Canceled, Sender};
|
||||
// use futures_util::stream::FuturesUnordered;
|
||||
// use tokio::task::JoinHandle;
|
||||
// use tokio::stream::StreamExt;
|
||||
use tokio::task::LocalSet;
|
||||
|
||||
use crate::runtime::Runtime;
|
||||
use crate::system::System;
|
||||
|
||||
thread_local!(
|
||||
static ADDR: RefCell<Option<Arbiter>> = RefCell::new(None);
|
||||
// TODO: Commented out code are for Arbiter::local_join function.
|
||||
// It can be safely removed if this function is not used in actix-*.
|
||||
//
|
||||
// /// stores join handle for spawned async tasks.
|
||||
// static HANDLE: RefCell<FuturesUnordered<JoinHandle<()>>> =
|
||||
// RefCell::new(FuturesUnordered::new());
|
||||
static STORAGE: RefCell<HashMap<TypeId, Box<dyn Any>>> = RefCell::new(HashMap::new());
|
||||
);
|
||||
|
||||
pub(crate) static COUNT: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
pub(crate) enum ArbiterCommand {
|
||||
Stop,
|
||||
Execute(Box<dyn Future<Output = ()> + Unpin + Send>),
|
||||
ExecuteFn(Box<dyn FnExec>),
|
||||
}
|
||||
|
||||
impl fmt::Debug for ArbiterCommand {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ArbiterCommand::Stop => write!(f, "ArbiterCommand::Stop"),
|
||||
ArbiterCommand::Execute(_) => write!(f, "ArbiterCommand::Execute"),
|
||||
ArbiterCommand::ExecuteFn(_) => write!(f, "ArbiterCommand::ExecuteFn"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Arbiters provide an asynchronous execution environment for actors, functions
|
||||
/// and futures. When an Arbiter is created, it spawns a new OS thread, and
|
||||
/// hosts an event loop. Some Arbiter functions execute on the current thread.
|
||||
pub struct Arbiter {
|
||||
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 {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Arbiter {
|
||||
pub(crate) fn new_system(local: &LocalSet) -> Self {
|
||||
let (tx, rx) = unbounded_channel();
|
||||
|
||||
let arb = Arbiter::with_sender(tx);
|
||||
ADDR.with(|cell| *cell.borrow_mut() = Some(arb.clone()));
|
||||
STORAGE.with(|cell| cell.borrow_mut().clear());
|
||||
|
||||
local.spawn_local(ArbiterController { rx });
|
||||
|
||||
arb
|
||||
}
|
||||
|
||||
/// Returns the current thread's arbiter's address. If no Arbiter is present, then this
|
||||
/// function will panic!
|
||||
pub fn current() -> Arbiter {
|
||||
ADDR.with(|cell| match *cell.borrow() {
|
||||
Some(ref addr) => addr.clone(),
|
||||
None => panic!("Arbiter is not running"),
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if current arbiter is running.
|
||||
#[deprecated(note = "Thread local variables for running state of Arbiter is removed")]
|
||||
pub fn is_running() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Stop arbiter from continuing it's event loop.
|
||||
pub fn stop(&self) {
|
||||
let _ = self.sender.send(ArbiterCommand::Stop);
|
||||
}
|
||||
|
||||
/// Spawn new thread and run event loop in spawned thread.
|
||||
/// Returns address of newly created arbiter.
|
||||
pub fn new() -> Arbiter {
|
||||
let id = COUNT.fetch_add(1, Ordering::Relaxed);
|
||||
let name = format!("actix-rt:worker:{}", id);
|
||||
let sys = System::current();
|
||||
let (tx, rx) = unbounded_channel();
|
||||
|
||||
let handle = thread::Builder::new()
|
||||
.name(name.clone())
|
||||
.spawn({
|
||||
let tx = tx.clone();
|
||||
move || {
|
||||
let rt = Runtime::new().expect("Can not create Runtime");
|
||||
let arb = Arbiter::with_sender(tx);
|
||||
|
||||
STORAGE.with(|cell| cell.borrow_mut().clear());
|
||||
|
||||
System::set_current(sys);
|
||||
|
||||
ADDR.with(|cell| *cell.borrow_mut() = Some(arb.clone()));
|
||||
|
||||
// register arbiter
|
||||
let _ = System::current()
|
||||
.sys()
|
||||
.send(SystemCommand::RegisterArbiter(id, arb));
|
||||
|
||||
// start arbiter controller
|
||||
// run loop
|
||||
rt.block_on(ArbiterController { rx });
|
||||
|
||||
// unregister arbiter
|
||||
let _ = System::current()
|
||||
.sys()
|
||||
.send(SystemCommand::UnregisterArbiter(id));
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|err| {
|
||||
panic!("Cannot spawn an arbiter's thread {:?}: {:?}", &name, err)
|
||||
});
|
||||
|
||||
Arbiter {
|
||||
sender: tx,
|
||||
thread_handle: Some(handle),
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawn a future on the current thread. This does not create a new Arbiter
|
||||
/// or Arbiter address, it is simply a helper for spawning futures on the current
|
||||
/// thread.
|
||||
pub fn spawn<F>(future: F)
|
||||
where
|
||||
F: Future<Output = ()> + 'static,
|
||||
{
|
||||
// HANDLE.with(|handle| {
|
||||
// let handle = handle.borrow();
|
||||
// handle.push(tokio::task::spawn_local(future));
|
||||
// });
|
||||
// let _ = tokio::task::spawn_local(CleanupPending);
|
||||
let _ = tokio::task::spawn_local(future);
|
||||
}
|
||||
|
||||
/// Executes a future on the current thread. This does not create a new Arbiter
|
||||
/// or Arbiter address, it is simply a helper for executing futures on the current
|
||||
/// thread.
|
||||
pub fn spawn_fn<F, R>(f: F)
|
||||
where
|
||||
F: FnOnce() -> R + 'static,
|
||||
R: Future<Output = ()> + 'static,
|
||||
{
|
||||
Arbiter::spawn(async {
|
||||
f();
|
||||
})
|
||||
}
|
||||
|
||||
/// Send a future to the Arbiter's thread, and spawn it.
|
||||
pub fn send<F>(&self, future: F)
|
||||
where
|
||||
F: Future<Output = ()> + Send + Unpin + 'static,
|
||||
{
|
||||
let _ = self.sender.send(ArbiterCommand::Execute(Box::new(future)));
|
||||
}
|
||||
|
||||
/// Send a function to the Arbiter's thread, and execute it. Any result from the function
|
||||
/// is discarded.
|
||||
pub fn exec_fn<F>(&self, f: F)
|
||||
where
|
||||
F: FnOnce() + Send + 'static,
|
||||
{
|
||||
let _ = self
|
||||
.sender
|
||||
.send(ArbiterCommand::ExecuteFn(Box::new(move || {
|
||||
f();
|
||||
})));
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// to the Arbiters thread.
|
||||
pub fn exec<F, R>(&self, f: F) -> impl Future<Output = Result<R, Canceled>>
|
||||
where
|
||||
F: FnOnce() -> R + Send + 'static,
|
||||
R: Send + 'static,
|
||||
{
|
||||
let (tx, rx) = channel();
|
||||
let _ = self
|
||||
.sender
|
||||
.send(ArbiterCommand::ExecuteFn(Box::new(move || {
|
||||
if !tx.is_closed() {
|
||||
let _ = tx.send(f());
|
||||
}
|
||||
})));
|
||||
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.
|
||||
#[deprecated(since = "1.2.0", note = "Arbiter::local_join function is removed.")]
|
||||
pub async fn local_join() {
|
||||
// let handle = HANDLE.with(|fut| std::mem::take(&mut *fut.borrow_mut()));
|
||||
// async move {
|
||||
// handle.collect::<Vec<_>>().await;
|
||||
// }
|
||||
unimplemented!("Arbiter::local_join function is removed.")
|
||||
}
|
||||
}
|
||||
|
||||
// /// 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> {
|
||||
// HANDLE.with(move |handle| {
|
||||
// recycle_join_handle(&mut *handle.borrow_mut(), cx);
|
||||
// });
|
||||
//
|
||||
// Poll::Ready(())
|
||||
// }
|
||||
// }
|
||||
|
||||
struct ArbiterController {
|
||||
rx: UnboundedReceiver<ArbiterCommand>,
|
||||
}
|
||||
|
||||
impl Drop for ArbiterController {
|
||||
fn drop(&mut self) {
|
||||
if thread::panicking() {
|
||||
if System::current().stop_on_panic() {
|
||||
eprintln!("Panic in Arbiter thread, shutting down system.");
|
||||
System::current().stop_with_code(1)
|
||||
} else {
|
||||
eprintln!("Panic in Arbiter thread.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for ArbiterController {
|
||||
type Output = ();
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
loop {
|
||||
match Pin::new(&mut self.rx).poll_recv(cx) {
|
||||
Poll::Ready(None) => return Poll::Ready(()),
|
||||
Poll::Ready(Some(item)) => match item {
|
||||
ArbiterCommand::Stop => return Poll::Ready(()),
|
||||
ArbiterCommand::Execute(fut) => {
|
||||
// HANDLE.with(|handle| {
|
||||
// let mut handle = handle.borrow_mut();
|
||||
// handle.push(tokio::task::spawn_local(fut));
|
||||
// recycle_join_handle(&mut *handle, cx);
|
||||
// });
|
||||
tokio::task::spawn_local(fut);
|
||||
}
|
||||
ArbiterCommand::ExecuteFn(f) => {
|
||||
f.call_box();
|
||||
}
|
||||
},
|
||||
Poll::Pending => return Poll::Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fn recycle_join_handle(handle: &mut FuturesUnordered<JoinHandle<()>>, cx: &mut Context<'_>) {
|
||||
// let _ = Pin::new(&mut *handle).poll_next(cx);
|
||||
//
|
||||
// // Try to recycle more join handles and free up memory.
|
||||
// //
|
||||
// // this is a guess. The yield limit for FuturesUnordered is 32.
|
||||
// // So poll an extra 3 times would make the total poll below 128.
|
||||
// if handle.len() > 64 {
|
||||
// (0..3).for_each(|_| {
|
||||
// let _ = Pin::new(&mut *handle).poll_next(cx);
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum SystemCommand {
|
||||
Exit(i32),
|
||||
RegisterArbiter(usize, Arbiter),
|
||||
UnregisterArbiter(usize),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct SystemArbiter {
|
||||
stop: Option<Sender<i32>>,
|
||||
commands: UnboundedReceiver<SystemCommand>,
|
||||
arbiters: HashMap<usize, Arbiter>,
|
||||
}
|
||||
|
||||
impl SystemArbiter {
|
||||
pub(crate) fn new(stop: Sender<i32>, commands: UnboundedReceiver<SystemCommand>) -> Self {
|
||||
SystemArbiter {
|
||||
commands,
|
||||
stop: Some(stop),
|
||||
arbiters: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for SystemArbiter {
|
||||
type Output = ();
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
loop {
|
||||
match Pin::new(&mut self.commands).poll_recv(cx) {
|
||||
Poll::Ready(None) => return Poll::Ready(()),
|
||||
Poll::Ready(Some(cmd)) => match cmd {
|
||||
SystemCommand::Exit(code) => {
|
||||
// stop arbiters
|
||||
for arb in self.arbiters.values() {
|
||||
arb.stop();
|
||||
}
|
||||
// stop event loop
|
||||
if let Some(stop) = self.stop.take() {
|
||||
let _ = stop.send(code);
|
||||
}
|
||||
}
|
||||
SystemCommand::RegisterArbiter(name, hnd) => {
|
||||
self.arbiters.insert(name, hnd);
|
||||
}
|
||||
SystemCommand::UnregisterArbiter(name) => {
|
||||
self.arbiters.remove(&name);
|
||||
}
|
||||
},
|
||||
Poll::Pending => return Poll::Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FnExec: Send + 'static {
|
||||
fn call_box(self: Box<Self>);
|
||||
}
|
||||
|
||||
impl<F> FnExec for F
|
||||
where
|
||||
F: FnOnce() + Send + 'static,
|
||||
{
|
||||
#[allow(clippy::boxed_local)]
|
||||
fn call_box(self: Box<Self>) {
|
||||
(*self)()
|
||||
}
|
||||
}
|
@@ -1,183 +0,0 @@
|
||||
use std::borrow::Cow;
|
||||
use std::future::Future;
|
||||
use std::io;
|
||||
|
||||
use tokio::sync::mpsc::unbounded_channel;
|
||||
use tokio::sync::oneshot::{channel, Receiver};
|
||||
use tokio::task::LocalSet;
|
||||
|
||||
use crate::arbiter::{Arbiter, SystemArbiter};
|
||||
use crate::runtime::Runtime;
|
||||
use crate::system::System;
|
||||
|
||||
/// Builder struct for a actix runtime.
|
||||
///
|
||||
/// Either use `Builder::build` to create a system and start actors.
|
||||
/// Alternatively, use `Builder::run` to start the tokio runtime and
|
||||
/// run a function in its context.
|
||||
pub struct Builder {
|
||||
/// Name of the System. Defaults to "actix" if unset.
|
||||
name: Cow<'static, str>,
|
||||
|
||||
/// Whether the Arbiter will stop the whole System on uncaught panic. Defaults to false.
|
||||
stop_on_panic: bool,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
pub(crate) fn new() -> Self {
|
||||
Builder {
|
||||
name: Cow::Borrowed("actix"),
|
||||
stop_on_panic: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the name of the System.
|
||||
pub fn name<T: Into<String>>(mut self, name: T) -> Self {
|
||||
self.name = Cow::Owned(name.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the option 'stop_on_panic' which controls whether the System is stopped when an
|
||||
/// uncaught panic is thrown from a worker thread.
|
||||
///
|
||||
/// Defaults to false.
|
||||
pub fn stop_on_panic(mut self, stop_on_panic: bool) -> Self {
|
||||
self.stop_on_panic = stop_on_panic;
|
||||
self
|
||||
}
|
||||
|
||||
/// Create new System.
|
||||
///
|
||||
/// This method panics if it can not create tokio runtime
|
||||
pub fn build(self) -> SystemRunner {
|
||||
self.create_runtime(|| {})
|
||||
}
|
||||
|
||||
/// Create new System that can run asynchronously.
|
||||
///
|
||||
/// This method panics if it cannot start the system arbiter
|
||||
pub(crate) fn build_async(self, local: &LocalSet) -> AsyncSystemRunner {
|
||||
self.create_async_runtime(local)
|
||||
}
|
||||
|
||||
/// This function will start tokio runtime and will finish once the
|
||||
/// `System::stop()` message get called.
|
||||
/// Function `f` get called within tokio runtime context.
|
||||
pub fn run<F>(self, f: F) -> io::Result<()>
|
||||
where
|
||||
F: FnOnce(),
|
||||
{
|
||||
self.create_runtime(f).run()
|
||||
}
|
||||
|
||||
fn create_async_runtime(self, local: &LocalSet) -> AsyncSystemRunner {
|
||||
let (stop_tx, stop) = channel();
|
||||
let (sys_sender, sys_receiver) = unbounded_channel();
|
||||
|
||||
let system =
|
||||
System::construct(sys_sender, Arbiter::new_system(local), self.stop_on_panic);
|
||||
|
||||
// system arbiter
|
||||
let arb = SystemArbiter::new(stop_tx, sys_receiver);
|
||||
|
||||
// start the system arbiter
|
||||
let _ = local.spawn_local(arb);
|
||||
|
||||
AsyncSystemRunner { stop, system }
|
||||
}
|
||||
|
||||
fn create_runtime<F>(self, f: F) -> SystemRunner
|
||||
where
|
||||
F: FnOnce(),
|
||||
{
|
||||
let (stop_tx, stop) = channel();
|
||||
let (sys_sender, sys_receiver) = unbounded_channel();
|
||||
|
||||
let rt = Runtime::new().unwrap();
|
||||
|
||||
let system = System::construct(
|
||||
sys_sender,
|
||||
Arbiter::new_system(rt.local()),
|
||||
self.stop_on_panic,
|
||||
);
|
||||
|
||||
// system arbiter
|
||||
let arb = SystemArbiter::new(stop_tx, sys_receiver);
|
||||
|
||||
rt.spawn(arb);
|
||||
|
||||
// init system arbiter and run configuration method
|
||||
rt.block_on(async { f() });
|
||||
|
||||
SystemRunner { rt, stop, system }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct AsyncSystemRunner {
|
||||
stop: Receiver<i32>,
|
||||
system: System,
|
||||
}
|
||||
|
||||
impl AsyncSystemRunner {
|
||||
/// This function will start event loop and returns a future that
|
||||
/// resolves once the `System::stop()` function is called.
|
||||
pub(crate) fn run_nonblocking(self) -> impl Future<Output = Result<(), io::Error>> + Send {
|
||||
let AsyncSystemRunner { stop, .. } = self;
|
||||
|
||||
// run loop
|
||||
async {
|
||||
match stop.await {
|
||||
Ok(code) => {
|
||||
if code != 0 {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("Non-zero exit code: {}", code),
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Err(e) => Err(io::Error::new(io::ErrorKind::Other, e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper object that runs System's event loop
|
||||
#[must_use = "SystemRunner must be run"]
|
||||
#[derive(Debug)]
|
||||
pub struct SystemRunner {
|
||||
rt: Runtime,
|
||||
stop: Receiver<i32>,
|
||||
system: System,
|
||||
}
|
||||
|
||||
impl SystemRunner {
|
||||
/// This function will start event loop and will finish once the
|
||||
/// `System::stop()` function is called.
|
||||
pub fn run(self) -> io::Result<()> {
|
||||
let SystemRunner { rt, stop, .. } = self;
|
||||
|
||||
// run loop
|
||||
match rt.block_on(stop) {
|
||||
Ok(code) => {
|
||||
if code != 0 {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("Non-zero exit code: {}", code),
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Err(e) => Err(io::Error::new(io::ErrorKind::Other, e)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute a future and wait for result.
|
||||
#[inline]
|
||||
pub fn block_on<F: Future>(&self, fut: F) -> F::Output {
|
||||
self.rt.block_on(fut)
|
||||
}
|
||||
}
|
@@ -1,65 +0,0 @@
|
||||
//! Tokio-based single-thread async runtime for the Actix ecosystem.
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![allow(clippy::type_complexity)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
|
||||
use std::future::Future;
|
||||
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
pub use actix_macros::{main, test};
|
||||
|
||||
mod arbiter;
|
||||
mod builder;
|
||||
mod runtime;
|
||||
mod system;
|
||||
|
||||
pub use self::arbiter::Arbiter;
|
||||
pub use self::builder::{Builder, SystemRunner};
|
||||
pub use self::runtime::Runtime;
|
||||
pub use self::system::System;
|
||||
|
||||
/// Spawns a future on the current arbiter.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if actix system is not running.
|
||||
#[inline]
|
||||
pub fn spawn<F>(f: F)
|
||||
where
|
||||
F: Future<Output = ()> + 'static,
|
||||
{
|
||||
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::{interval, interval_at, Interval};
|
||||
pub use tokio::time::{sleep, sleep_until, Sleep};
|
||||
pub use tokio::time::{timeout, Timeout};
|
||||
}
|
@@ -1,94 +0,0 @@
|
||||
use std::future::Future;
|
||||
use std::io;
|
||||
use tokio::{runtime, task::LocalSet};
|
||||
|
||||
/// Single-threaded runtime provides a way to start reactor
|
||||
/// and runtime on the current thread.
|
||||
///
|
||||
/// See [module level][mod] documentation for more details.
|
||||
///
|
||||
/// [mod]: crate
|
||||
#[derive(Debug)]
|
||||
pub struct Runtime {
|
||||
local: LocalSet,
|
||||
rt: runtime::Runtime,
|
||||
}
|
||||
|
||||
impl Runtime {
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
/// Returns a new runtime initialized with default configuration values.
|
||||
pub fn new() -> io::Result<Runtime> {
|
||||
let rt = runtime::Builder::new_current_thread()
|
||||
.enable_io()
|
||||
.enable_time()
|
||||
.build()?;
|
||||
|
||||
Ok(Runtime {
|
||||
rt,
|
||||
local: LocalSet::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn local(&self) -> &LocalSet {
|
||||
&self.local
|
||||
}
|
||||
|
||||
/// Spawn a future onto the single-threaded runtime.
|
||||
///
|
||||
/// See [module level][mod] documentation for more details.
|
||||
///
|
||||
/// [mod]: crate
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// # use futures::{future, Future, Stream};
|
||||
/// use actix_rt::Runtime;
|
||||
///
|
||||
/// # fn dox() {
|
||||
/// // Create the runtime
|
||||
/// let rt = Runtime::new().unwrap();
|
||||
///
|
||||
/// // Spawn a future onto the runtime
|
||||
/// rt.spawn(future::lazy(|_| {
|
||||
/// println!("running on the runtime");
|
||||
/// }));
|
||||
/// # }
|
||||
/// # pub fn main() {}
|
||||
/// ```
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if the spawn fails. Failure occurs if the executor
|
||||
/// is currently at capacity and is unable to spawn a new future.
|
||||
pub fn spawn<F>(&self, future: F) -> &Self
|
||||
where
|
||||
F: Future<Output = ()> + 'static,
|
||||
{
|
||||
self.local.spawn_local(future);
|
||||
self
|
||||
}
|
||||
|
||||
/// Runs the provided future, blocking the current thread until the future
|
||||
/// completes.
|
||||
///
|
||||
/// This function can be used to synchronously block the current thread
|
||||
/// until the provided `future` has resolved either successfully or with an
|
||||
/// error. The result of the future is then returned from this function
|
||||
/// call.
|
||||
///
|
||||
/// Note that this function will **also** execute any spawned futures on the
|
||||
/// current thread, but will **not** block until these other spawned futures
|
||||
/// have completed. Once the function returns, any uncompleted futures
|
||||
/// remain pending in the `Runtime` instance. These futures will not run
|
||||
/// until `block_on` or `run` is called again.
|
||||
///
|
||||
/// The caller is responsible for ensuring that other spawned futures
|
||||
/// complete execution by calling `block_on` or `run`.
|
||||
pub fn block_on<F>(&self, f: F) -> F::Output
|
||||
where
|
||||
F: Future,
|
||||
{
|
||||
self.local.block_on(&self.rt, f)
|
||||
}
|
||||
}
|
@@ -1,261 +0,0 @@
|
||||
use std::cell::RefCell;
|
||||
use std::future::Future;
|
||||
use std::io;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
use tokio::task::LocalSet;
|
||||
|
||||
use crate::arbiter::{Arbiter, SystemCommand};
|
||||
use crate::builder::{Builder, SystemRunner};
|
||||
|
||||
static SYSTEM_COUNT: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
/// System is a runtime manager.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct System {
|
||||
id: usize,
|
||||
sys: UnboundedSender<SystemCommand>,
|
||||
arbiter: Arbiter,
|
||||
stop_on_panic: bool,
|
||||
}
|
||||
|
||||
thread_local!(
|
||||
static CURRENT: RefCell<Option<System>> = RefCell::new(None);
|
||||
);
|
||||
|
||||
impl System {
|
||||
/// Constructs new system and sets it as current
|
||||
pub(crate) fn construct(
|
||||
sys: UnboundedSender<SystemCommand>,
|
||||
arbiter: Arbiter,
|
||||
stop_on_panic: bool,
|
||||
) -> Self {
|
||||
let sys = System {
|
||||
sys,
|
||||
arbiter,
|
||||
stop_on_panic,
|
||||
id: SYSTEM_COUNT.fetch_add(1, Ordering::SeqCst),
|
||||
};
|
||||
System::set_current(sys.clone());
|
||||
sys
|
||||
}
|
||||
|
||||
/// Build a new system with a customized tokio runtime.
|
||||
///
|
||||
/// This allows to customize the runtime. See struct level docs on
|
||||
/// `Builder` for more information.
|
||||
pub fn builder() -> Builder {
|
||||
Builder::new()
|
||||
}
|
||||
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
/// Create new system.
|
||||
///
|
||||
/// This method panics if it can not create tokio runtime
|
||||
pub fn new<T: Into<String>>(name: T) -> SystemRunner {
|
||||
Self::builder().name(name).build()
|
||||
}
|
||||
|
||||
/// Create new system using provided tokio `LocalSet`.
|
||||
///
|
||||
/// This method panics if it can not spawn system arbiter
|
||||
///
|
||||
/// Note: This method uses provided `LocalSet` to create a `System` future only.
|
||||
/// All the [`Arbiter`]s will be started in separate threads using their own tokio `Runtime`s.
|
||||
/// It means that using this method currently it is impossible to make `actix-rt` work in the
|
||||
/// alternative `tokio` `Runtime`s (e.g. provided by [`tokio_compat`]).
|
||||
///
|
||||
/// [`tokio_compat`]: https://crates.io/crates/tokio-compat
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// use tokio::{runtime::Runtime, task::LocalSet};
|
||||
/// use actix_rt::System;
|
||||
/// use futures_util::future::try_join_all;
|
||||
///
|
||||
/// async fn run_application() {
|
||||
/// let first_task = tokio::spawn(async {
|
||||
/// // ...
|
||||
/// # println!("One task");
|
||||
/// # Ok::<(),()>(())
|
||||
/// });
|
||||
///
|
||||
/// let second_task = tokio::spawn(async {
|
||||
/// // ...
|
||||
/// # println!("Another task");
|
||||
/// # Ok::<(),()>(())
|
||||
/// });
|
||||
///
|
||||
/// try_join_all(vec![first_task, second_task])
|
||||
/// .await
|
||||
/// .expect("Some of the futures finished unexpectedly");
|
||||
/// }
|
||||
///
|
||||
///
|
||||
/// let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
/// .worker_threads(2)
|
||||
/// .enable_all()
|
||||
/// .build()
|
||||
/// .unwrap();
|
||||
///
|
||||
///
|
||||
/// let actix_system_task = LocalSet::new();
|
||||
/// let sys = System::run_in_tokio("actix-main-system", &actix_system_task);
|
||||
/// actix_system_task.spawn_local(sys);
|
||||
///
|
||||
/// let rest_operations = run_application();
|
||||
/// runtime.block_on(actix_system_task.run_until(rest_operations));
|
||||
/// ```
|
||||
pub fn run_in_tokio<T: Into<String>>(
|
||||
name: T,
|
||||
local: &LocalSet,
|
||||
) -> impl Future<Output = io::Result<()>> {
|
||||
Self::builder()
|
||||
.name(name)
|
||||
.build_async(local)
|
||||
.run_nonblocking()
|
||||
}
|
||||
|
||||
/// Consume the provided tokio Runtime and start the `System` in it.
|
||||
/// This method will create a `LocalSet` object and occupy the current thread
|
||||
/// for the created `System` exclusively. All the other asynchronous tasks that
|
||||
/// should be executed as well must be aggregated into one future, provided as the last
|
||||
/// argument to this method.
|
||||
///
|
||||
/// Note: This method uses provided `Runtime` to create a `System` future only.
|
||||
/// All the [`Arbiter`]s will be started in separate threads using their own tokio `Runtime`s.
|
||||
/// It means that using this method currently it is impossible to make `actix-rt` work in the
|
||||
/// alternative `tokio` `Runtime`s (e.g. provided by `tokio_compat`).
|
||||
///
|
||||
/// [`tokio_compat`]: https://crates.io/crates/tokio-compat
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `name`: Name of the System
|
||||
/// - `runtime`: A tokio Runtime to run the system in.
|
||||
/// - `rest_operations`: A future to be executed in the runtime along with the System.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// use tokio::runtime::Runtime;
|
||||
/// use actix_rt::System;
|
||||
/// use futures_util::future::try_join_all;
|
||||
///
|
||||
/// async fn run_application() {
|
||||
/// let first_task = tokio::spawn(async {
|
||||
/// // ...
|
||||
/// # println!("One task");
|
||||
/// # Ok::<(),()>(())
|
||||
/// });
|
||||
///
|
||||
/// let second_task = tokio::spawn(async {
|
||||
/// // ...
|
||||
/// # println!("Another task");
|
||||
/// # Ok::<(),()>(())
|
||||
/// });
|
||||
///
|
||||
/// try_join_all(vec![first_task, second_task])
|
||||
/// .await
|
||||
/// .expect("Some of the futures finished unexpectedly");
|
||||
/// }
|
||||
///
|
||||
///
|
||||
/// let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
/// .worker_threads(2)
|
||||
/// .enable_all()
|
||||
/// .build()
|
||||
/// .unwrap();
|
||||
///
|
||||
/// let rest_operations = run_application();
|
||||
/// System::attach_to_tokio("actix-main-system", runtime, rest_operations);
|
||||
/// ```
|
||||
pub fn attach_to_tokio<Fut, R>(
|
||||
name: impl Into<String>,
|
||||
runtime: tokio::runtime::Runtime,
|
||||
rest_operations: Fut,
|
||||
) -> R
|
||||
where
|
||||
Fut: std::future::Future<Output = R>,
|
||||
{
|
||||
let actix_system_task = LocalSet::new();
|
||||
let sys = System::run_in_tokio(name.into(), &actix_system_task);
|
||||
actix_system_task.spawn_local(sys);
|
||||
|
||||
runtime.block_on(actix_system_task.run_until(rest_operations))
|
||||
}
|
||||
|
||||
/// Get current running system.
|
||||
pub fn current() -> System {
|
||||
CURRENT.with(|cell| match *cell.borrow() {
|
||||
Some(ref sys) => sys.clone(),
|
||||
None => panic!("System is not running"),
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if current system is set, i.e., as already been started.
|
||||
pub fn is_set() -> bool {
|
||||
CURRENT.with(|cell| cell.borrow().is_some())
|
||||
}
|
||||
|
||||
/// Set current running system.
|
||||
#[doc(hidden)]
|
||||
pub fn set_current(sys: System) {
|
||||
CURRENT.with(|s| {
|
||||
*s.borrow_mut() = Some(sys);
|
||||
})
|
||||
}
|
||||
|
||||
/// Execute function with system reference.
|
||||
pub fn with_current<F, R>(f: F) -> R
|
||||
where
|
||||
F: FnOnce(&System) -> R,
|
||||
{
|
||||
CURRENT.with(|cell| match *cell.borrow() {
|
||||
Some(ref sys) => f(sys),
|
||||
None => panic!("System is not running"),
|
||||
})
|
||||
}
|
||||
|
||||
/// System id
|
||||
pub fn id(&self) -> usize {
|
||||
self.id
|
||||
}
|
||||
|
||||
/// Stop the system
|
||||
pub fn stop(&self) {
|
||||
self.stop_with_code(0)
|
||||
}
|
||||
|
||||
/// Stop the system with a particular exit code.
|
||||
pub fn stop_with_code(&self, code: i32) {
|
||||
let _ = self.sys.send(SystemCommand::Exit(code));
|
||||
}
|
||||
|
||||
pub(crate) fn sys(&self) -> &UnboundedSender<SystemCommand> {
|
||||
&self.sys
|
||||
}
|
||||
|
||||
/// Return status of 'stop_on_panic' option which controls whether the System is stopped when an
|
||||
/// uncaught panic is thrown from a worker thread.
|
||||
pub fn stop_on_panic(&self) -> bool {
|
||||
self.stop_on_panic
|
||||
}
|
||||
|
||||
/// System arbiter
|
||||
pub fn arbiter(&self) -> &Arbiter {
|
||||
&self.arbiter
|
||||
}
|
||||
|
||||
/// This function will start tokio runtime and will finish once the
|
||||
/// `System::stop()` message get called.
|
||||
/// Function `f` get called within tokio runtime context.
|
||||
pub fn run<F>(f: F) -> io::Result<()>
|
||||
where
|
||||
F: FnOnce(),
|
||||
{
|
||||
Self::builder().run(f)
|
||||
}
|
||||
}
|
@@ -1,126 +0,0 @@
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
#[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::sleep(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::sleep(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::sleep(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::sleep(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"
|
||||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn non_static_block_on() {
|
||||
let string = String::from("test_str");
|
||||
let str = string.as_str();
|
||||
|
||||
let sys = actix_rt::System::new("borrow some");
|
||||
|
||||
sys.block_on(async {
|
||||
actix_rt::time::sleep(Duration::from_millis(1)).await;
|
||||
assert_eq!("test_str", str);
|
||||
});
|
||||
|
||||
let rt = actix_rt::Runtime::new().unwrap();
|
||||
|
||||
rt.block_on(async {
|
||||
actix_rt::time::sleep(Duration::from_millis(1)).await;
|
||||
assert_eq!("test_str", str);
|
||||
});
|
||||
|
||||
actix_rt::System::run(|| {
|
||||
assert_eq!("test_str", str);
|
||||
actix_rt::System::current().stop();
|
||||
})
|
||||
.unwrap();
|
||||
}
|
@@ -1,133 +0,0 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2020-xx-xx
|
||||
* Added explicit info log message on accept queue pause. [#215]
|
||||
* Prevent double registration of sockets when back-pressure is resolved. [#223]
|
||||
|
||||
[#215]: https://github.com/actix/actix-net/pull/215
|
||||
[#223]: https://github.com/actix/actix-net/pull/223
|
||||
|
||||
|
||||
## 1.0.4 - 2020-09-12
|
||||
* Update actix-codec to 0.3.0.
|
||||
* Workers must be greater than 0. [#167]
|
||||
|
||||
[#167]: https://github.com/actix/actix-net/pull/167
|
||||
|
||||
|
||||
## 1.0.3 - 2020-05-19
|
||||
* Replace deprecated `net2` crate with `socket2` [#140]
|
||||
|
||||
[#140]: https://github.com/actix/actix-net/pull/140
|
||||
|
||||
|
||||
## 1.0.2 - 2020-02-26
|
||||
* Avoid error by calling `reregister()` on Windows [#103]
|
||||
|
||||
[#103]: https://github.com/actix/actix-net/pull/103
|
||||
|
||||
|
||||
## 1.0.1 - 2019-12-29
|
||||
* Rename `.start()` method to `.run()`
|
||||
|
||||
|
||||
## 1.0.0 - 2019-12-11
|
||||
* Use actix-net releases
|
||||
|
||||
|
||||
## 1.0.0-alpha.4 - 2019-12-08
|
||||
* Use actix-service 1.0.0-alpha.4
|
||||
|
||||
|
||||
## 1.0.0-alpha.3 - 2019-12-07
|
||||
* Migrate to tokio 0.2
|
||||
* Fix compilation on non-unix platforms
|
||||
* Better handling server configuration
|
||||
|
||||
|
||||
## 1.0.0-alpha.2 - 2019-12-02
|
||||
* Simplify server service (remove actix-server-config)
|
||||
* Allow to wait on `Server` until server stops
|
||||
|
||||
|
||||
## 0.8.0-alpha.1 - 2019-11-22
|
||||
* Migrate to `std::future`
|
||||
|
||||
|
||||
## 0.7.0 - 2019-10-04
|
||||
* Update `rustls` to 0.16
|
||||
* Minimum required Rust version upped to 1.37.0
|
||||
|
||||
|
||||
## 0.6.1 - 2019-09-25
|
||||
* Add UDS listening support to `ServerBuilder`
|
||||
|
||||
|
||||
## 0.6.0 - 2019-07-18
|
||||
* Support Unix domain sockets #3
|
||||
|
||||
|
||||
## 0.5.1 - 2019-05-18
|
||||
* ServerBuilder::shutdown_timeout() accepts u64
|
||||
|
||||
|
||||
## 0.5.0 - 2019-05-12
|
||||
* Add `Debug` impl for `SslError`
|
||||
* Derive debug for `Server` and `ServerCommand`
|
||||
* Upgrade to actix-service 0.4
|
||||
|
||||
|
||||
## 0.4.3 - 2019-04-16
|
||||
* Re-export `IoStream` trait
|
||||
* Depend on `ssl` and `rust-tls` features from actix-server-config
|
||||
|
||||
|
||||
## 0.4.2 - 2019-03-30
|
||||
* Fix SIGINT force shutdown
|
||||
|
||||
|
||||
## 0.4.1 - 2019-03-14
|
||||
* `SystemRuntime::on_start()` - allow to run future before server service initialization
|
||||
|
||||
|
||||
## 0.4.0 - 2019-03-12
|
||||
* Use `ServerConfig` for service factory
|
||||
* Wrap tcp socket to `Io` type
|
||||
* Upgrade actix-service
|
||||
|
||||
|
||||
## 0.3.1 - 2019-03-04
|
||||
* Add `ServerBuilder::maxconnrate` sets the maximum per-worker number of concurrent connections
|
||||
* Add helper ssl error `SslError`
|
||||
* Rename `StreamServiceFactory` to `ServiceFactory`
|
||||
* Deprecate `StreamServiceFactory`
|
||||
|
||||
|
||||
## 0.3.0 - 2019-03-02
|
||||
* Use new `NewService` trait
|
||||
|
||||
|
||||
## 0.2.1 - 2019-02-09
|
||||
* Drop service response
|
||||
|
||||
|
||||
## 0.2.0 - 2019-02-01
|
||||
* Migrate to actix-service 0.2
|
||||
* Updated rustls dependency
|
||||
|
||||
|
||||
## 0.1.3 - 2018-12-21
|
||||
* Fix max concurrent connections handling
|
||||
|
||||
|
||||
## 0.1.2 - 2018-12-12
|
||||
* rename ServiceConfig::rt() to ServiceConfig::apply()
|
||||
* Fix back-pressure for concurrent ssl handshakes
|
||||
|
||||
|
||||
## 0.1.1 - 2018-12-11
|
||||
* Fix signal handling on windows
|
||||
|
||||
|
||||
## 0.1.0 - 2018-12-09
|
||||
* Move server to separate crate
|
@@ -1,44 +0,0 @@
|
||||
[package]
|
||||
name = "actix-server"
|
||||
version = "1.0.4"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "General purpose TCP server built for the Actix ecosystem"
|
||||
keywords = ["network", "framework", "async", "futures"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-net.git"
|
||||
documentation = "https://docs.rs/actix-server/"
|
||||
categories = ["network-programming", "asynchronous"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
exclude = [".gitignore", ".cargo/config"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
name = "actix_server"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
actix-service = "1.0.6"
|
||||
actix-rt = "1.1.1"
|
||||
actix-codec = "0.3.0"
|
||||
actix-utils = "2.0.0"
|
||||
|
||||
log = "0.4"
|
||||
num_cpus = "1.13"
|
||||
mio = "0.6.19"
|
||||
socket2 = "0.3"
|
||||
futures-channel = { version = "0.3.4", default-features = false }
|
||||
futures-util = { version = "0.3.4", default-features = false, features = ["sink"] }
|
||||
slab = "0.4"
|
||||
|
||||
# unix domain sockets
|
||||
# FIXME: Remove it and use mio own uds feature once mio 0.7 is released
|
||||
mio-uds = { version = "0.6.7" }
|
||||
|
||||
[dev-dependencies]
|
||||
bytes = "0.5"
|
||||
env_logger = "0.7"
|
||||
actix-testing = "1.0.0"
|
||||
tokio = { version = "0.2", features = ["io-util"] }
|
@@ -1 +0,0 @@
|
||||
../LICENSE-APACHE
|
@@ -1 +0,0 @@
|
||||
../LICENSE-MIT
|
@@ -1,88 +0,0 @@
|
||||
//! Simple composite-service TCP echo server.
|
||||
//!
|
||||
//! Using the following command:
|
||||
//!
|
||||
//! ```sh
|
||||
//! nc 127.0.0.1 8080
|
||||
//! ```
|
||||
//!
|
||||
//! Start typing. When you press enter the typed line will be echoed back. The server will log
|
||||
//! the length of each line it echos and the total size of data sent when the connection is closed.
|
||||
|
||||
use std::sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc,
|
||||
};
|
||||
use std::{env, io};
|
||||
|
||||
use actix_rt::net::TcpStream;
|
||||
use actix_server::Server;
|
||||
use actix_service::pipeline_factory;
|
||||
use bytes::BytesMut;
|
||||
use futures_util::future::ok;
|
||||
use log::{error, info};
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
|
||||
#[actix_rt::main]
|
||||
async fn main() -> io::Result<()> {
|
||||
env::set_var("RUST_LOG", "actix=trace,basic=trace");
|
||||
env_logger::init();
|
||||
|
||||
let count = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let addr = ("127.0.0.1", 8080);
|
||||
info!("starting server on port: {}", &addr.0);
|
||||
|
||||
// Bind socket address and start worker(s). By default, the server uses the number of available
|
||||
// logical CPU cores as the worker count. For this reason, the closure passed to bind needs
|
||||
// to return a service *factory*; so it can be created once per worker.
|
||||
Server::build()
|
||||
.bind("echo", addr, move || {
|
||||
let count = Arc::clone(&count);
|
||||
let num2 = Arc::clone(&count);
|
||||
|
||||
pipeline_factory(move |mut stream: TcpStream| {
|
||||
let count = Arc::clone(&count);
|
||||
|
||||
async move {
|
||||
let num = count.fetch_add(1, Ordering::SeqCst);
|
||||
let num = num + 1;
|
||||
|
||||
let mut size = 0;
|
||||
let mut buf = BytesMut::new();
|
||||
|
||||
loop {
|
||||
match stream.read_buf(&mut buf).await {
|
||||
// end of stream; bail from loop
|
||||
Ok(0) => break,
|
||||
|
||||
// more bytes to process
|
||||
Ok(bytes_read) => {
|
||||
info!("[{}] read {} bytes", num, bytes_read);
|
||||
stream.write_all(&buf[size..]).await.unwrap();
|
||||
size += bytes_read;
|
||||
}
|
||||
|
||||
// stream error; bail from loop with error
|
||||
Err(err) => {
|
||||
error!("Stream Error: {:?}", err);
|
||||
return Err(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// send data down service pipeline
|
||||
Ok((buf.freeze(), size))
|
||||
}
|
||||
})
|
||||
.map_err(|err| error!("Service Error: {:?}", err))
|
||||
.and_then(move |(_, size)| {
|
||||
let num = num2.load(Ordering::SeqCst);
|
||||
info!("[{}] total bytes read: {}", num, size);
|
||||
ok(size)
|
||||
})
|
||||
})?
|
||||
.workers(1)
|
||||
.run()
|
||||
.await
|
||||
}
|
@@ -1,500 +0,0 @@
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use std::time::Duration;
|
||||
use std::{io, mem, net};
|
||||
|
||||
use actix_rt::net::TcpStream;
|
||||
use actix_rt::time::{delay_until, Instant};
|
||||
use actix_rt::{spawn, System};
|
||||
use futures_channel::mpsc::{unbounded, UnboundedReceiver};
|
||||
use futures_channel::oneshot;
|
||||
use futures_util::future::ready;
|
||||
use futures_util::stream::FuturesUnordered;
|
||||
use futures_util::{future::Future, ready, stream::Stream, FutureExt, StreamExt};
|
||||
use log::{error, info};
|
||||
use socket2::{Domain, Protocol, Socket, Type};
|
||||
|
||||
use crate::accept::{AcceptLoop, AcceptNotify, Command};
|
||||
use crate::config::{ConfiguredService, ServiceConfig};
|
||||
use crate::server::{Server, ServerCommand};
|
||||
use crate::service::{InternalServiceFactory, ServiceFactory, StreamNewService};
|
||||
use crate::signals::{Signal, Signals};
|
||||
use crate::socket::StdListener;
|
||||
use crate::worker::{self, Worker, WorkerAvailability, WorkerClient};
|
||||
use crate::Token;
|
||||
|
||||
/// Server builder
|
||||
pub struct ServerBuilder {
|
||||
threads: usize,
|
||||
token: Token,
|
||||
backlog: i32,
|
||||
workers: Vec<(usize, WorkerClient)>,
|
||||
services: Vec<Box<dyn InternalServiceFactory>>,
|
||||
sockets: Vec<(Token, String, StdListener)>,
|
||||
accept: AcceptLoop,
|
||||
exit: bool,
|
||||
shutdown_timeout: Duration,
|
||||
no_signals: bool,
|
||||
cmd: UnboundedReceiver<ServerCommand>,
|
||||
server: Server,
|
||||
notify: Vec<oneshot::Sender<()>>,
|
||||
}
|
||||
|
||||
impl Default for ServerBuilder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ServerBuilder {
|
||||
/// Create new Server builder instance
|
||||
pub fn new() -> ServerBuilder {
|
||||
let (tx, rx) = unbounded();
|
||||
let server = Server::new(tx);
|
||||
|
||||
ServerBuilder {
|
||||
threads: num_cpus::get(),
|
||||
token: Token(0),
|
||||
workers: Vec::new(),
|
||||
services: Vec::new(),
|
||||
sockets: Vec::new(),
|
||||
accept: AcceptLoop::new(server.clone()),
|
||||
backlog: 2048,
|
||||
exit: false,
|
||||
shutdown_timeout: Duration::from_secs(30),
|
||||
no_signals: false,
|
||||
cmd: rx,
|
||||
notify: Vec::new(),
|
||||
server,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set number of workers to start.
|
||||
///
|
||||
/// By default server uses number of available logical cpu as workers
|
||||
/// count. Workers must be greater than 0.
|
||||
pub fn workers(mut self, num: usize) -> Self {
|
||||
assert_ne!(num, 0, "workers must be greater than 0");
|
||||
self.threads = num;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the maximum number of pending connections.
|
||||
///
|
||||
/// This refers to the number of clients that can be waiting to be served.
|
||||
/// Exceeding this number results in the client getting an error when
|
||||
/// attempting to connect. It should only affect servers under significant
|
||||
/// load.
|
||||
///
|
||||
/// Generally set in the 64-2048 range. Default value is 2048.
|
||||
///
|
||||
/// This method should be called before `bind()` method call.
|
||||
pub fn backlog(mut self, num: i32) -> Self {
|
||||
self.backlog = num;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the maximum per-worker number of concurrent connections.
|
||||
///
|
||||
/// All socket listeners will stop accepting connections when this limit is
|
||||
/// reached for each worker.
|
||||
///
|
||||
/// By default max connections is set to a 25k per worker.
|
||||
pub fn maxconn(self, num: usize) -> Self {
|
||||
worker::max_concurrent_connections(num);
|
||||
self
|
||||
}
|
||||
|
||||
/// Stop actix system.
|
||||
pub fn system_exit(mut self) -> Self {
|
||||
self.exit = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Disable signal handling
|
||||
pub fn disable_signals(mut self) -> Self {
|
||||
self.no_signals = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Timeout for graceful workers shutdown in seconds.
|
||||
///
|
||||
/// After receiving a stop signal, workers have this much time to finish
|
||||
/// serving requests. Workers still alive after the timeout are force
|
||||
/// dropped.
|
||||
///
|
||||
/// By default shutdown timeout sets to 30 seconds.
|
||||
pub fn shutdown_timeout(mut self, sec: u64) -> Self {
|
||||
self.shutdown_timeout = Duration::from_secs(sec);
|
||||
self
|
||||
}
|
||||
|
||||
/// Execute external configuration as part of the server building
|
||||
/// process.
|
||||
///
|
||||
/// This function is useful for moving parts of configuration to a
|
||||
/// different module or even library.
|
||||
pub fn configure<F>(mut self, f: F) -> io::Result<ServerBuilder>
|
||||
where
|
||||
F: Fn(&mut ServiceConfig) -> io::Result<()>,
|
||||
{
|
||||
let mut cfg = ServiceConfig::new(self.threads, self.backlog);
|
||||
|
||||
f(&mut cfg)?;
|
||||
|
||||
if let Some(apply) = cfg.apply {
|
||||
let mut srv = ConfiguredService::new(apply);
|
||||
for (name, lst) in cfg.services {
|
||||
let token = self.token.next();
|
||||
srv.stream(token, name.clone(), lst.local_addr()?);
|
||||
self.sockets.push((token, name, StdListener::Tcp(lst)));
|
||||
}
|
||||
self.services.push(Box::new(srv));
|
||||
}
|
||||
self.threads = cfg.threads;
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Add new service to the server.
|
||||
pub fn bind<F, U, N: AsRef<str>>(mut self, name: N, addr: U, factory: F) -> io::Result<Self>
|
||||
where
|
||||
F: ServiceFactory<TcpStream>,
|
||||
U: net::ToSocketAddrs,
|
||||
{
|
||||
let sockets = bind_addr(addr, self.backlog)?;
|
||||
|
||||
for lst in sockets {
|
||||
let token = self.token.next();
|
||||
self.services.push(StreamNewService::create(
|
||||
name.as_ref().to_string(),
|
||||
token,
|
||||
factory.clone(),
|
||||
lst.local_addr()?,
|
||||
));
|
||||
self.sockets
|
||||
.push((token, name.as_ref().to_string(), StdListener::Tcp(lst)));
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
#[cfg(all(unix))]
|
||||
/// 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>
|
||||
where
|
||||
F: ServiceFactory<actix_rt::net::UnixStream>,
|
||||
N: AsRef<str>,
|
||||
U: AsRef<std::path::Path>,
|
||||
{
|
||||
use std::os::unix::net::UnixListener;
|
||||
|
||||
// The path must not exist when we try to bind.
|
||||
// Try to remove it to avoid bind error.
|
||||
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)?;
|
||||
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 addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
|
||||
self.services.push(StreamNewService::create(
|
||||
name.as_ref().to_string(),
|
||||
token,
|
||||
factory,
|
||||
addr,
|
||||
));
|
||||
self.sockets
|
||||
.push((token, name.as_ref().to_string(), StdListener::Uds(lst)));
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Add new service to the server.
|
||||
pub fn listen<F, N: AsRef<str>>(
|
||||
mut self,
|
||||
name: N,
|
||||
lst: net::TcpListener,
|
||||
factory: F,
|
||||
) -> io::Result<Self>
|
||||
where
|
||||
F: ServiceFactory<TcpStream>,
|
||||
{
|
||||
let token = self.token.next();
|
||||
self.services.push(StreamNewService::create(
|
||||
name.as_ref().to_string(),
|
||||
token,
|
||||
factory,
|
||||
lst.local_addr()?,
|
||||
));
|
||||
self.sockets
|
||||
.push((token, name.as_ref().to_string(), StdListener::Tcp(lst)));
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn start(self) -> Server {
|
||||
self.run()
|
||||
}
|
||||
|
||||
/// Starts processing incoming connections and return server controller.
|
||||
pub fn run(mut self) -> Server {
|
||||
if self.sockets.is_empty() {
|
||||
panic!("Server should have at least one bound socket");
|
||||
} else {
|
||||
info!("Starting {} workers", self.threads);
|
||||
|
||||
// start workers
|
||||
let workers = (0..self.threads)
|
||||
.map(|idx| {
|
||||
let worker = self.start_worker(idx, self.accept.get_notify());
|
||||
self.workers.push((idx, worker.clone()));
|
||||
|
||||
worker
|
||||
})
|
||||
.collect();
|
||||
|
||||
// start accept thread
|
||||
for sock in &self.sockets {
|
||||
info!("Starting \"{}\" service on {}", sock.1, sock.2);
|
||||
}
|
||||
self.accept.start(
|
||||
mem::take(&mut self.sockets)
|
||||
.into_iter()
|
||||
.map(|t| (t.0, t.2))
|
||||
.collect(),
|
||||
workers,
|
||||
);
|
||||
|
||||
// handle signals
|
||||
if !self.no_signals {
|
||||
Signals::start(self.server.clone());
|
||||
}
|
||||
|
||||
// start http server actor
|
||||
let server = self.server.clone();
|
||||
spawn(self);
|
||||
server
|
||||
}
|
||||
}
|
||||
|
||||
fn start_worker(&self, idx: usize, notify: AcceptNotify) -> WorkerClient {
|
||||
let avail = WorkerAvailability::new(notify);
|
||||
let services: Vec<Box<dyn InternalServiceFactory>> =
|
||||
self.services.iter().map(|v| v.clone_factory()).collect();
|
||||
|
||||
Worker::start(idx, services, avail, self.shutdown_timeout)
|
||||
}
|
||||
|
||||
fn handle_cmd(&mut self, item: ServerCommand) {
|
||||
match item {
|
||||
ServerCommand::Pause(tx) => {
|
||||
self.accept.send(Command::Pause);
|
||||
let _ = tx.send(());
|
||||
}
|
||||
ServerCommand::Resume(tx) => {
|
||||
self.accept.send(Command::Resume);
|
||||
let _ = tx.send(());
|
||||
}
|
||||
ServerCommand::Signal(sig) => {
|
||||
// Signals support
|
||||
// Handle `SIGINT`, `SIGTERM`, `SIGQUIT` signals and stop actix system
|
||||
match sig {
|
||||
Signal::Int => {
|
||||
info!("SIGINT received, exiting");
|
||||
self.exit = true;
|
||||
self.handle_cmd(ServerCommand::Stop {
|
||||
graceful: false,
|
||||
completion: None,
|
||||
})
|
||||
}
|
||||
Signal::Term => {
|
||||
info!("SIGTERM received, stopping");
|
||||
self.exit = true;
|
||||
self.handle_cmd(ServerCommand::Stop {
|
||||
graceful: true,
|
||||
completion: None,
|
||||
})
|
||||
}
|
||||
Signal::Quit => {
|
||||
info!("SIGQUIT received, exiting");
|
||||
self.exit = true;
|
||||
self.handle_cmd(ServerCommand::Stop {
|
||||
graceful: false,
|
||||
completion: None,
|
||||
})
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
ServerCommand::Notify(tx) => {
|
||||
self.notify.push(tx);
|
||||
}
|
||||
ServerCommand::Stop {
|
||||
graceful,
|
||||
completion,
|
||||
} => {
|
||||
let exit = self.exit;
|
||||
|
||||
// stop accept thread
|
||||
self.accept.send(Command::Stop);
|
||||
let notify = std::mem::take(&mut self.notify);
|
||||
|
||||
// stop workers
|
||||
if !self.workers.is_empty() && graceful {
|
||||
spawn(
|
||||
self.workers
|
||||
.iter()
|
||||
.map(move |worker| worker.1.stop(graceful))
|
||||
.collect::<FuturesUnordered<_>>()
|
||||
.collect::<Vec<_>>()
|
||||
.then(move |_| {
|
||||
if let Some(tx) = completion {
|
||||
let _ = tx.send(());
|
||||
}
|
||||
for tx in notify {
|
||||
let _ = tx.send(());
|
||||
}
|
||||
if exit {
|
||||
spawn(
|
||||
async {
|
||||
delay_until(
|
||||
Instant::now() + Duration::from_millis(300),
|
||||
)
|
||||
.await;
|
||||
System::current().stop();
|
||||
}
|
||||
.boxed(),
|
||||
);
|
||||
}
|
||||
ready(())
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
// we need to stop system if server was spawned
|
||||
if self.exit {
|
||||
spawn(
|
||||
delay_until(Instant::now() + Duration::from_millis(300)).then(
|
||||
|_| {
|
||||
System::current().stop();
|
||||
ready(())
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
if let Some(tx) = completion {
|
||||
let _ = tx.send(());
|
||||
}
|
||||
for tx in notify {
|
||||
let _ = tx.send(());
|
||||
}
|
||||
}
|
||||
}
|
||||
ServerCommand::WorkerFaulted(idx) => {
|
||||
let mut found = false;
|
||||
for i in 0..self.workers.len() {
|
||||
if self.workers[i].0 == idx {
|
||||
self.workers.swap_remove(i);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
error!("Worker has died {:?}, restarting", idx);
|
||||
|
||||
let mut new_idx = self.workers.len();
|
||||
'found: loop {
|
||||
for i in 0..self.workers.len() {
|
||||
if self.workers[i].0 == new_idx {
|
||||
new_idx += 1;
|
||||
continue 'found;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
let worker = self.start_worker(new_idx, self.accept.get_notify());
|
||||
self.workers.push((new_idx, worker.clone()));
|
||||
self.accept.send(Command::Worker(worker));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for ServerBuilder {
|
||||
type Output = ();
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
loop {
|
||||
match ready!(Pin::new(&mut self.cmd).poll_next(cx)) {
|
||||
Some(it) => self.as_mut().get_mut().handle_cmd(it),
|
||||
None => {
|
||||
return Poll::Pending;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn bind_addr<S: net::ToSocketAddrs>(
|
||||
addr: S,
|
||||
backlog: i32,
|
||||
) -> io::Result<Vec<net::TcpListener>> {
|
||||
let mut err = None;
|
||||
let mut succ = false;
|
||||
let mut sockets = Vec::new();
|
||||
for addr in addr.to_socket_addrs()? {
|
||||
match create_tcp_listener(addr, backlog) {
|
||||
Ok(lst) => {
|
||||
succ = true;
|
||||
sockets.push(lst);
|
||||
}
|
||||
Err(e) => err = Some(e),
|
||||
}
|
||||
}
|
||||
|
||||
if !succ {
|
||||
if let Some(e) = err.take() {
|
||||
Err(e)
|
||||
} else {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Can not bind to address.",
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Ok(sockets)
|
||||
}
|
||||
}
|
||||
|
||||
fn create_tcp_listener(addr: net::SocketAddr, backlog: i32) -> io::Result<net::TcpListener> {
|
||||
let domain = match addr {
|
||||
net::SocketAddr::V4(_) => Domain::ipv4(),
|
||||
net::SocketAddr::V6(_) => Domain::ipv6(),
|
||||
};
|
||||
let socket = Socket::new(domain, Type::stream(), Some(Protocol::tcp()))?;
|
||||
socket.set_reuse_address(true)?;
|
||||
socket.bind(&addr.into())?;
|
||||
socket.listen(backlog)?;
|
||||
Ok(socket.into_tcp_listener())
|
||||
}
|
@@ -1,284 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
use std::{fmt, io, net};
|
||||
|
||||
use actix_rt::net::TcpStream;
|
||||
use actix_service::{
|
||||
fn_service, IntoServiceFactory as IntoBaseServiceFactory,
|
||||
ServiceFactory as BaseServiceFactory,
|
||||
};
|
||||
use actix_utils::counter::CounterGuard;
|
||||
use futures_util::future::{ok, Future, FutureExt, LocalBoxFuture};
|
||||
use log::error;
|
||||
|
||||
use super::builder::bind_addr;
|
||||
use super::service::{BoxedServerService, InternalServiceFactory, StreamService};
|
||||
use super::Token;
|
||||
use crate::socket::StdStream;
|
||||
|
||||
pub struct ServiceConfig {
|
||||
pub(crate) services: Vec<(String, net::TcpListener)>,
|
||||
pub(crate) apply: Option<Box<dyn ServiceRuntimeConfiguration>>,
|
||||
pub(crate) threads: usize,
|
||||
pub(crate) backlog: i32,
|
||||
}
|
||||
|
||||
impl ServiceConfig {
|
||||
pub(super) fn new(threads: usize, backlog: i32) -> ServiceConfig {
|
||||
ServiceConfig {
|
||||
threads,
|
||||
backlog,
|
||||
services: Vec::new(),
|
||||
apply: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set number of workers to start.
|
||||
///
|
||||
/// By default server uses number of available logical cpu as workers
|
||||
/// count.
|
||||
pub fn workers(&mut self, num: usize) {
|
||||
self.threads = num;
|
||||
}
|
||||
|
||||
/// Add new service to server
|
||||
pub fn bind<U, N: AsRef<str>>(&mut self, name: N, addr: U) -> io::Result<&mut Self>
|
||||
where
|
||||
U: net::ToSocketAddrs,
|
||||
{
|
||||
let sockets = bind_addr(addr, self.backlog)?;
|
||||
|
||||
for lst in sockets {
|
||||
self.listen(name.as_ref(), lst);
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Add new service to server
|
||||
pub fn listen<N: AsRef<str>>(&mut self, name: N, lst: net::TcpListener) -> &mut Self {
|
||||
if self.apply.is_none() {
|
||||
self.apply = Some(Box::new(not_configured));
|
||||
}
|
||||
self.services.push((name.as_ref().to_string(), lst));
|
||||
self
|
||||
}
|
||||
|
||||
/// Register service configuration function. This function get called
|
||||
/// during worker runtime configuration. It get executed in worker thread.
|
||||
pub fn apply<F>(&mut self, f: F) -> io::Result<()>
|
||||
where
|
||||
F: Fn(&mut ServiceRuntime) + Send + Clone + 'static,
|
||||
{
|
||||
self.apply = Some(Box::new(f));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct ConfiguredService {
|
||||
rt: Box<dyn ServiceRuntimeConfiguration>,
|
||||
names: HashMap<Token, (String, net::SocketAddr)>,
|
||||
topics: HashMap<String, Token>,
|
||||
services: Vec<Token>,
|
||||
}
|
||||
|
||||
impl ConfiguredService {
|
||||
pub(super) fn new(rt: Box<dyn ServiceRuntimeConfiguration>) -> Self {
|
||||
ConfiguredService {
|
||||
rt,
|
||||
names: HashMap::new(),
|
||||
topics: HashMap::new(),
|
||||
services: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn stream(&mut self, token: Token, name: String, addr: net::SocketAddr) {
|
||||
self.names.insert(token, (name.clone(), addr));
|
||||
self.topics.insert(name, token);
|
||||
self.services.push(token);
|
||||
}
|
||||
}
|
||||
|
||||
impl InternalServiceFactory for ConfiguredService {
|
||||
fn name(&self, token: Token) -> &str {
|
||||
&self.names[&token].0
|
||||
}
|
||||
|
||||
fn clone_factory(&self) -> Box<dyn InternalServiceFactory> {
|
||||
Box::new(Self {
|
||||
rt: self.rt.clone(),
|
||||
names: self.names.clone(),
|
||||
topics: self.topics.clone(),
|
||||
services: self.services.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn create(&self) -> LocalBoxFuture<'static, Result<Vec<(Token, BoxedServerService)>, ()>> {
|
||||
// configure services
|
||||
let mut rt = ServiceRuntime::new(self.topics.clone());
|
||||
self.rt.configure(&mut rt);
|
||||
rt.validate();
|
||||
let mut names = self.names.clone();
|
||||
let tokens = self.services.clone();
|
||||
|
||||
// construct services
|
||||
async move {
|
||||
let mut services = rt.services;
|
||||
// TODO: Proper error handling here
|
||||
for f in rt.onstart.into_iter() {
|
||||
f.await;
|
||||
}
|
||||
let mut res = vec![];
|
||||
for token in tokens {
|
||||
if let Some(srv) = services.remove(&token) {
|
||||
let newserv = srv.new_service(());
|
||||
match newserv.await {
|
||||
Ok(serv) => {
|
||||
res.push((token, serv));
|
||||
}
|
||||
Err(_) => {
|
||||
error!("Can not construct service");
|
||||
return Err(());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let name = names.remove(&token).unwrap().0;
|
||||
res.push((
|
||||
token,
|
||||
Box::new(StreamService::new(fn_service(move |_: TcpStream| {
|
||||
error!("Service {:?} is not configured", name);
|
||||
ok::<_, ()>(())
|
||||
}))),
|
||||
));
|
||||
};
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
.boxed_local()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) trait ServiceRuntimeConfiguration: Send {
|
||||
fn clone(&self) -> Box<dyn ServiceRuntimeConfiguration>;
|
||||
|
||||
fn configure(&self, rt: &mut ServiceRuntime);
|
||||
}
|
||||
|
||||
impl<F> ServiceRuntimeConfiguration for F
|
||||
where
|
||||
F: Fn(&mut ServiceRuntime) + Send + Clone + 'static,
|
||||
{
|
||||
fn clone(&self) -> Box<dyn ServiceRuntimeConfiguration> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn configure(&self, rt: &mut ServiceRuntime) {
|
||||
(self)(rt)
|
||||
}
|
||||
}
|
||||
|
||||
fn not_configured(_: &mut ServiceRuntime) {
|
||||
error!("Service is not configured");
|
||||
}
|
||||
|
||||
pub struct ServiceRuntime {
|
||||
names: HashMap<String, Token>,
|
||||
services: HashMap<Token, BoxedNewService>,
|
||||
onstart: Vec<LocalBoxFuture<'static, ()>>,
|
||||
}
|
||||
|
||||
impl ServiceRuntime {
|
||||
fn new(names: HashMap<String, Token>) -> Self {
|
||||
ServiceRuntime {
|
||||
names,
|
||||
services: HashMap::new(),
|
||||
onstart: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn validate(&self) {
|
||||
for (name, token) in &self.names {
|
||||
if !self.services.contains_key(&token) {
|
||||
error!("Service {:?} is not configured", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Register service.
|
||||
///
|
||||
/// Name of the service must be registered during configuration stage with
|
||||
/// *ServiceConfig::bind()* or *ServiceConfig::listen()* methods.
|
||||
pub fn service<T, F>(&mut self, name: &str, service: F)
|
||||
where
|
||||
F: IntoBaseServiceFactory<T, TcpStream>,
|
||||
T: BaseServiceFactory<TcpStream, Config = ()> + 'static,
|
||||
T::Future: 'static,
|
||||
T::Service: 'static,
|
||||
T::InitError: fmt::Debug,
|
||||
{
|
||||
// let name = name.to_owned();
|
||||
if let Some(token) = self.names.get(name) {
|
||||
self.services.insert(
|
||||
*token,
|
||||
Box::new(ServiceFactory {
|
||||
inner: service.into_factory(),
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
panic!("Unknown service: {:?}", name);
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute future before services initialization.
|
||||
pub fn on_start<F>(&mut self, fut: F)
|
||||
where
|
||||
F: Future<Output = ()> + 'static,
|
||||
{
|
||||
self.onstart.push(fut.boxed_local())
|
||||
}
|
||||
}
|
||||
|
||||
type BoxedNewService = Box<
|
||||
dyn BaseServiceFactory<
|
||||
(Option<CounterGuard>, StdStream),
|
||||
Response = (),
|
||||
Error = (),
|
||||
InitError = (),
|
||||
Config = (),
|
||||
Service = BoxedServerService,
|
||||
Future = LocalBoxFuture<'static, Result<BoxedServerService, ()>>,
|
||||
>,
|
||||
>;
|
||||
|
||||
struct ServiceFactory<T> {
|
||||
inner: T,
|
||||
}
|
||||
|
||||
impl<T> BaseServiceFactory<(Option<CounterGuard>, StdStream)> for ServiceFactory<T>
|
||||
where
|
||||
T: BaseServiceFactory<TcpStream, Config = ()>,
|
||||
T::Future: 'static,
|
||||
T::Service: 'static,
|
||||
T::Error: 'static,
|
||||
T::InitError: fmt::Debug + 'static,
|
||||
{
|
||||
type Response = ();
|
||||
type Error = ();
|
||||
type Config = ();
|
||||
type Service = BoxedServerService;
|
||||
type InitError = ();
|
||||
type Future = LocalBoxFuture<'static, Result<BoxedServerService, ()>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
let fut = self.inner.new_service(());
|
||||
async move {
|
||||
match fut.await {
|
||||
Ok(s) => Ok(Box::new(StreamService::new(s)) as BoxedServerService),
|
||||
Err(e) => {
|
||||
error!("Can not construct service: {:?}", e);
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
.boxed_local()
|
||||
}
|
||||
}
|
@@ -1,39 +0,0 @@
|
||||
//! General purpose TCP server.
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
|
||||
mod accept;
|
||||
mod builder;
|
||||
mod config;
|
||||
mod server;
|
||||
mod service;
|
||||
mod signals;
|
||||
mod socket;
|
||||
mod worker;
|
||||
|
||||
pub use self::builder::ServerBuilder;
|
||||
pub use self::config::{ServiceConfig, ServiceRuntime};
|
||||
pub use self::server::Server;
|
||||
pub use self::service::ServiceFactory;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use self::socket::FromStream;
|
||||
|
||||
/// Socket ID token
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct Token(usize);
|
||||
|
||||
impl Token {
|
||||
pub(crate) fn next(&mut self) -> Token {
|
||||
let token = Token(self.0);
|
||||
self.0 += 1;
|
||||
token
|
||||
}
|
||||
}
|
||||
|
||||
/// Start server building process
|
||||
pub fn new() -> ServerBuilder {
|
||||
ServerBuilder::default()
|
||||
}
|
@@ -1,108 +0,0 @@
|
||||
use std::future::Future;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use futures_channel::mpsc::UnboundedSender;
|
||||
use futures_channel::oneshot;
|
||||
use futures_util::FutureExt;
|
||||
|
||||
use crate::builder::ServerBuilder;
|
||||
use crate::signals::Signal;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ServerCommand {
|
||||
WorkerFaulted(usize),
|
||||
Pause(oneshot::Sender<()>),
|
||||
Resume(oneshot::Sender<()>),
|
||||
Signal(Signal),
|
||||
/// Whether to try and shut down gracefully
|
||||
Stop {
|
||||
graceful: bool,
|
||||
completion: Option<oneshot::Sender<()>>,
|
||||
},
|
||||
/// Notify of server stop
|
||||
Notify(oneshot::Sender<()>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Server(
|
||||
UnboundedSender<ServerCommand>,
|
||||
Option<oneshot::Receiver<()>>,
|
||||
);
|
||||
|
||||
impl Server {
|
||||
pub(crate) fn new(tx: UnboundedSender<ServerCommand>) -> Self {
|
||||
Server(tx, None)
|
||||
}
|
||||
|
||||
/// Start server building process
|
||||
pub fn build() -> ServerBuilder {
|
||||
ServerBuilder::default()
|
||||
}
|
||||
|
||||
pub(crate) fn signal(&self, sig: Signal) {
|
||||
let _ = self.0.unbounded_send(ServerCommand::Signal(sig));
|
||||
}
|
||||
|
||||
pub(crate) fn worker_faulted(&self, idx: usize) {
|
||||
let _ = self.0.unbounded_send(ServerCommand::WorkerFaulted(idx));
|
||||
}
|
||||
|
||||
/// Pause accepting incoming connections
|
||||
///
|
||||
/// If socket contains some pending connection, they might be dropped.
|
||||
/// All opened connection remains active.
|
||||
pub fn pause(&self) -> impl Future<Output = ()> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let _ = self.0.unbounded_send(ServerCommand::Pause(tx));
|
||||
rx.map(|_| ())
|
||||
}
|
||||
|
||||
/// Resume accepting incoming connections
|
||||
pub fn resume(&self) -> impl Future<Output = ()> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let _ = self.0.unbounded_send(ServerCommand::Resume(tx));
|
||||
rx.map(|_| ())
|
||||
}
|
||||
|
||||
/// Stop incoming connection processing, stop all workers and exit.
|
||||
///
|
||||
/// If server starts with `spawn()` method, then spawned thread get terminated.
|
||||
pub fn stop(&self, graceful: bool) -> impl Future<Output = ()> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let _ = self.0.unbounded_send(ServerCommand::Stop {
|
||||
graceful,
|
||||
completion: Some(tx),
|
||||
});
|
||||
rx.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
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(())),
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,158 +0,0 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::net::SocketAddr;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use actix_rt::spawn;
|
||||
use actix_service::{Service, ServiceFactory as BaseServiceFactory};
|
||||
use actix_utils::counter::CounterGuard;
|
||||
use futures_util::future::{err, ok, LocalBoxFuture, Ready};
|
||||
use futures_util::{FutureExt, TryFutureExt};
|
||||
use log::error;
|
||||
|
||||
use super::Token;
|
||||
use crate::socket::{FromStream, StdStream};
|
||||
|
||||
pub trait ServiceFactory<Stream: FromStream>: Send + Clone + 'static {
|
||||
type Factory: BaseServiceFactory<Stream, Config = ()>;
|
||||
|
||||
fn create(&self) -> Self::Factory;
|
||||
}
|
||||
|
||||
pub(crate) trait InternalServiceFactory: Send {
|
||||
fn name(&self, token: Token) -> &str;
|
||||
|
||||
fn clone_factory(&self) -> Box<dyn InternalServiceFactory>;
|
||||
|
||||
fn create(&self) -> LocalBoxFuture<'static, Result<Vec<(Token, BoxedServerService)>, ()>>;
|
||||
}
|
||||
|
||||
pub(crate) type BoxedServerService = Box<
|
||||
dyn Service<
|
||||
(Option<CounterGuard>, StdStream),
|
||||
Response = (),
|
||||
Error = (),
|
||||
Future = Ready<Result<(), ()>>,
|
||||
>,
|
||||
>;
|
||||
|
||||
pub(crate) struct StreamService<S, I> {
|
||||
service: S,
|
||||
_phantom: PhantomData<I>,
|
||||
}
|
||||
|
||||
impl<S, I> StreamService<S, I> {
|
||||
pub(crate) fn new(service: S) -> Self {
|
||||
StreamService {
|
||||
service,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, I> Service<(Option<CounterGuard>, StdStream)> for StreamService<S, I>
|
||||
where
|
||||
S: Service<I>,
|
||||
S::Future: 'static,
|
||||
S::Error: 'static,
|
||||
I: FromStream,
|
||||
{
|
||||
type Response = ();
|
||||
type Error = ();
|
||||
type Future = Ready<Result<(), ()>>;
|
||||
|
||||
fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.service.poll_ready(ctx).map_err(|_| ())
|
||||
}
|
||||
|
||||
fn call(&mut self, (guard, req): (Option<CounterGuard>, StdStream)) -> Self::Future {
|
||||
match FromStream::from_stdstream(req) {
|
||||
Ok(stream) => {
|
||||
let f = self.service.call(stream);
|
||||
spawn(async move {
|
||||
let _ = f.await;
|
||||
drop(guard);
|
||||
});
|
||||
ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Can not convert to an async tcp stream: {}", e);
|
||||
err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct StreamNewService<F: ServiceFactory<Io>, Io: FromStream> {
|
||||
name: String,
|
||||
inner: F,
|
||||
token: Token,
|
||||
addr: SocketAddr,
|
||||
_t: PhantomData<Io>,
|
||||
}
|
||||
|
||||
impl<F, Io> StreamNewService<F, Io>
|
||||
where
|
||||
F: ServiceFactory<Io>,
|
||||
Io: FromStream + Send + 'static,
|
||||
{
|
||||
pub(crate) fn create(
|
||||
name: String,
|
||||
token: Token,
|
||||
inner: F,
|
||||
addr: SocketAddr,
|
||||
) -> Box<dyn InternalServiceFactory> {
|
||||
Box::new(Self {
|
||||
name,
|
||||
token,
|
||||
inner,
|
||||
addr,
|
||||
_t: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, Io> InternalServiceFactory for StreamNewService<F, Io>
|
||||
where
|
||||
F: ServiceFactory<Io>,
|
||||
Io: FromStream + Send + 'static,
|
||||
{
|
||||
fn name(&self, _: Token) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn clone_factory(&self) -> Box<dyn InternalServiceFactory> {
|
||||
Box::new(Self {
|
||||
name: self.name.clone(),
|
||||
inner: self.inner.clone(),
|
||||
token: self.token,
|
||||
addr: self.addr,
|
||||
_t: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
fn create(&self) -> LocalBoxFuture<'static, Result<Vec<(Token, BoxedServerService)>, ()>> {
|
||||
let token = self.token;
|
||||
self.inner
|
||||
.create()
|
||||
.new_service(())
|
||||
.map_err(|_| ())
|
||||
.map_ok(move |inner| {
|
||||
let service: BoxedServerService = Box::new(StreamService::new(inner));
|
||||
vec![(token, service)]
|
||||
})
|
||||
.boxed_local()
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, T, I> ServiceFactory<I> for F
|
||||
where
|
||||
F: Fn() -> T + Send + Clone + 'static,
|
||||
T: BaseServiceFactory<I, Config = ()>,
|
||||
I: FromStream,
|
||||
{
|
||||
type Factory = T;
|
||||
|
||||
fn create(&self) -> T {
|
||||
(self)()
|
||||
}
|
||||
}
|
@@ -1,100 +0,0 @@
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use futures_util::future::lazy;
|
||||
|
||||
use crate::server::Server;
|
||||
|
||||
/// Different types of process signals
|
||||
#[allow(dead_code)]
|
||||
#[derive(PartialEq, Clone, Copy, Debug)]
|
||||
pub(crate) enum Signal {
|
||||
/// SIGHUP
|
||||
Hup,
|
||||
/// SIGINT
|
||||
Int,
|
||||
/// SIGTERM
|
||||
Term,
|
||||
/// SIGQUIT
|
||||
Quit,
|
||||
}
|
||||
|
||||
pub(crate) struct Signals {
|
||||
srv: Server,
|
||||
#[cfg(not(unix))]
|
||||
stream: Pin<Box<dyn Future<Output = std::io::Result<()>>>>,
|
||||
#[cfg(unix)]
|
||||
streams: Vec<(Signal, actix_rt::signal::unix::Signal)>,
|
||||
}
|
||||
|
||||
impl Signals {
|
||||
pub(crate) fn start(srv: Server) {
|
||||
actix_rt::spawn(lazy(|_| {
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
actix_rt::spawn(Signals {
|
||||
srv,
|
||||
stream: Box::pin(actix_rt::signal::ctrl_c()),
|
||||
});
|
||||
}
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use actix_rt::signal::unix;
|
||||
|
||||
let mut streams = Vec::new();
|
||||
|
||||
let sig_map = [
|
||||
(unix::SignalKind::interrupt(), Signal::Int),
|
||||
(unix::SignalKind::hangup(), Signal::Hup),
|
||||
(unix::SignalKind::terminate(), Signal::Term),
|
||||
(unix::SignalKind::quit(), Signal::Quit),
|
||||
];
|
||||
|
||||
for (kind, sig) in sig_map.iter() {
|
||||
match unix::signal(*kind) {
|
||||
Ok(stream) => streams.push((*sig, stream)),
|
||||
Err(e) => log::error!(
|
||||
"Can not initialize stream handler for {:?} err: {}",
|
||||
sig,
|
||||
e
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
actix_rt::spawn(Signals { srv, streams })
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for Signals {
|
||||
type Output = ();
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
#[cfg(not(unix))]
|
||||
match Pin::new(&mut self.stream).poll(cx) {
|
||||
Poll::Ready(_) => {
|
||||
self.srv.signal(Signal::Int);
|
||||
Poll::Ready(())
|
||||
}
|
||||
Poll::Pending => return Poll::Pending,
|
||||
}
|
||||
#[cfg(unix)]
|
||||
{
|
||||
for idx in 0..self.streams.len() {
|
||||
loop {
|
||||
match self.streams[idx].1.poll_recv(cx) {
|
||||
Poll::Ready(None) => return Poll::Ready(()),
|
||||
Poll::Pending => break,
|
||||
Poll::Ready(Some(_)) => {
|
||||
let sig = self.streams[idx].0;
|
||||
self.srv.signal(sig);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,170 +0,0 @@
|
||||
use std::{fmt, io, net};
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite};
|
||||
use actix_rt::net::TcpStream;
|
||||
|
||||
pub(crate) enum StdListener {
|
||||
Tcp(net::TcpListener),
|
||||
#[cfg(all(unix))]
|
||||
Uds(std::os::unix::net::UnixListener),
|
||||
}
|
||||
|
||||
pub(crate) enum SocketAddr {
|
||||
Tcp(net::SocketAddr),
|
||||
#[cfg(all(unix))]
|
||||
Uds(std::os::unix::net::SocketAddr),
|
||||
}
|
||||
|
||||
impl fmt::Display for SocketAddr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
SocketAddr::Tcp(ref addr) => write!(f, "{}", addr),
|
||||
#[cfg(all(unix))]
|
||||
SocketAddr::Uds(ref addr) => write!(f, "{:?}", addr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for SocketAddr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
SocketAddr::Tcp(ref addr) => write!(f, "{:?}", addr),
|
||||
#[cfg(all(unix))]
|
||||
SocketAddr::Uds(ref addr) => write!(f, "{:?}", addr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for StdListener {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
StdListener::Tcp(ref lst) => write!(f, "{}", lst.local_addr().ok().unwrap()),
|
||||
#[cfg(all(unix))]
|
||||
StdListener::Uds(ref lst) => write!(f, "{:?}", lst.local_addr().ok().unwrap()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StdListener {
|
||||
pub(crate) fn local_addr(&self) -> SocketAddr {
|
||||
match self {
|
||||
StdListener::Tcp(lst) => SocketAddr::Tcp(lst.local_addr().unwrap()),
|
||||
#[cfg(all(unix))]
|
||||
StdListener::Uds(lst) => SocketAddr::Uds(lst.local_addr().unwrap()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn into_listener(self) -> SocketListener {
|
||||
match self {
|
||||
StdListener::Tcp(lst) => SocketListener::Tcp(
|
||||
mio::net::TcpListener::from_std(lst)
|
||||
.expect("Can not create mio::net::TcpListener"),
|
||||
),
|
||||
#[cfg(all(unix))]
|
||||
StdListener::Uds(lst) => SocketListener::Uds(
|
||||
mio_uds::UnixListener::from_listener(lst)
|
||||
.expect("Can not create mio_uds::UnixListener"),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum StdStream {
|
||||
Tcp(std::net::TcpStream),
|
||||
#[cfg(all(unix))]
|
||||
Uds(std::os::unix::net::UnixStream),
|
||||
}
|
||||
|
||||
pub(crate) enum SocketListener {
|
||||
Tcp(mio::net::TcpListener),
|
||||
#[cfg(all(unix))]
|
||||
Uds(mio_uds::UnixListener),
|
||||
}
|
||||
|
||||
impl SocketListener {
|
||||
pub(crate) fn accept(&self) -> io::Result<Option<(StdStream, SocketAddr)>> {
|
||||
match *self {
|
||||
SocketListener::Tcp(ref lst) => lst
|
||||
.accept_std()
|
||||
.map(|(stream, addr)| Some((StdStream::Tcp(stream), SocketAddr::Tcp(addr)))),
|
||||
#[cfg(all(unix))]
|
||||
SocketListener::Uds(ref lst) => lst.accept_std().map(|res| {
|
||||
res.map(|(stream, addr)| (StdStream::Uds(stream), SocketAddr::Uds(addr)))
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl mio::Evented for SocketListener {
|
||||
fn register(
|
||||
&self,
|
||||
poll: &mio::Poll,
|
||||
token: mio::Token,
|
||||
interest: mio::Ready,
|
||||
opts: mio::PollOpt,
|
||||
) -> io::Result<()> {
|
||||
match *self {
|
||||
SocketListener::Tcp(ref lst) => lst.register(poll, token, interest, opts),
|
||||
#[cfg(all(unix))]
|
||||
SocketListener::Uds(ref lst) => lst.register(poll, token, interest, opts),
|
||||
}
|
||||
}
|
||||
|
||||
fn reregister(
|
||||
&self,
|
||||
poll: &mio::Poll,
|
||||
token: mio::Token,
|
||||
interest: mio::Ready,
|
||||
opts: mio::PollOpt,
|
||||
) -> io::Result<()> {
|
||||
match *self {
|
||||
SocketListener::Tcp(ref lst) => lst.reregister(poll, token, interest, opts),
|
||||
#[cfg(all(unix))]
|
||||
SocketListener::Uds(ref lst) => lst.reregister(poll, token, interest, opts),
|
||||
}
|
||||
}
|
||||
fn deregister(&self, poll: &mio::Poll) -> io::Result<()> {
|
||||
match *self {
|
||||
SocketListener::Tcp(ref lst) => lst.deregister(poll),
|
||||
#[cfg(all(unix))]
|
||||
SocketListener::Uds(ref lst) => {
|
||||
let res = lst.deregister(poll);
|
||||
|
||||
// cleanup file path
|
||||
if let Ok(addr) = lst.local_addr() {
|
||||
if let Some(path) = addr.as_pathname() {
|
||||
let _ = std::fs::remove_file(path);
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FromStream: AsyncRead + AsyncWrite + Sized {
|
||||
fn from_stdstream(sock: StdStream) -> io::Result<Self>;
|
||||
}
|
||||
|
||||
impl FromStream for TcpStream {
|
||||
fn from_stdstream(sock: StdStream) -> io::Result<Self> {
|
||||
match sock {
|
||||
StdStream::Tcp(stream) => TcpStream::from_std(stream),
|
||||
#[cfg(all(unix))]
|
||||
StdStream::Uds(_) => {
|
||||
panic!("Should not happen, bug in server impl");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(unix))]
|
||||
impl FromStream for actix_rt::net::UnixStream {
|
||||
fn from_stdstream(sock: StdStream) -> io::Result<Self> {
|
||||
match sock {
|
||||
StdStream::Tcp(_) => panic!("Should not happen, bug in server impl"),
|
||||
StdStream::Uds(stream) => actix_rt::net::UnixStream::from_std(stream),
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,482 +0,0 @@
|
||||
use std::pin::Pin;
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
use std::time;
|
||||
|
||||
use actix_rt::time::{delay_until, Delay, Instant};
|
||||
use actix_rt::{spawn, Arbiter};
|
||||
use actix_utils::counter::Counter;
|
||||
use futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
|
||||
use futures_channel::oneshot;
|
||||
use futures_util::future::{join_all, LocalBoxFuture, MapOk};
|
||||
use futures_util::{future::Future, stream::Stream, FutureExt, TryFutureExt};
|
||||
use log::{error, info, trace};
|
||||
|
||||
use crate::accept::AcceptNotify;
|
||||
use crate::service::{BoxedServerService, InternalServiceFactory};
|
||||
use crate::socket::{SocketAddr, StdStream};
|
||||
use crate::Token;
|
||||
|
||||
pub(crate) struct WorkerCommand(Conn);
|
||||
|
||||
/// Stop worker message. Returns `true` on successful shutdown
|
||||
/// and `false` if some connections still alive.
|
||||
pub(crate) struct StopCommand {
|
||||
graceful: bool,
|
||||
result: oneshot::Sender<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Conn {
|
||||
pub io: StdStream,
|
||||
pub token: Token,
|
||||
pub peer: Option<SocketAddr>,
|
||||
}
|
||||
|
||||
static MAX_CONNS: AtomicUsize = AtomicUsize::new(25600);
|
||||
|
||||
/// Sets the maximum per-worker number of concurrent connections.
|
||||
///
|
||||
/// All socket listeners will stop accepting connections when this limit is
|
||||
/// reached for each worker.
|
||||
///
|
||||
/// By default max connections is set to a 25k per worker.
|
||||
pub fn max_concurrent_connections(num: usize) {
|
||||
MAX_CONNS.store(num, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub(crate) fn num_connections() -> usize {
|
||||
MAX_CONNS_COUNTER.with(|conns| conns.total())
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static MAX_CONNS_COUNTER: Counter =
|
||||
Counter::new(MAX_CONNS.load(Ordering::Relaxed));
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct WorkerClient {
|
||||
pub idx: usize,
|
||||
tx1: UnboundedSender<WorkerCommand>,
|
||||
tx2: UnboundedSender<StopCommand>,
|
||||
avail: WorkerAvailability,
|
||||
}
|
||||
|
||||
impl WorkerClient {
|
||||
pub fn new(
|
||||
idx: usize,
|
||||
tx1: UnboundedSender<WorkerCommand>,
|
||||
tx2: UnboundedSender<StopCommand>,
|
||||
avail: WorkerAvailability,
|
||||
) -> Self {
|
||||
WorkerClient {
|
||||
idx,
|
||||
tx1,
|
||||
tx2,
|
||||
avail,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send(&self, msg: Conn) -> Result<(), Conn> {
|
||||
self.tx1
|
||||
.unbounded_send(WorkerCommand(msg))
|
||||
.map_err(|msg| msg.into_inner().0)
|
||||
}
|
||||
|
||||
pub fn available(&self) -> bool {
|
||||
self.avail.available()
|
||||
}
|
||||
|
||||
pub fn stop(&self, graceful: bool) -> oneshot::Receiver<bool> {
|
||||
let (result, rx) = oneshot::channel();
|
||||
let _ = self.tx2.unbounded_send(StopCommand { graceful, result });
|
||||
rx
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct WorkerAvailability {
|
||||
notify: AcceptNotify,
|
||||
available: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl WorkerAvailability {
|
||||
pub fn new(notify: AcceptNotify) -> Self {
|
||||
WorkerAvailability {
|
||||
notify,
|
||||
available: Arc::new(AtomicBool::new(false)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn available(&self) -> bool {
|
||||
self.available.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
pub fn set(&self, val: bool) {
|
||||
let old = self.available.swap(val, Ordering::Release);
|
||||
if !old && val {
|
||||
self.notify.notify()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Service worker
|
||||
///
|
||||
/// Worker accepts Socket objects via unbounded channel and starts stream
|
||||
/// processing.
|
||||
pub(crate) struct Worker {
|
||||
rx: UnboundedReceiver<WorkerCommand>,
|
||||
rx2: UnboundedReceiver<StopCommand>,
|
||||
services: Vec<WorkerService>,
|
||||
availability: WorkerAvailability,
|
||||
conns: Counter,
|
||||
factories: Vec<Box<dyn InternalServiceFactory>>,
|
||||
state: WorkerState,
|
||||
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 {
|
||||
pub(crate) fn start(
|
||||
idx: usize,
|
||||
factories: Vec<Box<dyn InternalServiceFactory>>,
|
||||
availability: WorkerAvailability,
|
||||
shutdown_timeout: time::Duration,
|
||||
) -> WorkerClient {
|
||||
let (tx1, rx) = unbounded();
|
||||
let (tx2, rx2) = unbounded();
|
||||
let avail = availability.clone();
|
||||
|
||||
Arbiter::new().send(
|
||||
async move {
|
||||
availability.set(false);
|
||||
let mut wrk = MAX_CONNS_COUNTER.with(move |conns| Worker {
|
||||
rx,
|
||||
rx2,
|
||||
availability,
|
||||
factories,
|
||||
shutdown_timeout,
|
||||
services: Vec::new(),
|
||||
conns: conns.clone(),
|
||||
state: WorkerState::Unavailable(Vec::new()),
|
||||
});
|
||||
|
||||
let mut fut: Vec<MapOk<LocalBoxFuture<'static, _>, _>> = Vec::new();
|
||||
for (idx, factory) in wrk.factories.iter().enumerate() {
|
||||
fut.push(factory.create().map_ok(move |r| {
|
||||
r.into_iter()
|
||||
.map(|(t, s): (Token, _)| (idx, t, s))
|
||||
.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,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Can not start worker: {:?}", e);
|
||||
Arbiter::current().stop();
|
||||
}
|
||||
}
|
||||
wrk.await
|
||||
});
|
||||
}
|
||||
.boxed(),
|
||||
);
|
||||
|
||||
WorkerClient::new(idx, tx1, tx2, avail)
|
||||
}
|
||||
|
||||
fn shutdown(&mut self, force: bool) {
|
||||
if force {
|
||||
self.services.iter_mut().for_each(|srv| {
|
||||
if srv.status == WorkerServiceStatus::Available {
|
||||
srv.status = WorkerServiceStatus::Stopped;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
self.services.iter_mut().for_each(move |srv| {
|
||||
if srv.status == WorkerServiceStatus::Available {
|
||||
srv.status = WorkerServiceStatus::Stopping;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn check_readiness(&mut self, cx: &mut Context<'_>) -> Result<bool, (Token, usize)> {
|
||||
let mut ready = self.conns.available(cx);
|
||||
let mut failed = None;
|
||||
for (idx, srv) in &mut self.services.iter_mut().enumerate() {
|
||||
if srv.status == WorkerServiceStatus::Available
|
||||
|| srv.status == WorkerServiceStatus::Unavailable
|
||||
{
|
||||
match srv.service.poll_ready(cx) {
|
||||
Poll::Ready(Ok(_)) => {
|
||||
if srv.status == WorkerServiceStatus::Unavailable {
|
||||
trace!(
|
||||
"Service {:?} is available",
|
||||
self.factories[srv.factory].name(Token(idx))
|
||||
);
|
||||
srv.status = WorkerServiceStatus::Available;
|
||||
}
|
||||
}
|
||||
Poll::Pending => {
|
||||
ready = false;
|
||||
|
||||
if srv.status == WorkerServiceStatus::Available {
|
||||
trace!(
|
||||
"Service {:?} is unavailable",
|
||||
self.factories[srv.factory].name(Token(idx))
|
||||
);
|
||||
srv.status = WorkerServiceStatus::Unavailable;
|
||||
}
|
||||
}
|
||||
Poll::Ready(Err(_)) => {
|
||||
error!(
|
||||
"Service {:?} readiness check returned error, restarting",
|
||||
self.factories[srv.factory].name(Token(idx))
|
||||
);
|
||||
failed = Some((Token(idx), srv.factory));
|
||||
srv.status = WorkerServiceStatus::Failed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(idx) = failed {
|
||||
Err(idx)
|
||||
} else {
|
||||
Ok(ready)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum WorkerState {
|
||||
Available,
|
||||
Unavailable(Vec<Conn>),
|
||||
Restarting(
|
||||
usize,
|
||||
Token,
|
||||
#[allow(clippy::type_complexity)]
|
||||
Pin<Box<dyn Future<Output = Result<Vec<(Token, BoxedServerService)>, ()>>>>,
|
||||
),
|
||||
Shutdown(
|
||||
Pin<Box<Delay>>,
|
||||
Pin<Box<Delay>>,
|
||||
Option<oneshot::Sender<bool>>,
|
||||
),
|
||||
}
|
||||
|
||||
impl Future for Worker {
|
||||
type Output = ();
|
||||
|
||||
// FIXME: remove this attribute
|
||||
#[allow(clippy::never_loop)]
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
// `StopWorker` message handler
|
||||
if let Poll::Ready(Some(StopCommand { graceful, result })) =
|
||||
Pin::new(&mut self.rx2).poll_next(cx)
|
||||
{
|
||||
self.availability.set(false);
|
||||
let num = num_connections();
|
||||
if num == 0 {
|
||||
info!("Shutting down worker, 0 connections");
|
||||
let _ = result.send(true);
|
||||
return Poll::Ready(());
|
||||
} else if graceful {
|
||||
self.shutdown(false);
|
||||
let num = num_connections();
|
||||
if num != 0 {
|
||||
info!("Graceful worker shutdown, {} connections", num);
|
||||
self.state = WorkerState::Shutdown(
|
||||
Box::pin(delay_until(Instant::now() + time::Duration::from_secs(1))),
|
||||
Box::pin(delay_until(Instant::now() + self.shutdown_timeout)),
|
||||
Some(result),
|
||||
);
|
||||
} else {
|
||||
let _ = result.send(true);
|
||||
return Poll::Ready(());
|
||||
}
|
||||
} else {
|
||||
info!("Force shutdown worker, {} connections", num);
|
||||
self.shutdown(true);
|
||||
let _ = result.send(false);
|
||||
return Poll::Ready(());
|
||||
}
|
||||
}
|
||||
|
||||
match self.state {
|
||||
WorkerState::Unavailable(ref mut conns) => {
|
||||
let conn = conns.pop();
|
||||
match self.check_readiness(cx) {
|
||||
Ok(true) => {
|
||||
// process requests from wait queue
|
||||
if let Some(conn) = conn {
|
||||
let guard = self.conns.get();
|
||||
let _ = self.services[conn.token.0]
|
||||
.service
|
||||
.call((Some(guard), conn.io));
|
||||
} else {
|
||||
self.state = WorkerState::Available;
|
||||
self.availability.set(true);
|
||||
}
|
||||
self.poll(cx)
|
||||
}
|
||||
Ok(false) => {
|
||||
// push connection back to queue
|
||||
if let Some(conn) = conn {
|
||||
if let WorkerState::Unavailable(ref mut conns) = self.state {
|
||||
conns.push(conn);
|
||||
}
|
||||
}
|
||||
Poll::Pending
|
||||
}
|
||||
Err((token, idx)) => {
|
||||
trace!(
|
||||
"Service {:?} failed, restarting",
|
||||
self.factories[idx].name(token)
|
||||
);
|
||||
self.services[token.0].status = WorkerServiceStatus::Restarting;
|
||||
self.state =
|
||||
WorkerState::Restarting(idx, token, self.factories[idx].create());
|
||||
self.poll(cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
WorkerState::Restarting(idx, token, ref mut fut) => {
|
||||
match Pin::new(fut).poll(cx) {
|
||||
Poll::Ready(Ok(item)) => {
|
||||
for (token, service) in item {
|
||||
trace!(
|
||||
"Service {:?} has been restarted",
|
||||
self.factories[idx].name(token)
|
||||
);
|
||||
self.services[token.0].created(service);
|
||||
self.state = WorkerState::Unavailable(Vec::new());
|
||||
return self.poll(cx);
|
||||
}
|
||||
}
|
||||
Poll::Ready(Err(_)) => {
|
||||
panic!(
|
||||
"Can not restart {:?} service",
|
||||
self.factories[idx].name(token)
|
||||
);
|
||||
}
|
||||
Poll::Pending => {
|
||||
return Poll::Pending;
|
||||
}
|
||||
}
|
||||
self.poll(cx)
|
||||
}
|
||||
WorkerState::Shutdown(ref mut t1, ref mut t2, ref mut tx) => {
|
||||
let num = num_connections();
|
||||
if num == 0 {
|
||||
let _ = tx.take().unwrap().send(true);
|
||||
Arbiter::current().stop();
|
||||
return Poll::Ready(());
|
||||
}
|
||||
|
||||
// check graceful timeout
|
||||
match t2.as_mut().poll(cx) {
|
||||
Poll::Pending => (),
|
||||
Poll::Ready(_) => {
|
||||
let _ = tx.take().unwrap().send(false);
|
||||
self.shutdown(true);
|
||||
Arbiter::current().stop();
|
||||
return Poll::Ready(());
|
||||
}
|
||||
}
|
||||
|
||||
// sleep for 1 second and then check again
|
||||
match t1.as_mut().poll(cx) {
|
||||
Poll::Pending => (),
|
||||
Poll::Ready(_) => {
|
||||
*t1 = Box::pin(delay_until(
|
||||
Instant::now() + time::Duration::from_secs(1),
|
||||
));
|
||||
let _ = t1.as_mut().poll(cx);
|
||||
}
|
||||
}
|
||||
Poll::Pending
|
||||
}
|
||||
WorkerState::Available => {
|
||||
loop {
|
||||
match Pin::new(&mut self.rx).poll_next(cx) {
|
||||
// handle incoming io stream
|
||||
Poll::Ready(Some(WorkerCommand(msg))) => {
|
||||
match self.check_readiness(cx) {
|
||||
Ok(true) => {
|
||||
let guard = self.conns.get();
|
||||
let _ = self.services[msg.token.0]
|
||||
.service
|
||||
.call((Some(guard), msg.io));
|
||||
continue;
|
||||
}
|
||||
Ok(false) => {
|
||||
trace!("Worker is unavailable");
|
||||
self.availability.set(false);
|
||||
self.state = WorkerState::Unavailable(vec![msg]);
|
||||
}
|
||||
Err((token, idx)) => {
|
||||
trace!(
|
||||
"Service {:?} failed, restarting",
|
||||
self.factories[idx].name(token)
|
||||
);
|
||||
self.availability.set(false);
|
||||
self.services[token.0].status =
|
||||
WorkerServiceStatus::Restarting;
|
||||
self.state = WorkerState::Restarting(
|
||||
idx,
|
||||
token,
|
||||
self.factories[idx].create(),
|
||||
);
|
||||
}
|
||||
}
|
||||
return self.poll(cx);
|
||||
}
|
||||
Poll::Pending => {
|
||||
self.state = WorkerState::Available;
|
||||
return Poll::Pending;
|
||||
}
|
||||
Poll::Ready(None) => return Poll::Ready(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,191 +0,0 @@
|
||||
use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
|
||||
use std::sync::{mpsc, Arc};
|
||||
use std::{net, thread, time};
|
||||
|
||||
use actix_server::Server;
|
||||
use actix_service::fn_service;
|
||||
use futures_util::future::{lazy, ok};
|
||||
use socket2::{Domain, Protocol, Socket, Type};
|
||||
|
||||
fn unused_addr() -> net::SocketAddr {
|
||||
let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
|
||||
let socket = Socket::new(Domain::ipv4(), Type::stream(), Some(Protocol::tcp())).unwrap();
|
||||
socket.bind(&addr.into()).unwrap();
|
||||
socket.set_reuse_address(true).unwrap();
|
||||
let tcp = socket.into_tcp_listener();
|
||||
tcp.local_addr().unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bind() {
|
||||
let addr = unused_addr();
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
let h = thread::spawn(move || {
|
||||
let mut sys = actix_rt::System::new("test");
|
||||
|
||||
let srv = sys.block_on(lazy(|_| {
|
||||
Server::build()
|
||||
.workers(1)
|
||||
.disable_signals()
|
||||
.bind("test", addr, move || fn_service(|_| ok::<_, ()>(())))
|
||||
.unwrap()
|
||||
.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(addr).is_ok());
|
||||
sys.stop();
|
||||
let _ = h.join();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_listen() {
|
||||
let addr = unused_addr();
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
let h = thread::spawn(move || {
|
||||
let mut sys = actix_rt::System::new("test");
|
||||
let lst = net::TcpListener::bind(addr).unwrap();
|
||||
sys.block_on(lazy(|_| {
|
||||
Server::build()
|
||||
.disable_signals()
|
||||
.workers(1)
|
||||
.listen("test", lst, move || fn_service(|_| ok::<_, ()>(())))
|
||||
.unwrap()
|
||||
.start()
|
||||
}));
|
||||
let _ = tx.send(actix_rt::System::current());
|
||||
let _ = sys.run();
|
||||
});
|
||||
let sys = rx.recv().unwrap();
|
||||
|
||||
thread::sleep(time::Duration::from_millis(500));
|
||||
assert!(net::TcpStream::connect(addr).is_ok());
|
||||
sys.stop();
|
||||
let _ = h.join();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
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 (tx, rx) = mpsc::channel();
|
||||
|
||||
let h = thread::spawn(move || {
|
||||
let mut sys = actix_rt::System::new("test");
|
||||
let srv = sys.block_on(lazy(|_| {
|
||||
Server::build()
|
||||
.backlog(100)
|
||||
.disable_signals()
|
||||
.bind("test", addr, move || {
|
||||
fn_service(|io: TcpStream| async move {
|
||||
let mut f = Framed::new(io, BytesCodec);
|
||||
f.send(Bytes::from_static(b"test")).await.unwrap();
|
||||
Ok::<_, ()>(())
|
||||
})
|
||||
})
|
||||
.unwrap()
|
||||
.start()
|
||||
}));
|
||||
|
||||
let _ = tx.send((srv, actix_rt::System::current()));
|
||||
let _ = sys.run();
|
||||
});
|
||||
let (srv, sys) = rx.recv().unwrap();
|
||||
|
||||
let mut buf = [1u8; 4];
|
||||
let mut conn = net::TcpStream::connect(addr).unwrap();
|
||||
let _ = conn.read_exact(&mut buf);
|
||||
assert_eq!(buf, b"test"[..]);
|
||||
|
||||
// pause
|
||||
let _ = srv.pause();
|
||||
thread::sleep(time::Duration::from_millis(200));
|
||||
let mut conn = net::TcpStream::connect(addr).unwrap();
|
||||
conn.set_read_timeout(Some(time::Duration::from_millis(100)))
|
||||
.unwrap();
|
||||
let res = conn.read_exact(&mut buf);
|
||||
assert!(res.is_err());
|
||||
|
||||
// resume
|
||||
let _ = srv.resume();
|
||||
thread::sleep(time::Duration::from_millis(100));
|
||||
assert!(net::TcpStream::connect(addr).is_ok());
|
||||
assert!(net::TcpStream::connect(addr).is_ok());
|
||||
assert!(net::TcpStream::connect(addr).is_ok());
|
||||
|
||||
let mut buf = [0u8; 4];
|
||||
let mut conn = net::TcpStream::connect(addr).unwrap();
|
||||
let _ = conn.read_exact(&mut buf);
|
||||
assert_eq!(buf, b"test"[..]);
|
||||
|
||||
// stop
|
||||
let _ = srv.stop(false);
|
||||
thread::sleep(time::Duration::from_millis(100));
|
||||
assert!(net::TcpStream::connect(addr).is_err());
|
||||
|
||||
thread::sleep(time::Duration::from_millis(100));
|
||||
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 mut sys = actix_rt::System::new("test");
|
||||
let srv = sys.block_on(lazy(|_| {
|
||||
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();
|
||||
}
|
@@ -1,300 +0,0 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2020-xx-xx
|
||||
|
||||
|
||||
## 2.0.0-beta.1 - 2020-12-28
|
||||
* `Service`, other traits, and many type signatures now take the the request type as a type
|
||||
parameter instead of an associated type. [#232]
|
||||
* Add `always_ready!` and `forward_ready!` macros. [#233]
|
||||
* Crate is now `no_std`. [#233]
|
||||
* Migrate pin projections to `pin-project-lite`. [#233]
|
||||
* Remove `AndThenApplyFn` and Pipeline `and_then_apply_fn`. Use the
|
||||
`.and_then(apply_fn(...))` construction. [#233]
|
||||
* Move non-vital methods to `ServiceExt` and `ServiceFactoryExt` extension traits. [#235]
|
||||
|
||||
[#232]: https://github.com/actix/actix-net/pull/232
|
||||
[#233]: https://github.com/actix/actix-net/pull/233
|
||||
[#235]: https://github.com/actix/actix-net/pull/235
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
### Added
|
||||
|
||||
* Add `new_apply_cfg` function
|
||||
|
||||
## [0.4.0] - 2019-05-12
|
||||
|
||||
### Changed
|
||||
|
||||
* Use associated type for `NewService` config
|
||||
|
||||
* Change `apply_cfg` function
|
||||
|
||||
* Renamed helper functions
|
||||
|
||||
### Added
|
||||
|
||||
* Add `NewService::map_config` and `NewService::unit_config` combinators
|
||||
|
||||
|
||||
## [0.3.6] - 2019-04-07
|
||||
|
||||
### Changed
|
||||
|
||||
* Poll boxed service call result immediately
|
||||
|
||||
|
||||
## [0.3.5] - 2019-03-29
|
||||
|
||||
### Added
|
||||
|
||||
* Add `impl<S: Service> Service for Rc<RefCell<S>>`
|
||||
|
||||
|
||||
## [0.3.4] - 2019-03-12
|
||||
|
||||
### Added
|
||||
|
||||
* Add `Transform::from_err()` combinator
|
||||
|
||||
* Add `apply_fn` helper
|
||||
|
||||
* Add `apply_fn_factory` helper
|
||||
|
||||
* Add `apply_transform` helper
|
||||
|
||||
* Add `apply_cfg` helper
|
||||
|
||||
|
||||
## [0.3.3] - 2019-03-09
|
||||
|
||||
### Added
|
||||
|
||||
* Add `ApplyTransform` new service for transform and new service.
|
||||
|
||||
* Add `NewService::apply_cfg()` combinator, allows to use
|
||||
nested `NewService` with different config parameter.
|
||||
|
||||
### Changed
|
||||
|
||||
* Revert IntoFuture change
|
||||
|
||||
|
||||
## [0.3.2] - 2019-03-04
|
||||
|
||||
### Changed
|
||||
|
||||
* Change `NewService::Future` and `Transform::Future` to the `IntoFuture` trait.
|
||||
|
||||
* Export `AndThenTransform` type
|
||||
|
||||
|
||||
## [0.3.1] - 2019-03-04
|
||||
|
||||
### Changed
|
||||
|
||||
* Simplify Transform trait
|
||||
|
||||
|
||||
## [0.3.0] - 2019-03-02
|
||||
|
||||
## Added
|
||||
|
||||
* Added boxed NewService and Service.
|
||||
|
||||
## Changed
|
||||
|
||||
* Added `Config` parameter to `NewService` trait.
|
||||
|
||||
* Added `Config` parameter to `NewTransform` trait.
|
||||
|
||||
|
||||
## [0.2.2] - 2019-02-19
|
||||
|
||||
### Added
|
||||
|
||||
* Added `NewService` impl for `Rc<S> where S: NewService`
|
||||
|
||||
* Added `NewService` impl for `Arc<S> where S: NewService`
|
||||
|
||||
|
||||
## [0.2.1] - 2019-02-03
|
||||
|
||||
### Changed
|
||||
|
||||
* Generalize `.apply` combinator with Transform trait
|
||||
|
||||
|
||||
## [0.2.0] - 2019-02-01
|
||||
|
||||
### Changed
|
||||
|
||||
* Use associated type instead of generic for Service definition.
|
||||
|
||||
* Before:
|
||||
|
||||
```rust
|
||||
impl Service<Request> for Client {
|
||||
type Response = Response;
|
||||
// ...
|
||||
}
|
||||
```
|
||||
* After:
|
||||
|
||||
```rust
|
||||
impl Service for Client {
|
||||
type Request = Request;
|
||||
type Response = Response;
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## [0.1.6] - 2019-01-24
|
||||
|
||||
### Changed
|
||||
|
||||
* Use `FnMut` instead of `Fn` for .apply() and .map() combinators and `FnService` type
|
||||
|
||||
* Change `.apply()` error semantic, new service's error is `From<Self::Error>`
|
||||
|
||||
|
||||
## [0.1.5] - 2019-01-13
|
||||
|
||||
### Changed
|
||||
|
||||
* Make `Out::Error` convertable from `T::Error` for apply combinator
|
||||
|
||||
|
||||
## [0.1.4] - 2019-01-11
|
||||
|
||||
### Changed
|
||||
|
||||
* Use `FnMut` instead of `Fn` for `FnService`
|
||||
|
||||
|
||||
## [0.1.3] - 2018-12-12
|
||||
|
||||
### Changed
|
||||
|
||||
* Split service combinators to separate trait
|
||||
|
||||
|
||||
## [0.1.2] - 2018-12-12
|
||||
|
||||
### Fixed
|
||||
|
||||
* Release future early for `.and_then()` and `.then()` combinators
|
||||
|
||||
|
||||
## [0.1.1] - 2018-12-09
|
||||
|
||||
### Added
|
||||
|
||||
* Added Service impl for Box<S: Service>
|
||||
|
||||
|
||||
## [0.1.0] - 2018-12-09
|
||||
|
||||
* Initial import
|
@@ -1,28 +0,0 @@
|
||||
[package]
|
||||
name = "actix-service"
|
||||
version = "2.0.0-beta.1"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
]
|
||||
description = "Service trait and combinators for representing asynchronous request/response operations."
|
||||
keywords = ["network", "framework", "async", "futures", "service"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-net.git"
|
||||
documentation = "https://docs.rs/actix-service"
|
||||
readme = "README.md"
|
||||
categories = ["network-programming", "asynchronous"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
name = "actix_service"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
futures-core = { version = "0.3.7", default-features = false }
|
||||
pin-project-lite = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "1.0.0"
|
||||
futures-util = { version = "0.3.7", default-features = false }
|
@@ -1 +0,0 @@
|
||||
../LICENSE-APACHE
|
@@ -1 +0,0 @@
|
||||
../LICENSE-MIT
|
@@ -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
|
@@ -1,354 +0,0 @@
|
||||
use alloc::rc::Rc;
|
||||
use core::{
|
||||
cell::RefCell,
|
||||
future::Future,
|
||||
marker::PhantomData,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use super::{Service, ServiceFactory};
|
||||
|
||||
/// Service for the `and_then` combinator, chaining a computation onto the end
|
||||
/// of another service which completes successfully.
|
||||
///
|
||||
/// This is created by the `Pipeline::and_then` method.
|
||||
pub(crate) struct AndThenService<A, B, Req>(Rc<RefCell<(A, B)>>, PhantomData<Req>);
|
||||
|
||||
impl<A, B, Req> AndThenService<A, B, Req> {
|
||||
/// Create new `AndThen` combinator
|
||||
pub(crate) fn new(a: A, b: B) -> Self
|
||||
where
|
||||
A: Service<Req>,
|
||||
B: Service<A::Response, Error = A::Error>,
|
||||
{
|
||||
Self(Rc::new(RefCell::new((a, b))), PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B, Req> Clone for AndThenService<A, B, Req> {
|
||||
fn clone(&self) -> Self {
|
||||
AndThenService(self.0.clone(), PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B, Req> Service<Req> for AndThenService<A, B, Req>
|
||||
where
|
||||
A: Service<Req>,
|
||||
B: Service<A::Response, Error = A::Error>,
|
||||
{
|
||||
type Response = B::Response;
|
||||
type Error = A::Error;
|
||||
type Future = AndThenServiceResponse<A, B, Req>;
|
||||
|
||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
let mut srv = self.0.borrow_mut();
|
||||
let not_ready = !srv.0.poll_ready(cx)?.is_ready();
|
||||
if !srv.1.poll_ready(cx)?.is_ready() || not_ready {
|
||||
Poll::Pending
|
||||
} else {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Req) -> Self::Future {
|
||||
AndThenServiceResponse {
|
||||
state: State::A {
|
||||
fut: self.0.borrow_mut().0.call(req),
|
||||
b: Some(self.0.clone()),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
pub(crate) struct AndThenServiceResponse<A, B, Req>
|
||||
where
|
||||
A: Service<Req>,
|
||||
B: Service<A::Response, Error = A::Error>,
|
||||
{
|
||||
#[pin]
|
||||
state: State<A, B, Req>,
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
#[project = StateProj]
|
||||
enum State<A, B, Req>
|
||||
where
|
||||
A: Service<Req>,
|
||||
B: Service<A::Response, Error = A::Error>,
|
||||
{
|
||||
A {
|
||||
#[pin]
|
||||
fut: A::Future,
|
||||
b: Option<Rc<RefCell<(A, B)>>>,
|
||||
},
|
||||
B {
|
||||
#[pin]
|
||||
fut: B::Future,
|
||||
},
|
||||
Empty,
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B, Req> Future for AndThenServiceResponse<A, B, Req>
|
||||
where
|
||||
A: Service<Req>,
|
||||
B: Service<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 = 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`")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `.and_then()` service factory combinator
|
||||
pub(crate) struct AndThenServiceFactory<A, B, Req>
|
||||
where
|
||||
A: ServiceFactory<Req>,
|
||||
A::Config: Clone,
|
||||
B: ServiceFactory<
|
||||
A::Response,
|
||||
Config = A::Config,
|
||||
Error = A::Error,
|
||||
InitError = A::InitError,
|
||||
>,
|
||||
{
|
||||
inner: Rc<(A, B)>,
|
||||
_phantom: PhantomData<Req>,
|
||||
}
|
||||
|
||||
impl<A, B, Req> AndThenServiceFactory<A, B, Req>
|
||||
where
|
||||
A: ServiceFactory<Req>,
|
||||
A::Config: Clone,
|
||||
B: ServiceFactory<
|
||||
A::Response,
|
||||
Config = A::Config,
|
||||
Error = A::Error,
|
||||
InitError = A::InitError,
|
||||
>,
|
||||
{
|
||||
/// Create new `AndThenFactory` combinator
|
||||
pub(crate) fn new(a: A, b: B) -> Self {
|
||||
Self {
|
||||
inner: Rc::new((a, b)),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B, Req> ServiceFactory<Req> for AndThenServiceFactory<A, B, Req>
|
||||
where
|
||||
A: ServiceFactory<Req>,
|
||||
A::Config: Clone,
|
||||
B: ServiceFactory<
|
||||
A::Response,
|
||||
Config = A::Config,
|
||||
Error = A::Error,
|
||||
InitError = A::InitError,
|
||||
>,
|
||||
{
|
||||
type Response = B::Response;
|
||||
type Error = A::Error;
|
||||
|
||||
type Config = A::Config;
|
||||
type Service = AndThenService<A::Service, B::Service, Req>;
|
||||
type InitError = A::InitError;
|
||||
type Future = AndThenServiceFactoryResponse<A, B, Req>;
|
||||
|
||||
fn new_service(&self, cfg: A::Config) -> Self::Future {
|
||||
let inner = &*self.inner;
|
||||
AndThenServiceFactoryResponse::new(
|
||||
inner.0.new_service(cfg.clone()),
|
||||
inner.1.new_service(cfg),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B, Req> Clone for AndThenServiceFactory<A, B, Req>
|
||||
where
|
||||
A: ServiceFactory<Req>,
|
||||
A::Config: Clone,
|
||||
B: ServiceFactory<
|
||||
A::Response,
|
||||
Config = A::Config,
|
||||
Error = A::Error,
|
||||
InitError = A::InitError,
|
||||
>,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.clone(),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
pub(crate) struct AndThenServiceFactoryResponse<A, B, Req>
|
||||
where
|
||||
A: ServiceFactory<Req>,
|
||||
B: ServiceFactory<A::Response>,
|
||||
{
|
||||
#[pin]
|
||||
fut_a: A::Future,
|
||||
#[pin]
|
||||
fut_b: B::Future,
|
||||
|
||||
a: Option<A::Service>,
|
||||
b: Option<B::Service>,
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B, Req> AndThenServiceFactoryResponse<A, B, Req>
|
||||
where
|
||||
A: ServiceFactory<Req>,
|
||||
B: ServiceFactory<A::Response>,
|
||||
{
|
||||
fn new(fut_a: A::Future, fut_b: B::Future) -> Self {
|
||||
AndThenServiceFactoryResponse {
|
||||
fut_a,
|
||||
fut_b,
|
||||
a: None,
|
||||
b: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B, Req> Future for AndThenServiceFactoryResponse<A, B, Req>
|
||||
where
|
||||
A: ServiceFactory<Req>,
|
||||
B: ServiceFactory<A::Response, Error = A::Error, InitError = A::InitError>,
|
||||
{
|
||||
type Output = Result<AndThenService<A::Service, B::Service, Req>, A::InitError>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
|
||||
if this.a.is_none() {
|
||||
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)? {
|
||||
*this.b = Some(service);
|
||||
}
|
||||
}
|
||||
if this.a.is_some() && this.b.is_some() {
|
||||
Poll::Ready(Ok(AndThenService::new(
|
||||
this.a.take().unwrap(),
|
||||
this.b.take().unwrap(),
|
||||
)))
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::rc::Rc;
|
||||
use core::{
|
||||
cell::Cell,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use futures_util::future::lazy;
|
||||
|
||||
use crate::{
|
||||
fn_factory, ok, pipeline, pipeline_factory, ready, Ready, Service, ServiceFactory,
|
||||
};
|
||||
|
||||
struct Srv1(Rc<Cell<usize>>);
|
||||
|
||||
impl Service<&'static str> for Srv1 {
|
||||
type Response = &'static str;
|
||||
type Error = ();
|
||||
type Future = Ready<Result<Self::Response, ()>>;
|
||||
|
||||
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.0.set(self.0.get() + 1);
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: &'static str) -> Self::Future {
|
||||
ok(req)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Srv2(Rc<Cell<usize>>);
|
||||
|
||||
impl Service<&'static str> for Srv2 {
|
||||
type Response = (&'static str, &'static str);
|
||||
type Error = ();
|
||||
type Future = Ready<Result<Self::Response, ()>>;
|
||||
|
||||
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.0.set(self.0.get() + 1);
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: &'static str) -> Self::Future {
|
||||
ok((req, "srv2"))
|
||||
}
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_poll_ready() {
|
||||
let cnt = Rc::new(Cell::new(0));
|
||||
let mut srv = pipeline(Srv1(cnt.clone())).and_then(Srv2(cnt.clone()));
|
||||
let res = lazy(|cx| srv.poll_ready(cx)).await;
|
||||
assert_eq!(res, Poll::Ready(Ok(())));
|
||||
assert_eq!(cnt.get(), 2);
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_call() {
|
||||
let cnt = Rc::new(Cell::new(0));
|
||||
let mut srv = pipeline(Srv1(cnt.clone())).and_then(Srv2(cnt));
|
||||
let res = srv.call("srv1").await;
|
||||
assert!(res.is_ok());
|
||||
assert_eq!(res.unwrap(), ("srv1", "srv2"));
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_new_service() {
|
||||
let cnt = Rc::new(Cell::new(0));
|
||||
let cnt2 = cnt.clone();
|
||||
let new_srv =
|
||||
pipeline_factory(fn_factory(move || ready(Ok::<_, ()>(Srv1(cnt2.clone())))))
|
||||
.and_then(move || ready(Ok(Srv2(cnt.clone()))));
|
||||
|
||||
let mut srv = new_srv.new_service(()).await.unwrap();
|
||||
let res = srv.call("srv1").await;
|
||||
assert!(res.is_ok());
|
||||
assert_eq!(res.unwrap(), ("srv1", "srv2"));
|
||||
}
|
||||
}
|
@@ -1,272 +0,0 @@
|
||||
use core::{
|
||||
future::Future,
|
||||
marker::PhantomData,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use futures_core::ready;
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use super::{IntoService, IntoServiceFactory, Service, ServiceFactory};
|
||||
|
||||
/// Apply transform function to a service.
|
||||
///
|
||||
/// The In and Out type params refer to the request and response types for the wrapped service.
|
||||
pub fn apply_fn<I, S, F, Fut, Req, In, Res, Err>(
|
||||
service: I,
|
||||
wrap_fn: F,
|
||||
) -> Apply<S, F, Req, In, Res, Err>
|
||||
where
|
||||
I: IntoService<S, In>,
|
||||
S: Service<In, Error = Err>,
|
||||
F: FnMut(Req, &mut S) -> Fut,
|
||||
Fut: Future<Output = Result<Res, Err>>,
|
||||
{
|
||||
Apply::new(service.into_service(), wrap_fn)
|
||||
}
|
||||
|
||||
/// Service factory that produces `apply_fn` service.
|
||||
///
|
||||
/// The In and Out type params refer to the request and response types for the wrapped service.
|
||||
pub fn apply_fn_factory<I, SF, F, Fut, Req, In, Res, Err>(
|
||||
service: I,
|
||||
f: F,
|
||||
) -> ApplyFactory<SF, F, Req, In, Res, Err>
|
||||
where
|
||||
I: IntoServiceFactory<SF, In>,
|
||||
SF: ServiceFactory<In, Error = Err>,
|
||||
F: FnMut(Req, &mut SF::Service) -> Fut + Clone,
|
||||
Fut: Future<Output = Result<Res, Err>>,
|
||||
{
|
||||
ApplyFactory::new(service.into_factory(), f)
|
||||
}
|
||||
|
||||
/// `Apply` service combinator.
|
||||
///
|
||||
/// The In and Out type params refer to the request and response types for the wrapped service.
|
||||
pub struct Apply<S, F, Req, In, Res, Err>
|
||||
where
|
||||
S: Service<In, Error = Err>,
|
||||
{
|
||||
service: S,
|
||||
wrap_fn: F,
|
||||
_phantom: PhantomData<(Req, In, Res, Err)>,
|
||||
}
|
||||
|
||||
impl<S, F, Fut, Req, In, Res, Err> Apply<S, F, Req, In, Res, Err>
|
||||
where
|
||||
S: Service<In, Error = Err>,
|
||||
F: FnMut(Req, &mut S) -> Fut,
|
||||
Fut: Future<Output = Result<Res, Err>>,
|
||||
{
|
||||
/// Create new `Apply` combinator
|
||||
fn new(service: S, wrap_fn: F) -> Self {
|
||||
Self {
|
||||
service,
|
||||
wrap_fn,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, F, Fut, Req, In, Res, Err> Clone for Apply<S, F, Req, In, Res, Err>
|
||||
where
|
||||
S: Service<In, Error = Err> + Clone,
|
||||
F: FnMut(Req, &mut S) -> Fut + Clone,
|
||||
Fut: Future<Output = Result<Res, Err>>,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Apply {
|
||||
service: self.service.clone(),
|
||||
wrap_fn: self.wrap_fn.clone(),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, F, Fut, Req, In, Res, Err> Service<Req> for Apply<S, F, Req, In, Res, Err>
|
||||
where
|
||||
S: Service<In, Error = Err>,
|
||||
F: FnMut(Req, &mut S) -> Fut,
|
||||
Fut: Future<Output = Result<Res, Err>>,
|
||||
{
|
||||
type Response = Res;
|
||||
type Error = Err;
|
||||
type Future = Fut;
|
||||
|
||||
crate::forward_ready!(service);
|
||||
|
||||
fn call(&mut self, req: Req) -> Self::Future {
|
||||
(self.wrap_fn)(req, &mut self.service)
|
||||
}
|
||||
}
|
||||
|
||||
/// `ApplyFactory` service factory combinator.
|
||||
pub struct ApplyFactory<SF, F, Req, In, Res, Err> {
|
||||
factory: SF,
|
||||
wrap_fn: F,
|
||||
_phantom: PhantomData<(Req, In, Res, Err)>,
|
||||
}
|
||||
|
||||
impl<SF, F, Fut, Req, In, Res, Err> ApplyFactory<SF, F, Req, In, Res, Err>
|
||||
where
|
||||
SF: ServiceFactory<In, Error = Err>,
|
||||
F: FnMut(Req, &mut SF::Service) -> Fut + Clone,
|
||||
Fut: Future<Output = Result<Res, Err>>,
|
||||
{
|
||||
/// Create new `ApplyFactory` new service instance
|
||||
fn new(factory: SF, wrap_fn: F) -> Self {
|
||||
Self {
|
||||
factory,
|
||||
wrap_fn,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<SF, F, Fut, Req, In, Res, Err> Clone for ApplyFactory<SF, F, Req, In, Res, Err>
|
||||
where
|
||||
SF: ServiceFactory<In, Error = Err> + Clone,
|
||||
F: FnMut(Req, &mut SF::Service) -> Fut + Clone,
|
||||
Fut: Future<Output = Result<Res, Err>>,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
factory: self.factory.clone(),
|
||||
wrap_fn: self.wrap_fn.clone(),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<SF, F, Fut, Req, In, Res, Err> ServiceFactory<Req>
|
||||
for ApplyFactory<SF, F, Req, In, Res, Err>
|
||||
where
|
||||
SF: ServiceFactory<In, Error = Err>,
|
||||
F: FnMut(Req, &mut SF::Service) -> Fut + Clone,
|
||||
Fut: Future<Output = Result<Res, Err>>,
|
||||
{
|
||||
type Response = Res;
|
||||
type Error = Err;
|
||||
|
||||
type Config = SF::Config;
|
||||
type Service = Apply<SF::Service, F, Req, In, Res, Err>;
|
||||
type InitError = SF::InitError;
|
||||
type Future = ApplyServiceFactoryResponse<SF, F, Fut, Req, In, Res, Err>;
|
||||
|
||||
fn new_service(&self, cfg: SF::Config) -> Self::Future {
|
||||
let svc = self.factory.new_service(cfg);
|
||||
ApplyServiceFactoryResponse::new(svc, self.wrap_fn.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
pub struct ApplyServiceFactoryResponse<SF, F, Fut, Req, In, Res, Err>
|
||||
where
|
||||
SF: ServiceFactory<In, Error = Err>,
|
||||
F: FnMut(Req, &mut SF::Service) -> Fut,
|
||||
Fut: Future<Output = Result<Res, Err>>,
|
||||
{
|
||||
#[pin]
|
||||
fut: SF::Future,
|
||||
wrap_fn: Option<F>,
|
||||
_phantom: PhantomData<(Req, Res)>,
|
||||
}
|
||||
}
|
||||
|
||||
impl<SF, F, Fut, Req, In, Res, Err> ApplyServiceFactoryResponse<SF, F, Fut, Req, In, Res, Err>
|
||||
where
|
||||
SF: ServiceFactory<In, Error = Err>,
|
||||
F: FnMut(Req, &mut SF::Service) -> Fut,
|
||||
Fut: Future<Output = Result<Res, Err>>,
|
||||
{
|
||||
fn new(fut: SF::Future, wrap_fn: F) -> Self {
|
||||
Self {
|
||||
fut,
|
||||
wrap_fn: Some(wrap_fn),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<SF, F, Fut, Req, In, Res, Err> Future
|
||||
for ApplyServiceFactoryResponse<SF, F, Fut, Req, In, Res, Err>
|
||||
where
|
||||
SF: ServiceFactory<In, Error = Err>,
|
||||
F: FnMut(Req, &mut SF::Service) -> Fut,
|
||||
Fut: Future<Output = Result<Res, Err>>,
|
||||
{
|
||||
type Output = Result<Apply<SF::Service, F, Req, In, Res, Err>, SF::InitError>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
|
||||
let svc = ready!(this.fut.poll(cx))?;
|
||||
Poll::Ready(Ok(Apply::new(svc, this.wrap_fn.take().unwrap())))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use core::task::Poll;
|
||||
|
||||
use futures_util::future::lazy;
|
||||
|
||||
use super::*;
|
||||
use crate::{ok, pipeline, pipeline_factory, Ready, Service, ServiceFactory};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Srv;
|
||||
|
||||
impl Service<()> for Srv {
|
||||
type Response = ();
|
||||
type Error = ();
|
||||
type Future = Ready<Result<(), ()>>;
|
||||
|
||||
crate::always_ready!();
|
||||
|
||||
fn call(&mut self, _: ()) -> Self::Future {
|
||||
ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_call() {
|
||||
let mut srv = pipeline(apply_fn(Srv, |req: &'static str, srv| {
|
||||
let fut = srv.call(());
|
||||
async move {
|
||||
fut.await.unwrap();
|
||||
Ok((req, ()))
|
||||
}
|
||||
}));
|
||||
|
||||
assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Ready(Ok(())));
|
||||
|
||||
let res = srv.call("srv").await;
|
||||
assert!(res.is_ok());
|
||||
assert_eq!(res.unwrap(), ("srv", ()));
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_new_service() {
|
||||
let new_srv = pipeline_factory(apply_fn_factory(
|
||||
|| ok::<_, ()>(Srv),
|
||||
|req: &'static str, srv| {
|
||||
let fut = srv.call(());
|
||||
async move {
|
||||
fut.await.unwrap();
|
||||
Ok((req, ()))
|
||||
}
|
||||
},
|
||||
));
|
||||
|
||||
let mut srv = new_srv.new_service(()).await.unwrap();
|
||||
|
||||
assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Ready(Ok(())));
|
||||
|
||||
let res = srv.call("srv").await;
|
||||
assert!(res.is_ok());
|
||||
assert_eq!(res.unwrap(), ("srv", ()));
|
||||
}
|
||||
}
|
@@ -1,237 +0,0 @@
|
||||
use alloc::rc::Rc;
|
||||
use core::{
|
||||
cell::RefCell,
|
||||
future::Future,
|
||||
marker::PhantomData,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use crate::{Service, ServiceFactory};
|
||||
|
||||
/// Convert `Fn(Config, &mut Service1) -> Future<Service2>` fn to a service factory.
|
||||
pub fn apply_cfg<S1, Req, F, Cfg, Fut, S2, Err>(
|
||||
srv: S1,
|
||||
f: F,
|
||||
) -> impl ServiceFactory<
|
||||
Req,
|
||||
Config = Cfg,
|
||||
Response = S2::Response,
|
||||
Error = S2::Error,
|
||||
Service = S2,
|
||||
InitError = Err,
|
||||
Future = Fut,
|
||||
> + Clone
|
||||
where
|
||||
S1: Service<Req>,
|
||||
F: FnMut(Cfg, &mut S1) -> Fut,
|
||||
Fut: Future<Output = Result<S2, Err>>,
|
||||
S2: Service<Req>,
|
||||
{
|
||||
ApplyConfigService {
|
||||
srv: Rc::new(RefCell::new((srv, f))),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert `Fn(Config, &mut ServiceFactory1) -> Future<ServiceFactory2>` fn to a service factory.
|
||||
///
|
||||
/// Service1 get constructed from `T` factory.
|
||||
pub fn apply_cfg_factory<SF, Req, F, Cfg, Fut, S>(
|
||||
factory: SF,
|
||||
f: F,
|
||||
) -> impl ServiceFactory<
|
||||
Req,
|
||||
Config = Cfg,
|
||||
Response = S::Response,
|
||||
Error = S::Error,
|
||||
Service = S,
|
||||
InitError = SF::InitError,
|
||||
> + Clone
|
||||
where
|
||||
SF: ServiceFactory<Req, Config = ()>,
|
||||
F: FnMut(Cfg, &mut SF::Service) -> Fut,
|
||||
SF::InitError: From<SF::Error>,
|
||||
Fut: Future<Output = Result<S, SF::InitError>>,
|
||||
S: Service<Req>,
|
||||
{
|
||||
ApplyConfigServiceFactory {
|
||||
srv: Rc::new(RefCell::new((factory, f))),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert `Fn(Config, &mut Server) -> Future<Service>` fn to NewService\
|
||||
struct ApplyConfigService<S1, Req, F, Cfg, Fut, S2, Err>
|
||||
where
|
||||
S1: Service<Req>,
|
||||
F: FnMut(Cfg, &mut S1) -> Fut,
|
||||
Fut: Future<Output = Result<S2, Err>>,
|
||||
S2: Service<Req>,
|
||||
{
|
||||
srv: Rc<RefCell<(S1, F)>>,
|
||||
_phantom: PhantomData<(Cfg, Req, Fut, S2)>,
|
||||
}
|
||||
|
||||
impl<S1, Req, F, Cfg, Fut, S2, Err> Clone for ApplyConfigService<S1, Req, F, Cfg, Fut, S2, Err>
|
||||
where
|
||||
S1: Service<Req>,
|
||||
F: FnMut(Cfg, &mut S1) -> Fut,
|
||||
Fut: Future<Output = Result<S2, Err>>,
|
||||
S2: Service<Req>,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
ApplyConfigService {
|
||||
srv: self.srv.clone(),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S1, Req, F, Cfg, Fut, S2, Err> ServiceFactory<Req>
|
||||
for ApplyConfigService<S1, Req, F, Cfg, Fut, S2, Err>
|
||||
where
|
||||
S1: Service<Req>,
|
||||
F: FnMut(Cfg, &mut S1) -> Fut,
|
||||
Fut: Future<Output = Result<S2, Err>>,
|
||||
S2: Service<Req>,
|
||||
{
|
||||
type Config = Cfg;
|
||||
type Response = S2::Response;
|
||||
type Error = S2::Error;
|
||||
type Service = S2;
|
||||
|
||||
type InitError = Err;
|
||||
type Future = Fut;
|
||||
|
||||
fn new_service(&self, cfg: Cfg) -> Self::Future {
|
||||
let (t, f) = &mut *self.srv.borrow_mut();
|
||||
f(cfg, t)
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert `Fn(&Config) -> Future<Service>` fn to NewService
|
||||
struct ApplyConfigServiceFactory<SF, Req, F, Cfg, Fut, S>
|
||||
where
|
||||
SF: ServiceFactory<Req, Config = ()>,
|
||||
F: FnMut(Cfg, &mut SF::Service) -> Fut,
|
||||
Fut: Future<Output = Result<S, SF::InitError>>,
|
||||
S: Service<Req>,
|
||||
{
|
||||
srv: Rc<RefCell<(SF, F)>>,
|
||||
_phantom: PhantomData<(Cfg, Req, Fut, S)>,
|
||||
}
|
||||
|
||||
impl<SF, Req, F, Cfg, Fut, S> Clone for ApplyConfigServiceFactory<SF, Req, F, Cfg, Fut, S>
|
||||
where
|
||||
SF: ServiceFactory<Req, Config = ()>,
|
||||
F: FnMut(Cfg, &mut SF::Service) -> Fut,
|
||||
Fut: Future<Output = Result<S, SF::InitError>>,
|
||||
S: Service<Req>,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
srv: self.srv.clone(),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<SF, Req, F, Cfg, Fut, S> ServiceFactory<Req>
|
||||
for ApplyConfigServiceFactory<SF, Req, F, Cfg, Fut, S>
|
||||
where
|
||||
SF: ServiceFactory<Req, Config = ()>,
|
||||
SF::InitError: From<SF::Error>,
|
||||
F: FnMut(Cfg, &mut SF::Service) -> Fut,
|
||||
Fut: Future<Output = Result<S, SF::InitError>>,
|
||||
S: Service<Req>,
|
||||
{
|
||||
type Config = Cfg;
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type Service = S;
|
||||
|
||||
type InitError = SF::InitError;
|
||||
type Future = ApplyConfigServiceFactoryResponse<SF, Req, F, Cfg, Fut, S>;
|
||||
|
||||
fn new_service(&self, cfg: Cfg) -> Self::Future {
|
||||
ApplyConfigServiceFactoryResponse {
|
||||
cfg: Some(cfg),
|
||||
store: self.srv.clone(),
|
||||
state: State::A {
|
||||
fut: self.srv.borrow().0.new_service(()),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
struct ApplyConfigServiceFactoryResponse<SF, Req, F, Cfg, Fut, S>
|
||||
where
|
||||
SF: ServiceFactory<Req, Config = ()>,
|
||||
SF::InitError: From<SF::Error>,
|
||||
F: FnMut(Cfg, &mut SF::Service) -> Fut,
|
||||
Fut: Future<Output = Result<S, SF::InitError>>,
|
||||
S: Service<Req>,
|
||||
{
|
||||
cfg: Option<Cfg>,
|
||||
store: Rc<RefCell<(SF, F)>>,
|
||||
#[pin]
|
||||
state: State<SF, Fut, S, Req>,
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
#[project = StateProj]
|
||||
enum State<SF, Fut, S, Req>
|
||||
where
|
||||
SF: ServiceFactory<Req, Config = ()>,
|
||||
SF::InitError: From<SF::Error>,
|
||||
Fut: Future<Output = Result<S, SF::InitError>>,
|
||||
S: Service<Req>,
|
||||
{
|
||||
A { #[pin] fut: SF::Future },
|
||||
B { svc: SF::Service },
|
||||
C { #[pin] fut: Fut },
|
||||
}
|
||||
}
|
||||
|
||||
impl<SF, Req, F, Cfg, Fut, S> Future
|
||||
for ApplyConfigServiceFactoryResponse<SF, Req, F, Cfg, Fut, S>
|
||||
where
|
||||
SF: ServiceFactory<Req, Config = ()>,
|
||||
SF::InitError: From<SF::Error>,
|
||||
F: FnMut(Cfg, &mut SF::Service) -> Fut,
|
||||
Fut: Future<Output = Result<S, SF::InitError>>,
|
||||
S: Service<Req>,
|
||||
{
|
||||
type Output = Result<S, SF::InitError>;
|
||||
|
||||
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 } => match fut.poll(cx)? {
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(svc) => {
|
||||
this.state.set(State::B { svc });
|
||||
self.poll(cx)
|
||||
}
|
||||
},
|
||||
StateProj::B { svc } => match svc.poll_ready(cx)? {
|
||||
Poll::Ready(_) => {
|
||||
{
|
||||
let (_, f) = &mut *this.store.borrow_mut();
|
||||
let fut = f(this.cfg.take().unwrap(), svc);
|
||||
this.state.set(State::C { fut });
|
||||
}
|
||||
self.poll(cx)
|
||||
}
|
||||
Poll::Pending => Poll::Pending,
|
||||
},
|
||||
StateProj::C { fut } => fut.poll(cx),
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,141 +0,0 @@
|
||||
use alloc::boxed::Box;
|
||||
use core::{
|
||||
future::Future,
|
||||
marker::PhantomData,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use crate::{Service, ServiceFactory};
|
||||
|
||||
pub type BoxFuture<T> = Pin<Box<dyn Future<Output = T>>>;
|
||||
|
||||
pub type BoxService<Req, Res, Err> =
|
||||
Box<dyn Service<Req, Response = Res, Error = Err, Future = BoxFuture<Result<Res, Err>>>>;
|
||||
|
||||
pub struct BoxServiceFactory<Cfg, Req, Res, Err, InitErr>(Inner<Cfg, Req, Res, Err, InitErr>);
|
||||
|
||||
/// Create boxed service factory
|
||||
pub fn factory<SF, Req>(
|
||||
factory: SF,
|
||||
) -> BoxServiceFactory<SF::Config, Req, SF::Response, SF::Error, SF::InitError>
|
||||
where
|
||||
SF: ServiceFactory<Req> + 'static,
|
||||
Req: 'static,
|
||||
SF::Response: 'static,
|
||||
SF::Service: 'static,
|
||||
SF::Future: 'static,
|
||||
SF::Error: 'static,
|
||||
SF::InitError: 'static,
|
||||
{
|
||||
BoxServiceFactory(Box::new(FactoryWrapper {
|
||||
factory,
|
||||
_t: PhantomData,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Create boxed service
|
||||
pub fn service<S, Req>(service: S) -> BoxService<Req, S::Response, S::Error>
|
||||
where
|
||||
S: Service<Req> + 'static,
|
||||
Req: 'static,
|
||||
S::Future: 'static,
|
||||
{
|
||||
Box::new(ServiceWrapper(service, PhantomData))
|
||||
}
|
||||
|
||||
type Inner<C, Req, Res, Err, InitErr> = Box<
|
||||
dyn ServiceFactory<
|
||||
Req,
|
||||
Config = C,
|
||||
Response = Res,
|
||||
Error = Err,
|
||||
InitError = InitErr,
|
||||
Service = BoxService<Req, Res, Err>,
|
||||
Future = BoxFuture<Result<BoxService<Req, Res, Err>, InitErr>>,
|
||||
>,
|
||||
>;
|
||||
|
||||
impl<C, Req, Res, Err, InitErr> ServiceFactory<Req>
|
||||
for BoxServiceFactory<C, Req, Res, Err, InitErr>
|
||||
where
|
||||
Req: 'static,
|
||||
Res: 'static,
|
||||
Err: 'static,
|
||||
InitErr: 'static,
|
||||
{
|
||||
type Response = Res;
|
||||
type Error = Err;
|
||||
type InitError = InitErr;
|
||||
type Config = C;
|
||||
type Service = BoxService<Req, Res, Err>;
|
||||
|
||||
type Future = BoxFuture<Result<Self::Service, InitErr>>;
|
||||
|
||||
fn new_service(&self, cfg: C) -> Self::Future {
|
||||
self.0.new_service(cfg)
|
||||
}
|
||||
}
|
||||
|
||||
struct FactoryWrapper<SF, Req, Cfg> {
|
||||
factory: SF,
|
||||
_t: PhantomData<(Req, Cfg)>,
|
||||
}
|
||||
|
||||
impl<SF, Req, Cfg, Res, Err, InitErr> ServiceFactory<Req> for FactoryWrapper<SF, Req, Cfg>
|
||||
where
|
||||
Req: 'static,
|
||||
Res: 'static,
|
||||
Err: 'static,
|
||||
InitErr: 'static,
|
||||
SF: ServiceFactory<Req, Config = Cfg, Response = Res, Error = Err, InitError = InitErr>,
|
||||
SF::Future: 'static,
|
||||
SF::Service: 'static,
|
||||
<SF::Service as Service<Req>>::Future: 'static,
|
||||
{
|
||||
type Response = Res;
|
||||
type Error = Err;
|
||||
type InitError = InitErr;
|
||||
type Config = Cfg;
|
||||
type Service = BoxService<Req, Res, Err>;
|
||||
type Future = BoxFuture<Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, cfg: Cfg) -> Self::Future {
|
||||
let fut = self.factory.new_service(cfg);
|
||||
Box::pin(async {
|
||||
let res = fut.await;
|
||||
res.map(ServiceWrapper::boxed)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct ServiceWrapper<S: Service<Req>, Req>(S, PhantomData<Req>);
|
||||
|
||||
impl<S, Req> ServiceWrapper<S, Req>
|
||||
where
|
||||
S: Service<Req> + 'static,
|
||||
Req: 'static,
|
||||
S::Future: 'static,
|
||||
{
|
||||
fn boxed(service: S) -> BoxService<Req, S::Response, S::Error> {
|
||||
Box::new(ServiceWrapper(service, PhantomData))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, Req, Res, Err> Service<Req> for ServiceWrapper<S, Req>
|
||||
where
|
||||
S: Service<Req, Response = Res, Error = Err>,
|
||||
S::Future: 'static,
|
||||
{
|
||||
type Response = Res;
|
||||
type Error = Err;
|
||||
type Future = BoxFuture<Result<Res, Err>>;
|
||||
|
||||
fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.0.poll_ready(ctx)
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Req) -> Self::Future {
|
||||
Box::pin(self.0.call(req))
|
||||
}
|
||||
}
|
@@ -1,70 +0,0 @@
|
||||
use crate::{dev, Service, ServiceFactory};
|
||||
|
||||
pub trait ServiceExt<Req>: Service<Req> {
|
||||
/// Map this service's output to a different type, returning a new service
|
||||
/// of the resulting type.
|
||||
///
|
||||
/// This function is similar to the `Option::map` or `Iterator::map` where
|
||||
/// it will change the type of the underlying service.
|
||||
///
|
||||
/// Note that this function consumes the receiving service and returns a
|
||||
/// wrapped version of it, similar to the existing `map` methods in the
|
||||
/// standard library.
|
||||
fn map<F, R>(self, f: F) -> dev::Map<Self, F, Req, R>
|
||||
where
|
||||
Self: Sized,
|
||||
F: FnMut(Self::Response) -> R,
|
||||
{
|
||||
dev::Map::new(self, f)
|
||||
}
|
||||
|
||||
/// Map this service's error to a different error, returning a new service.
|
||||
///
|
||||
/// This function is similar to the `Result::map_err` where it will change
|
||||
/// the error type of the underlying service. For example, this can be useful to
|
||||
/// ensure that services have the same error type.
|
||||
///
|
||||
/// Note that this function consumes the receiving service and returns a
|
||||
/// wrapped version of it.
|
||||
fn map_err<F, E>(self, f: F) -> dev::MapErr<Self, Req, F, E>
|
||||
where
|
||||
Self: Sized,
|
||||
F: Fn(Self::Error) -> E,
|
||||
{
|
||||
dev::MapErr::new(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, Req> ServiceExt<Req> for S where S: Service<Req> {}
|
||||
|
||||
pub trait ServiceFactoryExt<Req>: ServiceFactory<Req> {
|
||||
/// Map this service's output to a different type, returning a new service
|
||||
/// of the resulting type.
|
||||
fn map<F, R>(self, f: F) -> crate::map::MapServiceFactory<Self, F, Req, R>
|
||||
where
|
||||
Self: Sized,
|
||||
F: FnMut(Self::Response) -> R + Clone,
|
||||
{
|
||||
crate::map::MapServiceFactory::new(self, f)
|
||||
}
|
||||
|
||||
/// Map this service's error to a different error, returning a new service.
|
||||
fn map_err<F, E>(self, f: F) -> crate::map_err::MapErrServiceFactory<Self, Req, F, E>
|
||||
where
|
||||
Self: Sized,
|
||||
F: Fn(Self::Error) -> E + Clone,
|
||||
{
|
||||
crate::map_err::MapErrServiceFactory::new(self, f)
|
||||
}
|
||||
|
||||
/// Map this factory's init error to a different error, returning a new service.
|
||||
fn map_init_err<F, E>(self, f: F) -> crate::map_init_err::MapInitErr<Self, F, Req, E>
|
||||
where
|
||||
Self: Sized,
|
||||
F: Fn(Self::InitError) -> E + Clone,
|
||||
{
|
||||
crate::map_init_err::MapInitErr::new(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, Req> ServiceFactoryExt<Req> for S where S: ServiceFactory<Req> {}
|
@@ -1,396 +0,0 @@
|
||||
use core::{future::Future, marker::PhantomData, task::Poll};
|
||||
|
||||
use crate::{ok, IntoService, IntoServiceFactory, Ready, Service, ServiceFactory};
|
||||
|
||||
/// Create `ServiceFactory` for function that can act as a `Service`
|
||||
pub fn fn_service<F, Fut, Req, Res, Err, Cfg>(
|
||||
f: F,
|
||||
) -> FnServiceFactory<F, Fut, Req, Res, Err, Cfg>
|
||||
where
|
||||
F: FnMut(Req) -> Fut + Clone,
|
||||
Fut: Future<Output = Result<Res, Err>>,
|
||||
{
|
||||
FnServiceFactory::new(f)
|
||||
}
|
||||
|
||||
/// Create `ServiceFactory` for function that can produce services
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use std::io;
|
||||
/// use actix_service::{fn_factory, fn_service, Service, ServiceFactory};
|
||||
/// use futures_util::future::ok;
|
||||
///
|
||||
/// /// Service that divides two usize values.
|
||||
/// async fn div((x, y): (usize, usize)) -> Result<usize, io::Error> {
|
||||
/// if y == 0 {
|
||||
/// Err(io::Error::new(io::ErrorKind::Other, "divide by zero"))
|
||||
/// } else {
|
||||
/// Ok(x / y)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// #[actix_rt::main]
|
||||
/// async fn main() -> io::Result<()> {
|
||||
/// // Create service factory that produces `div` services
|
||||
/// let factory = fn_factory(|| {
|
||||
/// ok::<_, io::Error>(fn_service(div))
|
||||
/// });
|
||||
///
|
||||
/// // construct new service
|
||||
/// let mut srv = factory.new_service(()).await?;
|
||||
///
|
||||
/// // now we can use `div` service
|
||||
/// let result = srv.call((10, 20)).await?;
|
||||
///
|
||||
/// println!("10 / 20 = {}", result);
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub fn fn_factory<F, Cfg, Srv, Req, Fut, Err>(
|
||||
f: F,
|
||||
) -> FnServiceNoConfig<F, Cfg, Srv, Req, Fut, Err>
|
||||
where
|
||||
Srv: Service<Req>,
|
||||
F: Fn() -> Fut,
|
||||
Fut: Future<Output = Result<Srv, Err>>,
|
||||
{
|
||||
FnServiceNoConfig::new(f)
|
||||
}
|
||||
|
||||
/// Create `ServiceFactory` for function that accepts config argument and can produce services
|
||||
///
|
||||
/// Any function that has following form `Fn(Config) -> Future<Output = Service>` could
|
||||
/// act as a `ServiceFactory`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use std::io;
|
||||
/// use actix_service::{fn_factory_with_config, fn_service, Service, ServiceFactory};
|
||||
/// use futures_util::future::ok;
|
||||
///
|
||||
/// #[actix_rt::main]
|
||||
/// async fn main() -> io::Result<()> {
|
||||
/// // Create service factory. factory uses config argument for
|
||||
/// // services it generates.
|
||||
/// let factory = fn_factory_with_config(|y: usize| {
|
||||
/// ok::<_, io::Error>(fn_service(move |x: usize| ok::<_, io::Error>(x * y)))
|
||||
/// });
|
||||
///
|
||||
/// // construct new service with config argument
|
||||
/// let mut srv = factory.new_service(10).await?;
|
||||
///
|
||||
/// let result = srv.call(10).await?;
|
||||
/// assert_eq!(result, 100);
|
||||
///
|
||||
/// println!("10 * 10 = {}", result);
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub fn fn_factory_with_config<F, Fut, Cfg, Srv, Req, Err>(
|
||||
f: F,
|
||||
) -> FnServiceConfig<F, Fut, Cfg, Srv, Req, Err>
|
||||
where
|
||||
F: Fn(Cfg) -> Fut,
|
||||
Fut: Future<Output = Result<Srv, Err>>,
|
||||
Srv: Service<Req>,
|
||||
{
|
||||
FnServiceConfig::new(f)
|
||||
}
|
||||
|
||||
pub struct FnService<F, Fut, Req, Res, Err>
|
||||
where
|
||||
F: FnMut(Req) -> Fut,
|
||||
Fut: Future<Output = Result<Res, Err>>,
|
||||
{
|
||||
f: F,
|
||||
_t: PhantomData<Req>,
|
||||
}
|
||||
|
||||
impl<F, Fut, Req, Res, Err> FnService<F, Fut, Req, Res, Err>
|
||||
where
|
||||
F: FnMut(Req) -> Fut,
|
||||
Fut: Future<Output = Result<Res, Err>>,
|
||||
{
|
||||
pub(crate) fn new(f: F) -> Self {
|
||||
Self { f, _t: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, Fut, Req, Res, Err> Clone for FnService<F, Fut, Req, Res, Err>
|
||||
where
|
||||
F: FnMut(Req) -> Fut + Clone,
|
||||
Fut: Future<Output = Result<Res, Err>>,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self::new(self.f.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, Fut, Req, Res, Err> Service<Req> for FnService<F, Fut, Req, Res, Err>
|
||||
where
|
||||
F: FnMut(Req) -> Fut,
|
||||
Fut: Future<Output = Result<Res, Err>>,
|
||||
{
|
||||
type Response = Res;
|
||||
type Error = Err;
|
||||
type Future = Fut;
|
||||
|
||||
crate::always_ready!();
|
||||
|
||||
fn call(&mut self, req: Req) -> Self::Future {
|
||||
(self.f)(req)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, Fut, Req, Res, Err> IntoService<FnService<F, Fut, Req, Res, Err>, Req> for F
|
||||
where
|
||||
F: FnMut(Req) -> Fut,
|
||||
Fut: Future<Output = Result<Res, Err>>,
|
||||
{
|
||||
fn into_service(self) -> FnService<F, Fut, Req, Res, Err> {
|
||||
FnService::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FnServiceFactory<F, Fut, Req, Res, Err, Cfg>
|
||||
where
|
||||
F: FnMut(Req) -> Fut,
|
||||
Fut: Future<Output = Result<Res, Err>>,
|
||||
{
|
||||
f: F,
|
||||
_t: PhantomData<(Req, Cfg)>,
|
||||
}
|
||||
|
||||
impl<F, Fut, Req, Res, Err, Cfg> FnServiceFactory<F, Fut, Req, Res, Err, Cfg>
|
||||
where
|
||||
F: FnMut(Req) -> Fut + Clone,
|
||||
Fut: Future<Output = Result<Res, Err>>,
|
||||
{
|
||||
fn new(f: F) -> Self {
|
||||
FnServiceFactory { f, _t: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, Fut, Req, Res, Err, Cfg> Clone for FnServiceFactory<F, Fut, Req, Res, Err, Cfg>
|
||||
where
|
||||
F: FnMut(Req) -> Fut + Clone,
|
||||
Fut: Future<Output = Result<Res, Err>>,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self::new(self.f.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, Fut, Req, Res, Err> Service<Req> for FnServiceFactory<F, Fut, Req, Res, Err, ()>
|
||||
where
|
||||
F: FnMut(Req) -> Fut + Clone,
|
||||
Fut: Future<Output = Result<Res, Err>>,
|
||||
{
|
||||
type Response = Res;
|
||||
type Error = Err;
|
||||
type Future = Fut;
|
||||
|
||||
crate::always_ready!();
|
||||
|
||||
fn call(&mut self, req: Req) -> Self::Future {
|
||||
(self.f)(req)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, Fut, Req, Res, Err, Cfg> ServiceFactory<Req>
|
||||
for FnServiceFactory<F, Fut, Req, Res, Err, Cfg>
|
||||
where
|
||||
F: FnMut(Req) -> Fut + Clone,
|
||||
Fut: Future<Output = Result<Res, Err>>,
|
||||
{
|
||||
type Response = Res;
|
||||
type Error = Err;
|
||||
|
||||
type Config = Cfg;
|
||||
type Service = FnService<F, Fut, Req, Res, Err>;
|
||||
type InitError = ();
|
||||
type Future = Ready<Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: Cfg) -> Self::Future {
|
||||
ok(FnService::new(self.f.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, Fut, Req, Res, Err, Cfg>
|
||||
IntoServiceFactory<FnServiceFactory<F, Fut, Req, Res, Err, Cfg>, Req> for F
|
||||
where
|
||||
F: Fn(Req) -> Fut + Clone,
|
||||
Fut: Future<Output = Result<Res, Err>>,
|
||||
{
|
||||
fn into_factory(self) -> FnServiceFactory<F, Fut, Req, Res, Err, Cfg> {
|
||||
FnServiceFactory::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert `Fn(&Config) -> Future<Service>` fn to NewService
|
||||
pub struct FnServiceConfig<F, Fut, Cfg, Srv, Req, Err>
|
||||
where
|
||||
F: Fn(Cfg) -> Fut,
|
||||
Fut: Future<Output = Result<Srv, Err>>,
|
||||
Srv: Service<Req>,
|
||||
{
|
||||
f: F,
|
||||
_t: PhantomData<(Fut, Cfg, Req, Srv, Err)>,
|
||||
}
|
||||
|
||||
impl<F, Fut, Cfg, Srv, Req, Err> FnServiceConfig<F, Fut, Cfg, Srv, Req, Err>
|
||||
where
|
||||
F: Fn(Cfg) -> Fut,
|
||||
Fut: Future<Output = Result<Srv, Err>>,
|
||||
Srv: Service<Req>,
|
||||
{
|
||||
fn new(f: F) -> Self {
|
||||
FnServiceConfig { f, _t: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, Fut, Cfg, Srv, Req, Err> Clone for FnServiceConfig<F, Fut, Cfg, Srv, Req, Err>
|
||||
where
|
||||
F: Fn(Cfg) -> Fut + Clone,
|
||||
Fut: Future<Output = Result<Srv, Err>>,
|
||||
Srv: Service<Req>,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
FnServiceConfig {
|
||||
f: self.f.clone(),
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, Fut, Cfg, Srv, Req, Err> ServiceFactory<Req>
|
||||
for FnServiceConfig<F, Fut, Cfg, Srv, Req, Err>
|
||||
where
|
||||
F: Fn(Cfg) -> Fut,
|
||||
Fut: Future<Output = Result<Srv, Err>>,
|
||||
Srv: Service<Req>,
|
||||
{
|
||||
type Response = Srv::Response;
|
||||
type Error = Srv::Error;
|
||||
|
||||
type Config = Cfg;
|
||||
type Service = Srv;
|
||||
type InitError = Err;
|
||||
type Future = Fut;
|
||||
|
||||
fn new_service(&self, cfg: Cfg) -> Self::Future {
|
||||
(self.f)(cfg)
|
||||
}
|
||||
}
|
||||
|
||||
/// Converter for `Fn() -> Future<Service>` fn
|
||||
pub struct FnServiceNoConfig<F, Cfg, Srv, Req, Fut, Err>
|
||||
where
|
||||
F: Fn() -> Fut,
|
||||
Srv: Service<Req>,
|
||||
Fut: Future<Output = Result<Srv, Err>>,
|
||||
{
|
||||
f: F,
|
||||
_t: PhantomData<(Cfg, Req)>,
|
||||
}
|
||||
|
||||
impl<F, Cfg, Srv, Req, Fut, Err> FnServiceNoConfig<F, Cfg, Srv, Req, Fut, Err>
|
||||
where
|
||||
F: Fn() -> Fut,
|
||||
Fut: Future<Output = Result<Srv, Err>>,
|
||||
Srv: Service<Req>,
|
||||
{
|
||||
fn new(f: F) -> Self {
|
||||
Self { f, _t: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, Cfg, Srv, Req, Fut, Err> ServiceFactory<Req>
|
||||
for FnServiceNoConfig<F, Cfg, Srv, Req, Fut, Err>
|
||||
where
|
||||
F: Fn() -> Fut,
|
||||
Fut: Future<Output = Result<Srv, Err>>,
|
||||
Srv: Service<Req>,
|
||||
{
|
||||
type Response = Srv::Response;
|
||||
type Error = Srv::Error;
|
||||
type Service = Srv;
|
||||
type Config = Cfg;
|
||||
type InitError = Err;
|
||||
type Future = Fut;
|
||||
|
||||
fn new_service(&self, _: Cfg) -> Self::Future {
|
||||
(self.f)()
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, Cfg, Srv, Req, Fut, Err> Clone for FnServiceNoConfig<F, Cfg, Srv, Req, Fut, Err>
|
||||
where
|
||||
F: Fn() -> Fut + Clone,
|
||||
Fut: Future<Output = Result<Srv, Err>>,
|
||||
Srv: Service<Req>,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self::new(self.f.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, Cfg, Srv, Req, Fut, Err>
|
||||
IntoServiceFactory<FnServiceNoConfig<F, Cfg, Srv, Req, Fut, Err>, Req> for F
|
||||
where
|
||||
F: Fn() -> Fut,
|
||||
Fut: Future<Output = Result<Srv, Err>>,
|
||||
Srv: Service<Req>,
|
||||
{
|
||||
fn into_factory(self) -> FnServiceNoConfig<F, Cfg, Srv, Req, Fut, Err> {
|
||||
FnServiceNoConfig::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use core::task::Poll;
|
||||
|
||||
use futures_util::future::lazy;
|
||||
|
||||
use super::*;
|
||||
use crate::{ok, Service, ServiceFactory};
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_fn_service() {
|
||||
let new_srv = fn_service(|()| ok::<_, ()>("srv"));
|
||||
|
||||
let mut srv = new_srv.new_service(()).await.unwrap();
|
||||
let res = srv.call(()).await;
|
||||
assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Ready(Ok(())));
|
||||
assert!(res.is_ok());
|
||||
assert_eq!(res.unwrap(), "srv");
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_fn_service_service() {
|
||||
let mut srv = fn_service(|()| ok::<_, ()>("srv"));
|
||||
|
||||
let res = srv.call(()).await;
|
||||
assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Ready(Ok(())));
|
||||
assert!(res.is_ok());
|
||||
assert_eq!(res.unwrap(), "srv");
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_fn_service_with_config() {
|
||||
let new_srv = fn_factory_with_config(|cfg: usize| {
|
||||
ok::<_, ()>(fn_service(move |()| ok::<_, ()>(("srv", cfg))))
|
||||
});
|
||||
|
||||
let mut srv = new_srv.new_service(1).await.unwrap();
|
||||
let res = srv.call(()).await;
|
||||
assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Ready(Ok(())));
|
||||
assert!(res.is_ok());
|
||||
assert_eq!(res.unwrap(), ("srv", 1));
|
||||
}
|
||||
}
|
@@ -1,333 +0,0 @@
|
||||
//! See [`Service`] docs for information on this crate's foundational trait.
|
||||
|
||||
#![no_std]
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![allow(clippy::type_complexity)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::{boxed::Box, rc::Rc, sync::Arc};
|
||||
use core::{
|
||||
cell::RefCell,
|
||||
future::Future,
|
||||
task::{self, Context, Poll},
|
||||
};
|
||||
|
||||
mod and_then;
|
||||
mod apply;
|
||||
mod apply_cfg;
|
||||
pub mod boxed;
|
||||
mod ext;
|
||||
mod fn_service;
|
||||
mod map;
|
||||
mod map_config;
|
||||
mod map_err;
|
||||
mod map_init_err;
|
||||
mod pipeline;
|
||||
mod ready;
|
||||
mod then;
|
||||
mod transform;
|
||||
mod transform_err;
|
||||
|
||||
pub use self::apply::{apply_fn, apply_fn_factory};
|
||||
pub use self::apply_cfg::{apply_cfg, apply_cfg_factory};
|
||||
pub use self::ext::{ServiceExt, ServiceFactoryExt};
|
||||
pub use self::fn_service::{fn_factory, fn_factory_with_config, fn_service};
|
||||
pub use self::map_config::{map_config, unit_config};
|
||||
pub use self::pipeline::{pipeline, pipeline_factory, Pipeline, PipelineFactory};
|
||||
pub use self::transform::{apply, Transform};
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use self::ready::{err, ok, ready, Ready};
|
||||
|
||||
/// An asynchronous operation from `Request` to a `Response`.
|
||||
///
|
||||
/// The `Service` trait models a request/response interaction, receiving requests and returning
|
||||
/// replies. You can think about a service as a function with one argument that returns some result
|
||||
/// asynchronously. Conceptually, the operation looks like this:
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// async fn(Request) -> Result<Response, Err>
|
||||
/// ```
|
||||
///
|
||||
/// The `Service` trait just generalizes this form where each parameter is described as an
|
||||
/// associated type on the trait. Services can also have mutable state that influence computation.
|
||||
///
|
||||
/// `Service` provides a symmetric and uniform API; the same abstractions can be used to represent
|
||||
/// both clients and servers. Services describe only _transformation_ operations which encourage
|
||||
/// simple API surfaces. This leads to simpler design of each service, improves test-ability and
|
||||
/// makes composition easier.
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// struct MyService;
|
||||
///
|
||||
/// impl Service for MyService {
|
||||
/// type Request = u8;
|
||||
/// type Response = u64;
|
||||
/// type Error = MyError;
|
||||
/// type Future = Pin<Box<Future<Output=Result<Self::Response, Self::Error>>>>;
|
||||
///
|
||||
/// fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { ... }
|
||||
///
|
||||
/// fn call(&mut self, req: Self::Request) -> Self::Future { ... }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Sometimes it is not necessary to implement the Service trait. For example, the above service
|
||||
/// could be rewritten as a simple function and passed to [fn_service](fn_service()).
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// async fn my_service(req: u8) -> Result<u64, MyError>;
|
||||
/// ```
|
||||
pub trait Service<Req> {
|
||||
/// Responses given by the service.
|
||||
type Response;
|
||||
|
||||
/// Errors produced by the service when polling readiness or executing call.
|
||||
type Error;
|
||||
|
||||
/// The future response value.
|
||||
type Future: Future<Output = Result<Self::Response, Self::Error>>;
|
||||
|
||||
/// Returns `Ready` when the service is able to process requests.
|
||||
///
|
||||
/// If the service is at capacity, then `Pending` is returned and the task
|
||||
/// is notified when the service becomes ready again. This function is
|
||||
/// expected to be called while on a task.
|
||||
///
|
||||
/// This is a **best effort** implementation. False positives are permitted.
|
||||
/// It is permitted for the service to return `Ready` from a `poll_ready`
|
||||
/// call and the next invocation of `call` results in an error.
|
||||
///
|
||||
/// # Notes
|
||||
/// 1. `.poll_ready()` might be called on different task from actual service call.
|
||||
/// 1. In case of chained services, `.poll_ready()` get called for all services at once.
|
||||
fn poll_ready(&mut self, ctx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>>;
|
||||
|
||||
/// Process the request and return the response asynchronously.
|
||||
///
|
||||
/// This function is expected to be callable off task. As such,
|
||||
/// implementations should take care to not call `poll_ready`. If the
|
||||
/// service is at capacity and the request is unable to be handled, the
|
||||
/// returned `Future` should resolve to an error.
|
||||
///
|
||||
/// Calling `call` without calling `poll_ready` is permitted. The
|
||||
/// implementation must be resilient to this fact.
|
||||
fn call(&mut self, req: Req) -> Self::Future;
|
||||
}
|
||||
|
||||
/// Factory for creating `Service`s.
|
||||
///
|
||||
/// Acts as a service factory. This is useful for cases where new `Service`s
|
||||
/// must be produced. One case is a TCP server listener. The listener
|
||||
/// accepts new TCP streams, obtains a new `Service` using the
|
||||
/// `ServiceFactory` trait, and uses the new `Service` to process inbound
|
||||
/// requests on that new TCP stream.
|
||||
///
|
||||
/// `Config` is a service factory configuration type.
|
||||
pub trait ServiceFactory<Req> {
|
||||
/// Responses given by the created services.
|
||||
type Response;
|
||||
|
||||
/// Errors produced by the created services.
|
||||
type Error;
|
||||
|
||||
/// Service factory configuration.
|
||||
type Config;
|
||||
|
||||
/// The kind of `Service` created by this factory.
|
||||
type Service: Service<Req, Response = Self::Response, Error = Self::Error>;
|
||||
|
||||
/// Errors potentially raised while building a service.
|
||||
type InitError;
|
||||
|
||||
/// The future of the `Service` instance.
|
||||
type Future: Future<Output = Result<Self::Service, Self::InitError>>;
|
||||
|
||||
/// Create and return a new service asynchronously.
|
||||
fn new_service(&self, cfg: Self::Config) -> Self::Future;
|
||||
}
|
||||
|
||||
impl<'a, S, Req> Service<Req> for &'a mut S
|
||||
where
|
||||
S: Service<Req> + 'a,
|
||||
{
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type Future = S::Future;
|
||||
|
||||
fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
(**self).poll_ready(ctx)
|
||||
}
|
||||
|
||||
fn call(&mut self, request: Req) -> S::Future {
|
||||
(**self).call(request)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, Req> Service<Req> for Box<S>
|
||||
where
|
||||
S: Service<Req> + ?Sized,
|
||||
{
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type Future = S::Future;
|
||||
|
||||
fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), S::Error>> {
|
||||
(**self).poll_ready(ctx)
|
||||
}
|
||||
|
||||
fn call(&mut self, request: Req) -> S::Future {
|
||||
(**self).call(request)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, Req> Service<Req> for RefCell<S>
|
||||
where
|
||||
S: Service<Req>,
|
||||
{
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type Future = S::Future;
|
||||
|
||||
fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.borrow_mut().poll_ready(ctx)
|
||||
}
|
||||
|
||||
fn call(&mut self, request: Req) -> S::Future {
|
||||
self.borrow_mut().call(request)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, Req> Service<Req> for Rc<RefCell<S>>
|
||||
where
|
||||
S: Service<Req>,
|
||||
{
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type Future = S::Future;
|
||||
|
||||
fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.borrow_mut().poll_ready(ctx)
|
||||
}
|
||||
|
||||
fn call(&mut self, request: Req) -> S::Future {
|
||||
(&mut (**self).borrow_mut()).call(request)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, Req> ServiceFactory<Req> for Rc<S>
|
||||
where
|
||||
S: ServiceFactory<Req>,
|
||||
{
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type Config = S::Config;
|
||||
type Service = S::Service;
|
||||
type InitError = S::InitError;
|
||||
type Future = S::Future;
|
||||
|
||||
fn new_service(&self, cfg: S::Config) -> S::Future {
|
||||
self.as_ref().new_service(cfg)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, Req> ServiceFactory<Req> for Arc<S>
|
||||
where
|
||||
S: ServiceFactory<Req>,
|
||||
{
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type Config = S::Config;
|
||||
type Service = S::Service;
|
||||
type InitError = S::InitError;
|
||||
type Future = S::Future;
|
||||
|
||||
fn new_service(&self, cfg: S::Config) -> S::Future {
|
||||
self.as_ref().new_service(cfg)
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for types that can be converted to a `Service`
|
||||
pub trait IntoService<S, Req>
|
||||
where
|
||||
S: Service<Req>,
|
||||
{
|
||||
/// Convert to a `Service`
|
||||
fn into_service(self) -> S;
|
||||
}
|
||||
|
||||
/// Trait for types that can be converted to a `ServiceFactory`
|
||||
pub trait IntoServiceFactory<SF, Req>
|
||||
where
|
||||
SF: ServiceFactory<Req>,
|
||||
{
|
||||
/// Convert `Self` to a `ServiceFactory`
|
||||
fn into_factory(self) -> SF;
|
||||
}
|
||||
|
||||
impl<S, Req> IntoService<S, Req> for S
|
||||
where
|
||||
S: Service<Req>,
|
||||
{
|
||||
fn into_service(self) -> S {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<SF, Req> IntoServiceFactory<SF, Req> for SF
|
||||
where
|
||||
SF: ServiceFactory<Req>,
|
||||
{
|
||||
fn into_factory(self) -> SF {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert object of type `U` to a service `S`
|
||||
pub fn into_service<I, S, Req>(tp: I) -> S
|
||||
where
|
||||
I: IntoService<S, Req>,
|
||||
S: Service<Req>,
|
||||
{
|
||||
tp.into_service()
|
||||
}
|
||||
|
||||
pub mod dev {
|
||||
pub use crate::apply::{Apply, ApplyFactory};
|
||||
pub use crate::fn_service::{
|
||||
FnService, FnServiceConfig, FnServiceFactory, FnServiceNoConfig,
|
||||
};
|
||||
pub use crate::map::{Map, MapServiceFactory};
|
||||
pub use crate::map_config::{MapConfig, UnitConfig};
|
||||
pub use crate::map_err::{MapErr, MapErrServiceFactory};
|
||||
pub use crate::map_init_err::MapInitErr;
|
||||
pub use crate::transform::ApplyTransform;
|
||||
pub use crate::transform_err::TransformMapInitErr;
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! always_ready {
|
||||
() => {
|
||||
fn poll_ready(
|
||||
&mut self,
|
||||
_: &mut ::core::task::Context<'_>,
|
||||
) -> ::core::task::Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! forward_ready {
|
||||
($field:ident) => {
|
||||
fn poll_ready(
|
||||
&mut self,
|
||||
cx: &mut ::core::task::Context<'_>,
|
||||
) -> ::core::task::Poll<Result<(), Self::Error>> {
|
||||
self.$field.poll_ready(cx)
|
||||
}
|
||||
};
|
||||
}
|
@@ -1,246 +0,0 @@
|
||||
use core::{
|
||||
future::Future,
|
||||
marker::PhantomData,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use super::{Service, ServiceFactory};
|
||||
|
||||
/// Service for the `map` combinator, changing the type of a service's response.
|
||||
///
|
||||
/// This is created by the `ServiceExt::map` method.
|
||||
pub struct Map<A, F, Req, Res> {
|
||||
service: A,
|
||||
f: F,
|
||||
_t: PhantomData<(Req, Res)>,
|
||||
}
|
||||
|
||||
impl<A, F, Req, Res> Map<A, F, Req, Res> {
|
||||
/// Create new `Map` combinator
|
||||
pub(crate) fn new(service: A, f: F) -> Self
|
||||
where
|
||||
A: Service<Req>,
|
||||
F: FnMut(A::Response) -> Res,
|
||||
{
|
||||
Self {
|
||||
service,
|
||||
f,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, F, Req, Res> Clone for Map<A, F, Req, Res>
|
||||
where
|
||||
A: Clone,
|
||||
F: Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Map {
|
||||
service: self.service.clone(),
|
||||
f: self.f.clone(),
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, F, Req, Res> Service<Req> for Map<A, F, Req, Res>
|
||||
where
|
||||
A: Service<Req>,
|
||||
F: FnMut(A::Response) -> Res + Clone,
|
||||
{
|
||||
type Response = Res;
|
||||
type Error = A::Error;
|
||||
type Future = MapFuture<A, F, Req, Res>;
|
||||
|
||||
crate::forward_ready!(service);
|
||||
|
||||
fn call(&mut self, req: Req) -> Self::Future {
|
||||
MapFuture::new(self.service.call(req), self.f.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
pub struct MapFuture<A, F, Req, Res>
|
||||
where
|
||||
A: Service<Req>,
|
||||
F: FnMut(A::Response) -> Res,
|
||||
{
|
||||
f: F,
|
||||
#[pin]
|
||||
fut: A::Future,
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, F, Req, Res> MapFuture<A, F, Req, Res>
|
||||
where
|
||||
A: Service<Req>,
|
||||
F: FnMut(A::Response) -> Res,
|
||||
{
|
||||
fn new(fut: A::Future, f: F) -> Self {
|
||||
MapFuture { f, fut }
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, F, Req, Res> Future for MapFuture<A, F, Req, Res>
|
||||
where
|
||||
A: Service<Req>,
|
||||
F: FnMut(A::Response) -> Res,
|
||||
{
|
||||
type Output = Result<Res, A::Error>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
|
||||
match this.fut.poll(cx) {
|
||||
Poll::Ready(Ok(resp)) => Poll::Ready(Ok((this.f)(resp))),
|
||||
Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
|
||||
Poll::Pending => Poll::Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `MapNewService` new service combinator
|
||||
pub struct MapServiceFactory<A, F, Req, Res> {
|
||||
a: A,
|
||||
f: F,
|
||||
r: PhantomData<(Res, Req)>,
|
||||
}
|
||||
|
||||
impl<A, F, Req, Res> MapServiceFactory<A, F, Req, Res> {
|
||||
/// Create new `Map` new service instance
|
||||
pub(crate) fn new(a: A, f: F) -> Self
|
||||
where
|
||||
A: ServiceFactory<Req>,
|
||||
F: FnMut(A::Response) -> Res,
|
||||
{
|
||||
Self {
|
||||
a,
|
||||
f,
|
||||
r: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, F, Req, Res> Clone for MapServiceFactory<A, F, Req, Res>
|
||||
where
|
||||
A: Clone,
|
||||
F: Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
a: self.a.clone(),
|
||||
f: self.f.clone(),
|
||||
r: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, F, Req, Res> ServiceFactory<Req> for MapServiceFactory<A, F, Req, Res>
|
||||
where
|
||||
A: ServiceFactory<Req>,
|
||||
F: FnMut(A::Response) -> Res + Clone,
|
||||
{
|
||||
type Response = Res;
|
||||
type Error = A::Error;
|
||||
|
||||
type Config = A::Config;
|
||||
type Service = Map<A::Service, F, Req, Res>;
|
||||
type InitError = A::InitError;
|
||||
type Future = MapServiceFuture<A, F, Req, Res>;
|
||||
|
||||
fn new_service(&self, cfg: A::Config) -> Self::Future {
|
||||
MapServiceFuture::new(self.a.new_service(cfg), self.f.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
pub struct MapServiceFuture<A, F, Req, Res>
|
||||
where
|
||||
A: ServiceFactory<Req>,
|
||||
F: FnMut(A::Response) -> Res,
|
||||
{
|
||||
#[pin]
|
||||
fut: A::Future,
|
||||
f: Option<F>,
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, F, Req, Res> MapServiceFuture<A, F, Req, Res>
|
||||
where
|
||||
A: ServiceFactory<Req>,
|
||||
F: FnMut(A::Response) -> Res,
|
||||
{
|
||||
fn new(fut: A::Future, f: F) -> Self {
|
||||
MapServiceFuture { f: Some(f), fut }
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, F, Req, Res> Future for MapServiceFuture<A, F, Req, Res>
|
||||
where
|
||||
A: ServiceFactory<Req>,
|
||||
F: FnMut(A::Response) -> Res,
|
||||
{
|
||||
type Output = Result<Map<A::Service, F, Req, Res>, A::InitError>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
|
||||
if let Poll::Ready(svc) = this.fut.poll(cx)? {
|
||||
Poll::Ready(Ok(Map::new(svc, this.f.take().unwrap())))
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use futures_util::future::lazy;
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
ok, IntoServiceFactory, Ready, Service, ServiceExt, ServiceFactory, ServiceFactoryExt,
|
||||
};
|
||||
|
||||
struct Srv;
|
||||
|
||||
impl Service<()> for Srv {
|
||||
type Response = ();
|
||||
type Error = ();
|
||||
type Future = Ready<Result<(), ()>>;
|
||||
|
||||
crate::always_ready!();
|
||||
|
||||
fn call(&mut self, _: ()) -> Self::Future {
|
||||
ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_poll_ready() {
|
||||
let mut srv = Srv.map(|_| "ok");
|
||||
let res = lazy(|cx| srv.poll_ready(cx)).await;
|
||||
assert_eq!(res, Poll::Ready(Ok(())));
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_call() {
|
||||
let mut srv = Srv.map(|_| "ok");
|
||||
let res = srv.call(()).await;
|
||||
assert!(res.is_ok());
|
||||
assert_eq!(res.unwrap(), "ok");
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_new_service() {
|
||||
let new_srv = (|| ok::<_, ()>(Srv)).into_factory().map(|_| "ok");
|
||||
let mut srv = new_srv.new_service(&()).await.unwrap();
|
||||
let res = srv.call(()).await;
|
||||
assert!(res.is_ok());
|
||||
assert_eq!(res.unwrap(), ("ok"));
|
||||
}
|
||||
}
|
@@ -1,128 +0,0 @@
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use super::{IntoServiceFactory, ServiceFactory};
|
||||
|
||||
/// Adapt external config argument to a config for provided service factory
|
||||
///
|
||||
/// Note that this function consumes the receiving service factory and returns
|
||||
/// a wrapped version of it.
|
||||
pub fn map_config<I, SF, S, Req, F, Cfg>(factory: I, f: F) -> MapConfig<SF, Req, F, Cfg>
|
||||
where
|
||||
I: IntoServiceFactory<SF, Req>,
|
||||
SF: ServiceFactory<Req>,
|
||||
F: Fn(Cfg) -> SF::Config,
|
||||
{
|
||||
MapConfig::new(factory.into_factory(), f)
|
||||
}
|
||||
|
||||
/// Replace config with unit.
|
||||
pub fn unit_config<I, SF, Cfg, Req>(factory: I) -> UnitConfig<SF, Cfg, Req>
|
||||
where
|
||||
I: IntoServiceFactory<SF, Req>,
|
||||
SF: ServiceFactory<Req, Config = ()>,
|
||||
{
|
||||
UnitConfig::new(factory.into_factory())
|
||||
}
|
||||
|
||||
/// `map_config()` adapter service factory
|
||||
pub struct MapConfig<SF, Req, F, Cfg> {
|
||||
factory: SF,
|
||||
cfg_mapper: F,
|
||||
e: PhantomData<(Cfg, Req)>,
|
||||
}
|
||||
|
||||
impl<SF, Req, F, Cfg> MapConfig<SF, Req, F, Cfg> {
|
||||
/// Create new `MapConfig` combinator
|
||||
pub(crate) fn new(factory: SF, cfg_mapper: F) -> Self
|
||||
where
|
||||
SF: ServiceFactory<Req>,
|
||||
F: Fn(Cfg) -> SF::Config,
|
||||
{
|
||||
Self {
|
||||
factory,
|
||||
cfg_mapper,
|
||||
e: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<SF, Req, F, Cfg> Clone for MapConfig<SF, Req, F, Cfg>
|
||||
where
|
||||
SF: Clone,
|
||||
F: Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
factory: self.factory.clone(),
|
||||
cfg_mapper: self.cfg_mapper.clone(),
|
||||
e: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<SF, Req, F, Cfg> ServiceFactory<Req> for MapConfig<SF, Req, F, Cfg>
|
||||
where
|
||||
SF: ServiceFactory<Req>,
|
||||
F: Fn(Cfg) -> SF::Config,
|
||||
{
|
||||
type Response = SF::Response;
|
||||
type Error = SF::Error;
|
||||
|
||||
type Config = Cfg;
|
||||
type Service = SF::Service;
|
||||
type InitError = SF::InitError;
|
||||
type Future = SF::Future;
|
||||
|
||||
fn new_service(&self, cfg: Self::Config) -> Self::Future {
|
||||
let mapped_cfg = (self.cfg_mapper)(cfg);
|
||||
self.factory.new_service(mapped_cfg)
|
||||
}
|
||||
}
|
||||
|
||||
/// `unit_config()` config combinator
|
||||
pub struct UnitConfig<SF, Cfg, Req> {
|
||||
factory: SF,
|
||||
_phantom: PhantomData<(Cfg, Req)>,
|
||||
}
|
||||
|
||||
impl<SF, Cfg, Req> UnitConfig<SF, Cfg, Req>
|
||||
where
|
||||
SF: ServiceFactory<Req, Config = ()>,
|
||||
{
|
||||
/// Create new `UnitConfig` combinator
|
||||
pub(crate) fn new(factory: SF) -> Self {
|
||||
Self {
|
||||
factory,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<SF, Cfg, Req> Clone for UnitConfig<SF, Cfg, Req>
|
||||
where
|
||||
SF: Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
factory: self.factory.clone(),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<SF, Cfg, Req> ServiceFactory<Req> for UnitConfig<SF, Cfg, Req>
|
||||
where
|
||||
SF: ServiceFactory<Req, Config = ()>,
|
||||
{
|
||||
type Response = SF::Response;
|
||||
type Error = SF::Error;
|
||||
|
||||
type Config = Cfg;
|
||||
type Service = SF::Service;
|
||||
type InitError = SF::InitError;
|
||||
type Future = SF::Future;
|
||||
|
||||
fn new_service(&self, _: Cfg) -> Self::Future {
|
||||
self.factory.new_service(())
|
||||
}
|
||||
}
|
@@ -1,253 +0,0 @@
|
||||
use core::{
|
||||
future::Future,
|
||||
marker::PhantomData,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use super::{Service, ServiceFactory};
|
||||
|
||||
/// Service for the `map_err` combinator, changing the type of a service's
|
||||
/// error.
|
||||
///
|
||||
/// This is created by the `ServiceExt::map_err` method.
|
||||
pub struct MapErr<S, Req, F, E> {
|
||||
service: S,
|
||||
f: F,
|
||||
_t: PhantomData<(E, Req)>,
|
||||
}
|
||||
|
||||
impl<S, Req, F, E> MapErr<S, Req, F, E> {
|
||||
/// Create new `MapErr` combinator
|
||||
pub(crate) fn new(service: S, f: F) -> Self
|
||||
where
|
||||
S: Service<Req>,
|
||||
F: Fn(S::Error) -> E,
|
||||
{
|
||||
Self {
|
||||
service,
|
||||
f,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, Req, F, E> Clone for MapErr<S, Req, F, E>
|
||||
where
|
||||
S: Clone,
|
||||
F: Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
MapErr {
|
||||
service: self.service.clone(),
|
||||
f: self.f.clone(),
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, Req, F, E> Service<Req> for MapErr<A, Req, F, E>
|
||||
where
|
||||
A: Service<Req>,
|
||||
F: Fn(A::Error) -> E + Clone,
|
||||
{
|
||||
type Response = A::Response;
|
||||
type Error = E;
|
||||
type Future = MapErrFuture<A, Req, F, E>;
|
||||
|
||||
fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.service.poll_ready(ctx).map_err(&self.f)
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Req) -> Self::Future {
|
||||
MapErrFuture::new(self.service.call(req), self.f.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
pub struct MapErrFuture<A, Req, F, E>
|
||||
where
|
||||
A: Service<Req>,
|
||||
F: Fn(A::Error) -> E,
|
||||
{
|
||||
f: F,
|
||||
#[pin]
|
||||
fut: A::Future,
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, Req, F, E> MapErrFuture<A, Req, F, E>
|
||||
where
|
||||
A: Service<Req>,
|
||||
F: Fn(A::Error) -> E,
|
||||
{
|
||||
fn new(fut: A::Future, f: F) -> Self {
|
||||
MapErrFuture { f, fut }
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, Req, F, E> Future for MapErrFuture<A, Req, F, E>
|
||||
where
|
||||
A: Service<Req>,
|
||||
F: Fn(A::Error) -> E,
|
||||
{
|
||||
type Output = Result<A::Response, E>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
this.fut.poll(cx).map_err(this.f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Factory for the `map_err` combinator, changing the type of a new
|
||||
/// service's error.
|
||||
///
|
||||
/// This is created by the `NewServiceExt::map_err` method.
|
||||
pub struct MapErrServiceFactory<A, Req, F, E>
|
||||
where
|
||||
A: ServiceFactory<Req>,
|
||||
F: Fn(A::Error) -> E + Clone,
|
||||
{
|
||||
a: A,
|
||||
f: F,
|
||||
e: PhantomData<(E, Req)>,
|
||||
}
|
||||
|
||||
impl<A, Req, F, E> MapErrServiceFactory<A, Req, F, E>
|
||||
where
|
||||
A: ServiceFactory<Req>,
|
||||
F: Fn(A::Error) -> E + Clone,
|
||||
{
|
||||
/// Create new `MapErr` new service instance
|
||||
pub(crate) fn new(a: A, f: F) -> Self {
|
||||
Self {
|
||||
a,
|
||||
f,
|
||||
e: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, Req, F, E> Clone for MapErrServiceFactory<A, Req, F, E>
|
||||
where
|
||||
A: ServiceFactory<Req> + Clone,
|
||||
F: Fn(A::Error) -> E + Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
a: self.a.clone(),
|
||||
f: self.f.clone(),
|
||||
e: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, Req, F, E> ServiceFactory<Req> for MapErrServiceFactory<A, Req, F, E>
|
||||
where
|
||||
A: ServiceFactory<Req>,
|
||||
F: Fn(A::Error) -> E + Clone,
|
||||
{
|
||||
type Response = A::Response;
|
||||
type Error = E;
|
||||
|
||||
type Config = A::Config;
|
||||
type Service = MapErr<A::Service, Req, F, E>;
|
||||
type InitError = A::InitError;
|
||||
type Future = MapErrServiceFuture<A, Req, F, E>;
|
||||
|
||||
fn new_service(&self, cfg: A::Config) -> Self::Future {
|
||||
MapErrServiceFuture::new(self.a.new_service(cfg), self.f.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
pub struct MapErrServiceFuture<A, Req, F, E>
|
||||
where
|
||||
A: ServiceFactory<Req>,
|
||||
F: Fn(A::Error) -> E,
|
||||
{
|
||||
#[pin]
|
||||
fut: A::Future,
|
||||
f: F,
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, Req, F, E> MapErrServiceFuture<A, Req, F, E>
|
||||
where
|
||||
A: ServiceFactory<Req>,
|
||||
F: Fn(A::Error) -> E,
|
||||
{
|
||||
fn new(fut: A::Future, f: F) -> Self {
|
||||
MapErrServiceFuture { f, fut }
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, Req, F, E> Future for MapErrServiceFuture<A, Req, F, E>
|
||||
where
|
||||
A: ServiceFactory<Req>,
|
||||
F: Fn(A::Error) -> E + Clone,
|
||||
{
|
||||
type Output = Result<MapErr<A::Service, Req, F, E>, A::InitError>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
if let Poll::Ready(svc) = this.fut.poll(cx)? {
|
||||
Poll::Ready(Ok(MapErr::new(svc, this.f.clone())))
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use futures_util::future::lazy;
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
err, ok, IntoServiceFactory, Ready, Service, ServiceExt, ServiceFactory,
|
||||
ServiceFactoryExt,
|
||||
};
|
||||
|
||||
struct Srv;
|
||||
|
||||
impl Service<()> for Srv {
|
||||
type Response = ();
|
||||
type Error = ();
|
||||
type Future = Ready<Result<(), ()>>;
|
||||
|
||||
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Err(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, _: ()) -> Self::Future {
|
||||
err(())
|
||||
}
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_poll_ready() {
|
||||
let mut srv = Srv.map_err(|_| "error");
|
||||
let res = lazy(|cx| srv.poll_ready(cx)).await;
|
||||
assert_eq!(res, Poll::Ready(Err("error")));
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_call() {
|
||||
let mut srv = Srv.map_err(|_| "error");
|
||||
let res = srv.call(()).await;
|
||||
assert!(res.is_err());
|
||||
assert_eq!(res.err().unwrap(), "error");
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_new_service() {
|
||||
let new_srv = (|| ok::<_, ()>(Srv)).into_factory().map_err(|_| "error");
|
||||
let mut srv = new_srv.new_service(&()).await.unwrap();
|
||||
let res = srv.call(()).await;
|
||||
assert!(res.is_err());
|
||||
assert_eq!(res.err().unwrap(), "error");
|
||||
}
|
||||
}
|
@@ -1,99 +0,0 @@
|
||||
use core::{
|
||||
future::Future,
|
||||
marker::PhantomData,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use super::ServiceFactory;
|
||||
|
||||
/// `MapInitErr` service combinator
|
||||
pub struct MapInitErr<A, F, Req, Err> {
|
||||
a: A,
|
||||
f: F,
|
||||
e: PhantomData<(Req, Err)>,
|
||||
}
|
||||
|
||||
impl<A, F, Req, Err> MapInitErr<A, F, Req, Err>
|
||||
where
|
||||
A: ServiceFactory<Req>,
|
||||
F: Fn(A::InitError) -> Err,
|
||||
{
|
||||
/// Create new `MapInitErr` combinator
|
||||
pub(crate) fn new(a: A, f: F) -> Self {
|
||||
Self {
|
||||
a,
|
||||
f,
|
||||
e: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, F, Req, E> Clone for MapInitErr<A, F, Req, E>
|
||||
where
|
||||
A: Clone,
|
||||
F: Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
a: self.a.clone(),
|
||||
f: self.f.clone(),
|
||||
e: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, F, Req, E> ServiceFactory<Req> for MapInitErr<A, F, Req, E>
|
||||
where
|
||||
A: ServiceFactory<Req>,
|
||||
F: Fn(A::InitError) -> E + Clone,
|
||||
{
|
||||
type Response = A::Response;
|
||||
type Error = A::Error;
|
||||
|
||||
type Config = A::Config;
|
||||
type Service = A::Service;
|
||||
type InitError = E;
|
||||
type Future = MapInitErrFuture<A, F, Req, E>;
|
||||
|
||||
fn new_service(&self, cfg: A::Config) -> Self::Future {
|
||||
MapInitErrFuture::new(self.a.new_service(cfg), self.f.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
pub struct MapInitErrFuture<A, F, Req, E>
|
||||
where
|
||||
A: ServiceFactory<Req>,
|
||||
F: Fn(A::InitError) -> E,
|
||||
{
|
||||
f: F,
|
||||
#[pin]
|
||||
fut: A::Future,
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, F, Req, E> MapInitErrFuture<A, F, Req, E>
|
||||
where
|
||||
A: ServiceFactory<Req>,
|
||||
F: Fn(A::InitError) -> E,
|
||||
{
|
||||
fn new(fut: A::Future, f: F) -> Self {
|
||||
MapInitErrFuture { f, fut }
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, F, Req, E> Future for MapInitErrFuture<A, F, Req, E>
|
||||
where
|
||||
A: ServiceFactory<Req>,
|
||||
F: Fn(A::InitError) -> E,
|
||||
{
|
||||
type Output = Result<A::Service, E>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
this.fut.poll(cx).map_err(this.f)
|
||||
}
|
||||
}
|
@@ -1,306 +0,0 @@
|
||||
use core::{
|
||||
marker::PhantomData,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use crate::and_then::{AndThenService, AndThenServiceFactory};
|
||||
use crate::map::{Map, MapServiceFactory};
|
||||
use crate::map_err::{MapErr, MapErrServiceFactory};
|
||||
use crate::map_init_err::MapInitErr;
|
||||
use crate::then::{ThenService, ThenServiceFactory};
|
||||
use crate::{IntoService, IntoServiceFactory, Service, ServiceFactory};
|
||||
|
||||
/// Construct new pipeline with one service in pipeline chain.
|
||||
pub fn pipeline<I, S, Req>(service: I) -> Pipeline<S, Req>
|
||||
where
|
||||
I: IntoService<S, Req>,
|
||||
S: Service<Req>,
|
||||
{
|
||||
Pipeline {
|
||||
service: service.into_service(),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct new pipeline factory with one service factory.
|
||||
pub fn pipeline_factory<I, SF, Req>(factory: I) -> PipelineFactory<SF, Req>
|
||||
where
|
||||
I: IntoServiceFactory<SF, Req>,
|
||||
SF: ServiceFactory<Req>,
|
||||
{
|
||||
PipelineFactory {
|
||||
factory: factory.into_factory(),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Pipeline service - pipeline allows to compose multiple service into one service.
|
||||
pub struct Pipeline<S, Req> {
|
||||
service: S,
|
||||
_phantom: PhantomData<Req>,
|
||||
}
|
||||
|
||||
impl<S, Req> Pipeline<S, Req>
|
||||
where
|
||||
S: Service<Req>,
|
||||
{
|
||||
/// Call another service after call to this one has resolved successfully.
|
||||
///
|
||||
/// This function can be used to chain two services together and ensure that
|
||||
/// the second service isn't called until call to the fist service have
|
||||
/// finished. Result of the call to the first service is used as an
|
||||
/// input parameter for the second service's call.
|
||||
///
|
||||
/// Note that this function consumes the receiving service and returns a
|
||||
/// wrapped version of it.
|
||||
pub fn and_then<I, S1>(
|
||||
self,
|
||||
service: I,
|
||||
) -> Pipeline<impl Service<Req, Response = S1::Response, Error = S::Error> + Clone, Req>
|
||||
where
|
||||
Self: Sized,
|
||||
I: IntoService<S1, S::Response>,
|
||||
S1: Service<S::Response, Error = S::Error>,
|
||||
{
|
||||
Pipeline {
|
||||
service: AndThenService::new(self.service, service.into_service()),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Chain on a computation for when a call to the service finished,
|
||||
/// passing the result of the call to the next service `U`.
|
||||
///
|
||||
/// Note that this function consumes the receiving pipeline and returns a
|
||||
/// wrapped version of it.
|
||||
pub fn then<F, S1>(
|
||||
self,
|
||||
service: F,
|
||||
) -> Pipeline<impl Service<Req, Response = S1::Response, Error = S::Error> + Clone, Req>
|
||||
where
|
||||
Self: Sized,
|
||||
F: IntoService<S1, Result<S::Response, S::Error>>,
|
||||
S1: Service<Result<S::Response, S::Error>, Error = S::Error>,
|
||||
{
|
||||
Pipeline {
|
||||
service: ThenService::new(self.service, service.into_service()),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Map this service's output to a different type, returning a new service
|
||||
/// of the resulting type.
|
||||
///
|
||||
/// This function is similar to the `Option::map` or `Iterator::map` where
|
||||
/// it will change the type of the underlying service.
|
||||
///
|
||||
/// Note that this function consumes the receiving service and returns a
|
||||
/// wrapped version of it, similar to the existing `map` methods in the
|
||||
/// standard library.
|
||||
pub fn map<F, R>(self, f: F) -> Pipeline<Map<S, F, Req, R>, Req>
|
||||
where
|
||||
Self: Sized,
|
||||
F: FnMut(S::Response) -> R,
|
||||
{
|
||||
Pipeline {
|
||||
service: Map::new(self.service, f),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Map this service's error to a different error, returning a new service.
|
||||
///
|
||||
/// This function is similar to the `Result::map_err` where it will change
|
||||
/// the error type of the underlying service. This is useful for example to
|
||||
/// ensure that services have the same error type.
|
||||
///
|
||||
/// Note that this function consumes the receiving service and returns a
|
||||
/// wrapped version of it.
|
||||
pub fn map_err<F, E>(self, f: F) -> Pipeline<MapErr<S, Req, F, E>, Req>
|
||||
where
|
||||
Self: Sized,
|
||||
F: Fn(S::Error) -> E,
|
||||
{
|
||||
Pipeline {
|
||||
service: MapErr::new(self.service, f),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Req> Clone for Pipeline<T, Req>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Pipeline {
|
||||
service: self.service.clone(),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Service<Req>, Req> Service<Req> for Pipeline<S, Req> {
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type Future = S::Future;
|
||||
|
||||
#[inline]
|
||||
fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), S::Error>> {
|
||||
self.service.poll_ready(ctx)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn call(&mut self, req: Req) -> Self::Future {
|
||||
self.service.call(req)
|
||||
}
|
||||
}
|
||||
|
||||
/// Pipeline factory
|
||||
pub struct PipelineFactory<SF, Req> {
|
||||
factory: SF,
|
||||
_phantom: PhantomData<Req>,
|
||||
}
|
||||
|
||||
impl<SF, Req> PipelineFactory<SF, Req>
|
||||
where
|
||||
SF: ServiceFactory<Req>,
|
||||
{
|
||||
/// Call another service after call to this one has resolved successfully.
|
||||
pub fn and_then<I, SF1>(
|
||||
self,
|
||||
factory: I,
|
||||
) -> PipelineFactory<
|
||||
impl ServiceFactory<
|
||||
Req,
|
||||
Response = SF1::Response,
|
||||
Error = SF::Error,
|
||||
Config = SF::Config,
|
||||
InitError = SF::InitError,
|
||||
Service = impl Service<Req, Response = SF1::Response, Error = SF::Error> + Clone,
|
||||
> + Clone,
|
||||
Req,
|
||||
>
|
||||
where
|
||||
Self: Sized,
|
||||
SF::Config: Clone,
|
||||
I: IntoServiceFactory<SF1, SF::Response>,
|
||||
SF1: ServiceFactory<
|
||||
SF::Response,
|
||||
Config = SF::Config,
|
||||
Error = SF::Error,
|
||||
InitError = SF::InitError,
|
||||
>,
|
||||
{
|
||||
PipelineFactory {
|
||||
factory: AndThenServiceFactory::new(self.factory, factory.into_factory()),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create `NewService` to chain on a computation for when a call to the
|
||||
/// service finished, passing the result of the call to the next
|
||||
/// service `U`.
|
||||
///
|
||||
/// Note that this function consumes the receiving pipeline and returns a
|
||||
/// wrapped version of it.
|
||||
pub fn then<I, SF1>(
|
||||
self,
|
||||
factory: I,
|
||||
) -> PipelineFactory<
|
||||
impl ServiceFactory<
|
||||
Req,
|
||||
Response = SF1::Response,
|
||||
Error = SF::Error,
|
||||
Config = SF::Config,
|
||||
InitError = SF::InitError,
|
||||
Service = impl Service<Req, Response = SF1::Response, Error = SF::Error> + Clone,
|
||||
> + Clone,
|
||||
Req,
|
||||
>
|
||||
where
|
||||
Self: Sized,
|
||||
SF::Config: Clone,
|
||||
I: IntoServiceFactory<SF1, Result<SF::Response, SF::Error>>,
|
||||
SF1: ServiceFactory<
|
||||
Result<SF::Response, SF::Error>,
|
||||
Config = SF::Config,
|
||||
Error = SF::Error,
|
||||
InitError = SF::InitError,
|
||||
>,
|
||||
{
|
||||
PipelineFactory {
|
||||
factory: ThenServiceFactory::new(self.factory, factory.into_factory()),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Map this service's output to a different type, returning a new service
|
||||
/// of the resulting type.
|
||||
pub fn map<F, R>(self, f: F) -> PipelineFactory<MapServiceFactory<SF, F, Req, R>, Req>
|
||||
where
|
||||
Self: Sized,
|
||||
F: FnMut(SF::Response) -> R + Clone,
|
||||
{
|
||||
PipelineFactory {
|
||||
factory: MapServiceFactory::new(self.factory, f),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Map this service's error to a different error, returning a new service.
|
||||
pub fn map_err<F, E>(
|
||||
self,
|
||||
f: F,
|
||||
) -> PipelineFactory<MapErrServiceFactory<SF, Req, F, E>, Req>
|
||||
where
|
||||
Self: Sized,
|
||||
F: Fn(SF::Error) -> E + Clone,
|
||||
{
|
||||
PipelineFactory {
|
||||
factory: MapErrServiceFactory::new(self.factory, f),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Map this factory's init error to a different error, returning a new service.
|
||||
pub fn map_init_err<F, E>(self, f: F) -> PipelineFactory<MapInitErr<SF, F, Req, E>, Req>
|
||||
where
|
||||
Self: Sized,
|
||||
F: Fn(SF::InitError) -> E + Clone,
|
||||
{
|
||||
PipelineFactory {
|
||||
factory: MapInitErr::new(self.factory, f),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Req> Clone for PipelineFactory<T, Req>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
PipelineFactory {
|
||||
factory: self.factory.clone(),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<SF, Req> ServiceFactory<Req> for PipelineFactory<SF, Req>
|
||||
where
|
||||
SF: ServiceFactory<Req>,
|
||||
{
|
||||
type Config = SF::Config;
|
||||
type Response = SF::Response;
|
||||
type Error = SF::Error;
|
||||
type Service = SF::Service;
|
||||
type InitError = SF::InitError;
|
||||
type Future = SF::Future;
|
||||
|
||||
#[inline]
|
||||
fn new_service(&self, cfg: SF::Config) -> Self::Future {
|
||||
self.factory.new_service(cfg)
|
||||
}
|
||||
}
|
@@ -1,54 +0,0 @@
|
||||
//! When MSRV is 1.48, replace with `core::future::Ready` and `core::future::ready()`.
|
||||
|
||||
use core::{
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
/// Future for the [`ready`](ready()) function.
|
||||
#[derive(Debug, Clone)]
|
||||
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||
pub struct Ready<T> {
|
||||
val: Option<T>,
|
||||
}
|
||||
|
||||
impl<T> Ready<T> {
|
||||
/// Unwraps the value from this immediately ready future.
|
||||
#[inline]
|
||||
pub fn into_inner(mut self) -> T {
|
||||
self.val.take().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Unpin for Ready<T> {}
|
||||
|
||||
impl<T> Future for Ready<T> {
|
||||
type Output = T;
|
||||
|
||||
#[inline]
|
||||
fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<T> {
|
||||
let val = self.val.take().expect("Ready can not be polled twice.");
|
||||
Poll::Ready(val)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a future that is immediately ready with a value.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn ready<T>(val: T) -> Ready<T> {
|
||||
Ready { val: Some(val) }
|
||||
}
|
||||
|
||||
/// Create a future that is immediately ready with a success value.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn ok<T, E>(val: T) -> Ready<Result<T, E>> {
|
||||
Ready { val: Some(Ok(val)) }
|
||||
}
|
||||
|
||||
/// Create a future that is immediately ready with an error value.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn err<T, E>(err: E) -> Ready<Result<T, E>> {
|
||||
Ready {
|
||||
val: Some(Err(err)),
|
||||
}
|
||||
}
|
@@ -1,340 +0,0 @@
|
||||
use alloc::rc::Rc;
|
||||
use core::{
|
||||
cell::RefCell,
|
||||
future::Future,
|
||||
marker::PhantomData,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use super::{Service, ServiceFactory};
|
||||
|
||||
/// Service for the `then` combinator, chaining a computation onto the end of
|
||||
/// another service.
|
||||
///
|
||||
/// This is created by the `Pipeline::then` method.
|
||||
pub(crate) struct ThenService<A, B, Req>(Rc<RefCell<(A, B)>>, PhantomData<Req>);
|
||||
|
||||
impl<A, B, Req> ThenService<A, B, Req> {
|
||||
/// Create new `.then()` combinator
|
||||
pub(crate) fn new(a: A, b: B) -> ThenService<A, B, Req>
|
||||
where
|
||||
A: Service<Req>,
|
||||
B: Service<Result<A::Response, A::Error>, Error = A::Error>,
|
||||
{
|
||||
Self(Rc::new(RefCell::new((a, b))), PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B, Req> Clone for ThenService<A, B, Req> {
|
||||
fn clone(&self) -> Self {
|
||||
ThenService(self.0.clone(), PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B, Req> Service<Req> for ThenService<A, B, Req>
|
||||
where
|
||||
A: Service<Req>,
|
||||
B: Service<Result<A::Response, A::Error>, Error = A::Error>,
|
||||
{
|
||||
type Response = B::Response;
|
||||
type Error = B::Error;
|
||||
type Future = ThenServiceResponse<A, B, Req>;
|
||||
|
||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
let mut srv = self.0.borrow_mut();
|
||||
let not_ready = !srv.0.poll_ready(cx)?.is_ready();
|
||||
if !srv.1.poll_ready(cx)?.is_ready() || not_ready {
|
||||
Poll::Pending
|
||||
} else {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Req) -> Self::Future {
|
||||
ThenServiceResponse {
|
||||
state: State::A {
|
||||
fut: self.0.borrow_mut().0.call(req),
|
||||
b: Some(self.0.clone()),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
pub(crate) struct ThenServiceResponse<A, B, Req>
|
||||
where
|
||||
A: Service<Req>,
|
||||
B: Service<Result<A::Response, A::Error>>,
|
||||
{
|
||||
#[pin]
|
||||
state: State<A, B, Req>,
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
#[project = StateProj]
|
||||
enum State<A, B, Req>
|
||||
where
|
||||
A: Service<Req>,
|
||||
B: Service<Result<A::Response, A::Error>>,
|
||||
{
|
||||
A { #[pin] fut: A::Future, b: Option<Rc<RefCell<(A, B)>>> },
|
||||
B { #[pin] fut: B::Future },
|
||||
Empty,
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B, Req> Future for ThenServiceResponse<A, B, Req>
|
||||
where
|
||||
A: Service<Req>,
|
||||
B: Service<Result<A::Response, A::Error>>,
|
||||
{
|
||||
type Output = Result<B::Response, B::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`")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `.then()` service factory combinator
|
||||
pub(crate) struct ThenServiceFactory<A, B, Req>(Rc<(A, B)>, PhantomData<Req>);
|
||||
|
||||
impl<A, B, Req> ThenServiceFactory<A, B, Req>
|
||||
where
|
||||
A: ServiceFactory<Req>,
|
||||
A::Config: Clone,
|
||||
B: ServiceFactory<
|
||||
Result<A::Response, A::Error>,
|
||||
Config = A::Config,
|
||||
Error = A::Error,
|
||||
InitError = A::InitError,
|
||||
>,
|
||||
{
|
||||
/// Create new `AndThen` combinator
|
||||
pub(crate) fn new(a: A, b: B) -> Self {
|
||||
Self(Rc::new((a, b)), PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B, Req> ServiceFactory<Req> for ThenServiceFactory<A, B, Req>
|
||||
where
|
||||
A: ServiceFactory<Req>,
|
||||
A::Config: Clone,
|
||||
B: ServiceFactory<
|
||||
Result<A::Response, A::Error>,
|
||||
Config = A::Config,
|
||||
Error = A::Error,
|
||||
InitError = A::InitError,
|
||||
>,
|
||||
{
|
||||
type Response = B::Response;
|
||||
type Error = A::Error;
|
||||
|
||||
type Config = A::Config;
|
||||
type Service = ThenService<A::Service, B::Service, Req>;
|
||||
type InitError = A::InitError;
|
||||
type Future = ThenServiceFactoryResponse<A, B, Req>;
|
||||
|
||||
fn new_service(&self, cfg: A::Config) -> Self::Future {
|
||||
let srv = &*self.0;
|
||||
ThenServiceFactoryResponse::new(srv.0.new_service(cfg.clone()), srv.1.new_service(cfg))
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B, Req> Clone for ThenServiceFactory<A, B, Req> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone(), PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
pub(crate) struct ThenServiceFactoryResponse<A, B, Req>
|
||||
where
|
||||
A: ServiceFactory<Req>,
|
||||
B: ServiceFactory<
|
||||
Result<A::Response, A::Error>,
|
||||
Config = A::Config,
|
||||
Error = A::Error,
|
||||
InitError = A::InitError,
|
||||
>,
|
||||
{
|
||||
#[pin]
|
||||
fut_b: B::Future,
|
||||
#[pin]
|
||||
fut_a: A::Future,
|
||||
a: Option<A::Service>,
|
||||
b: Option<B::Service>,
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B, Req> ThenServiceFactoryResponse<A, B, Req>
|
||||
where
|
||||
A: ServiceFactory<Req>,
|
||||
B: ServiceFactory<
|
||||
Result<A::Response, A::Error>,
|
||||
Config = A::Config,
|
||||
Error = A::Error,
|
||||
InitError = A::InitError,
|
||||
>,
|
||||
{
|
||||
fn new(fut_a: A::Future, fut_b: B::Future) -> Self {
|
||||
Self {
|
||||
fut_a,
|
||||
fut_b,
|
||||
a: None,
|
||||
b: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B, Req> Future for ThenServiceFactoryResponse<A, B, Req>
|
||||
where
|
||||
A: ServiceFactory<Req>,
|
||||
B: ServiceFactory<
|
||||
Result<A::Response, A::Error>,
|
||||
Config = A::Config,
|
||||
Error = A::Error,
|
||||
InitError = A::InitError,
|
||||
>,
|
||||
{
|
||||
type Output = Result<ThenService<A::Service, B::Service, Req>, A::InitError>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
|
||||
if this.a.is_none() {
|
||||
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)? {
|
||||
*this.b = Some(service);
|
||||
}
|
||||
}
|
||||
if this.a.is_some() && this.b.is_some() {
|
||||
Poll::Ready(Ok(ThenService::new(
|
||||
this.a.take().unwrap(),
|
||||
this.b.take().unwrap(),
|
||||
)))
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::rc::Rc;
|
||||
use core::{
|
||||
cell::Cell,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use futures_util::future::lazy;
|
||||
|
||||
use crate::{err, ok, pipeline, pipeline_factory, ready, Ready, Service, ServiceFactory};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Srv1(Rc<Cell<usize>>);
|
||||
|
||||
impl Service<Result<&'static str, &'static str>> for Srv1 {
|
||||
type Response = &'static str;
|
||||
type Error = ();
|
||||
type Future = Ready<Result<Self::Response, Self::Error>>;
|
||||
|
||||
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.0.set(self.0.get() + 1);
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Result<&'static str, &'static str>) -> Self::Future {
|
||||
match req {
|
||||
Ok(msg) => ok(msg),
|
||||
Err(_) => err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Srv2(Rc<Cell<usize>>);
|
||||
|
||||
impl Service<Result<&'static str, ()>> for Srv2 {
|
||||
type Response = (&'static str, &'static str);
|
||||
type Error = ();
|
||||
type Future = Ready<Result<Self::Response, ()>>;
|
||||
|
||||
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.0.set(self.0.get() + 1);
|
||||
Poll::Ready(Err(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Result<&'static str, ()>) -> Self::Future {
|
||||
match req {
|
||||
Ok(msg) => ok((msg, "ok")),
|
||||
Err(()) => ok(("srv2", "err")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_poll_ready() {
|
||||
let cnt = Rc::new(Cell::new(0));
|
||||
let mut srv = pipeline(Srv1(cnt.clone())).then(Srv2(cnt.clone()));
|
||||
let res = lazy(|cx| srv.poll_ready(cx)).await;
|
||||
assert_eq!(res, Poll::Ready(Err(())));
|
||||
assert_eq!(cnt.get(), 2);
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_call() {
|
||||
let cnt = Rc::new(Cell::new(0));
|
||||
let mut srv = pipeline(Srv1(cnt.clone())).then(Srv2(cnt));
|
||||
|
||||
let res = srv.call(Ok("srv1")).await;
|
||||
assert!(res.is_ok());
|
||||
assert_eq!(res.unwrap(), ("srv1", "ok"));
|
||||
|
||||
let res = srv.call(Err("srv")).await;
|
||||
assert!(res.is_ok());
|
||||
assert_eq!(res.unwrap(), ("srv2", "err"));
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_factory() {
|
||||
let cnt = Rc::new(Cell::new(0));
|
||||
let cnt2 = cnt.clone();
|
||||
let blank = move || ready(Ok::<_, ()>(Srv1(cnt2.clone())));
|
||||
let factory = pipeline_factory(blank).then(move || ready(Ok(Srv2(cnt.clone()))));
|
||||
let mut srv = factory.new_service(&()).await.unwrap();
|
||||
let res = srv.call(Ok("srv1")).await;
|
||||
assert!(res.is_ok());
|
||||
assert_eq!(res.unwrap(), ("srv1", "ok"));
|
||||
|
||||
let res = srv.call(Err("srv")).await;
|
||||
assert!(res.is_ok());
|
||||
assert_eq!(res.unwrap(), ("srv2", "err"));
|
||||
}
|
||||
}
|
@@ -1,245 +0,0 @@
|
||||
use alloc::{rc::Rc, sync::Arc};
|
||||
use core::{
|
||||
future::Future,
|
||||
marker::PhantomData,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use crate::transform_err::TransformMapInitErr;
|
||||
use crate::{IntoServiceFactory, Service, ServiceFactory};
|
||||
|
||||
/// Apply transform to a service.
|
||||
pub fn apply<T, S, I, Req>(t: T, factory: I) -> ApplyTransform<T, S, Req>
|
||||
where
|
||||
I: IntoServiceFactory<S, Req>,
|
||||
S: ServiceFactory<Req>,
|
||||
T: Transform<S::Service, Req, InitError = S::InitError>,
|
||||
{
|
||||
ApplyTransform::new(t, factory.into_factory())
|
||||
}
|
||||
|
||||
/// The `Transform` trait defines the interface of a service factory that wraps inner service
|
||||
/// during construction.
|
||||
///
|
||||
/// Transform(middleware) wraps inner service and runs during
|
||||
/// inbound and/or outbound processing in the request/response lifecycle.
|
||||
/// It may modify request and/or response.
|
||||
///
|
||||
/// For example, timeout transform:
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// pub struct Timeout<S> {
|
||||
/// service: S,
|
||||
/// timeout: Duration,
|
||||
/// }
|
||||
///
|
||||
/// impl<S> Service for Timeout<S>
|
||||
/// where
|
||||
/// S: Service,
|
||||
/// {
|
||||
/// type Request = S::Request;
|
||||
/// type Response = S::Response;
|
||||
/// type Error = TimeoutError<S::Error>;
|
||||
/// type Future = TimeoutServiceResponse<S>;
|
||||
///
|
||||
/// fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
/// ready!(self.service.poll_ready(cx)).map_err(TimeoutError::Service)
|
||||
/// }
|
||||
///
|
||||
/// fn call(&mut self, req: S::Request) -> Self::Future {
|
||||
/// TimeoutServiceResponse {
|
||||
/// fut: self.service.call(req),
|
||||
/// sleep: Delay::new(clock::now() + self.timeout),
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Timeout service in above example is decoupled from underlying service implementation
|
||||
/// and could be applied to any service.
|
||||
///
|
||||
/// The `Transform` trait defines the interface of a Service factory. `Transform`
|
||||
/// is often implemented for middleware, defining how to construct a
|
||||
/// middleware Service. A Service that is constructed by the factory takes
|
||||
/// the Service that follows it during execution as a parameter, assuming
|
||||
/// ownership of the next Service.
|
||||
///
|
||||
/// Factory for `Timeout` middleware from the above example could look like this:
|
||||
///
|
||||
/// ```rust,,ignore
|
||||
/// pub struct TimeoutTransform {
|
||||
/// timeout: Duration,
|
||||
/// }
|
||||
///
|
||||
/// impl<S> Transform<S> for TimeoutTransform
|
||||
/// where
|
||||
/// S: Service,
|
||||
/// {
|
||||
/// type Request = S::Request;
|
||||
/// type Response = S::Response;
|
||||
/// type Error = TimeoutError<S::Error>;
|
||||
/// type InitError = S::Error;
|
||||
/// type Transform = Timeout<S>;
|
||||
/// type Future = Ready<Result<Self::Transform, Self::InitError>>;
|
||||
///
|
||||
/// fn new_transform(&self, service: S) -> Self::Future {
|
||||
/// ok(TimeoutService {
|
||||
/// service,
|
||||
/// timeout: self.timeout,
|
||||
/// })
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub trait Transform<S, Req> {
|
||||
/// Responses given by the service.
|
||||
type Response;
|
||||
|
||||
/// Errors produced by the service.
|
||||
type Error;
|
||||
|
||||
/// The `TransformService` value created by this factory
|
||||
type Transform: Service<Req, Response = Self::Response, Error = Self::Error>;
|
||||
|
||||
/// Errors produced while building a transform service.
|
||||
type InitError;
|
||||
|
||||
/// The future response value.
|
||||
type Future: Future<Output = Result<Self::Transform, Self::InitError>>;
|
||||
|
||||
/// Creates and returns a new Transform component, asynchronously
|
||||
fn new_transform(&self, service: S) -> Self::Future;
|
||||
|
||||
/// Map this transform's factory error to a different error,
|
||||
/// returning a new transform service factory.
|
||||
fn map_init_err<F, E>(self, f: F) -> TransformMapInitErr<Self, S, Req, F, E>
|
||||
where
|
||||
Self: Sized,
|
||||
F: Fn(Self::InitError) -> E + Clone,
|
||||
{
|
||||
TransformMapInitErr::new(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S, Req> Transform<S, Req> for Rc<T>
|
||||
where
|
||||
T: Transform<S, Req>,
|
||||
{
|
||||
type Response = T::Response;
|
||||
type Error = T::Error;
|
||||
type InitError = T::InitError;
|
||||
type Transform = T::Transform;
|
||||
type Future = T::Future;
|
||||
|
||||
fn new_transform(&self, service: S) -> T::Future {
|
||||
self.as_ref().new_transform(service)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S, Req> Transform<S, Req> for Arc<T>
|
||||
where
|
||||
T: Transform<S, Req>,
|
||||
{
|
||||
type Response = T::Response;
|
||||
type Error = T::Error;
|
||||
type InitError = T::InitError;
|
||||
type Transform = T::Transform;
|
||||
type Future = T::Future;
|
||||
|
||||
fn new_transform(&self, service: S) -> T::Future {
|
||||
self.as_ref().new_transform(service)
|
||||
}
|
||||
}
|
||||
|
||||
/// `Apply` transform to new service
|
||||
pub struct ApplyTransform<T, S, Req>(Rc<(T, S)>, PhantomData<Req>);
|
||||
|
||||
impl<T, S, Req> ApplyTransform<T, S, Req>
|
||||
where
|
||||
S: ServiceFactory<Req>,
|
||||
T: Transform<S::Service, Req, InitError = S::InitError>,
|
||||
{
|
||||
/// Create new `ApplyTransform` new service instance
|
||||
fn new(t: T, service: S) -> Self {
|
||||
Self(Rc::new((t, service)), PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S, Req> Clone for ApplyTransform<T, S, Req> {
|
||||
fn clone(&self) -> Self {
|
||||
ApplyTransform(self.0.clone(), PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S, Req> ServiceFactory<Req> for ApplyTransform<T, S, Req>
|
||||
where
|
||||
S: ServiceFactory<Req>,
|
||||
T: Transform<S::Service, Req, InitError = S::InitError>,
|
||||
{
|
||||
type Response = T::Response;
|
||||
type Error = T::Error;
|
||||
|
||||
type Config = S::Config;
|
||||
type Service = T::Transform;
|
||||
type InitError = T::InitError;
|
||||
type Future = ApplyTransformFuture<T, S, Req>;
|
||||
|
||||
fn new_service(&self, cfg: S::Config) -> Self::Future {
|
||||
ApplyTransformFuture {
|
||||
store: self.0.clone(),
|
||||
state: ApplyTransformFutureState::A {
|
||||
fut: self.0.as_ref().1.new_service(cfg),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
pub struct ApplyTransformFuture<T, S, Req>
|
||||
where
|
||||
S: ServiceFactory<Req>,
|
||||
T: Transform<S::Service, Req, InitError = S::InitError>,
|
||||
{
|
||||
store: Rc<(T, S)>,
|
||||
#[pin]
|
||||
state: ApplyTransformFutureState<T, S, Req>,
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
#[project = ApplyTransformFutureStateProj]
|
||||
pub enum ApplyTransformFutureState<T, S, Req>
|
||||
where
|
||||
S: ServiceFactory<Req>,
|
||||
T: Transform<S::Service, Req, InitError = S::InitError>,
|
||||
{
|
||||
A { #[pin] fut: S::Future },
|
||||
B { #[pin] fut: T::Future },
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S, Req> Future for ApplyTransformFuture<T, S, Req>
|
||||
where
|
||||
S: ServiceFactory<Req>,
|
||||
T: Transform<S::Service, Req, InitError = S::InitError>,
|
||||
{
|
||||
type Output = Result<T::Transform, T::InitError>;
|
||||
|
||||
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() {
|
||||
ApplyTransformFutureStateProj::A { fut } => match fut.poll(cx)? {
|
||||
Poll::Ready(srv) => {
|
||||
let fut = this.store.0.new_transform(srv);
|
||||
this.state.set(ApplyTransformFutureState::B { fut });
|
||||
self.poll(cx)
|
||||
}
|
||||
Poll::Pending => Poll::Pending,
|
||||
},
|
||||
ApplyTransformFutureStateProj::B { fut } => fut.poll(cx),
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,97 +0,0 @@
|
||||
use core::{
|
||||
future::Future,
|
||||
marker::PhantomData,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use super::Transform;
|
||||
|
||||
/// Transform for the `map_init_err` combinator, changing the type of a new
|
||||
/// transform's init error.
|
||||
///
|
||||
/// This is created by the `Transform::map_init_err` method.
|
||||
pub struct TransformMapInitErr<T, S, Req, F, E> {
|
||||
transform: T,
|
||||
mapper: F,
|
||||
_phantom: PhantomData<(S, Req, E)>,
|
||||
}
|
||||
|
||||
impl<T, S, F, E, Req> TransformMapInitErr<T, S, Req, F, E> {
|
||||
pub(crate) fn new(t: T, f: F) -> Self
|
||||
where
|
||||
T: Transform<S, Req>,
|
||||
F: Fn(T::InitError) -> E,
|
||||
{
|
||||
Self {
|
||||
transform: t,
|
||||
mapper: f,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S, Req, F, E> Clone for TransformMapInitErr<T, S, Req, F, E>
|
||||
where
|
||||
T: Clone,
|
||||
F: Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
transform: self.transform.clone(),
|
||||
mapper: self.mapper.clone(),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S, F, E, Req> Transform<S, Req> for TransformMapInitErr<T, S, Req, F, E>
|
||||
where
|
||||
T: Transform<S, Req>,
|
||||
F: Fn(T::InitError) -> E + Clone,
|
||||
{
|
||||
type Response = T::Response;
|
||||
type Error = T::Error;
|
||||
type Transform = T::Transform;
|
||||
|
||||
type InitError = E;
|
||||
type Future = TransformMapInitErrFuture<T, S, F, E, Req>;
|
||||
|
||||
fn new_transform(&self, service: S) -> Self::Future {
|
||||
TransformMapInitErrFuture {
|
||||
fut: self.transform.new_transform(service),
|
||||
f: self.mapper.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
pub struct TransformMapInitErrFuture<T, S, F, E, Req>
|
||||
where
|
||||
T: Transform<S, Req>,
|
||||
F: Fn(T::InitError) -> E,
|
||||
{
|
||||
#[pin]
|
||||
fut: T::Future,
|
||||
f: F,
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S, F, E, Req> Future for TransformMapInitErrFuture<T, S, F, E, Req>
|
||||
where
|
||||
T: Transform<S, Req>,
|
||||
F: Fn(T::InitError) -> E + Clone,
|
||||
{
|
||||
type Output = Result<T::Transform, E>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
if let Poll::Ready(res) = this.fut.poll(cx) {
|
||||
Poll::Ready(res.map_err(this.f))
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,33 +0,0 @@
|
||||
# Changes
|
||||
|
||||
## [1.0.1] - 2020-05-19
|
||||
|
||||
* Replace deprecated `net2` crate with `socket2`
|
||||
|
||||
* Remove unused `futures` dependency
|
||||
|
||||
## [1.0.0] - 2019-12-11
|
||||
|
||||
* Update actix-server to 1.0.0
|
||||
|
||||
## [1.0.0-alpha.3] - 2019-12-07
|
||||
|
||||
* Migrate to tokio 0.2
|
||||
|
||||
## [1.0.0-alpha.2] - 2019-12-02
|
||||
|
||||
* Re-export `test` attribute macros
|
||||
|
||||
|
||||
## [0.3.0-alpha.1] - 2019-11-22
|
||||
|
||||
* Migrate to std::future
|
||||
|
||||
## [0.2.0] - 2019-10-14
|
||||
|
||||
* Upgrade actix-server and actix-server-config deps
|
||||
|
||||
|
||||
## [0.1.0] - 2019-09-25
|
||||
|
||||
* Initial impl
|
@@ -1,27 +0,0 @@
|
||||
[package]
|
||||
name = "actix-testing"
|
||||
version = "1.0.1"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix testing utils"
|
||||
keywords = ["network", "framework", "async", "futures"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-net.git"
|
||||
documentation = "https://docs.rs/actix-testing/"
|
||||
categories = ["network-programming", "asynchronous"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
workspace = ".."
|
||||
readme = "README.md"
|
||||
|
||||
[lib]
|
||||
name = "actix_testing"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
actix-rt = "1.0.0"
|
||||
actix-macros = "0.1.0"
|
||||
actix-server = "1.0.0"
|
||||
actix-service = "1.0.0"
|
||||
|
||||
log = "0.4"
|
||||
socket2 = "0.3"
|
@@ -1 +0,0 @@
|
||||
../LICENSE-APACHE
|
@@ -1 +0,0 @@
|
||||
../LICENSE-MIT
|
@@ -1,9 +0,0 @@
|
||||
# Actix test utilities [](https://crates.io/crates/actix-testint) [](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
## Documentation & community resources
|
||||
|
||||
* [User Guide](https://actix.rs/docs/)
|
||||
* [API Documentation](https://docs.rs/actix-testing/)
|
||||
* [Chat on gitter](https://gitter.im/actix/actix)
|
||||
* Cargo package: [actix-http-test](https://crates.io/crates/actix-testing)
|
||||
* Minimum supported Rust version: 1.37 or later
|
@@ -1,159 +0,0 @@
|
||||
//! Various helpers for Actix applications to use during testing.
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![allow(clippy::type_complexity, clippy::needless_doctest_main)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
|
||||
use std::sync::mpsc;
|
||||
use std::{net, thread};
|
||||
|
||||
use actix_rt::{net::TcpStream, System};
|
||||
use actix_server::{Server, ServerBuilder, ServiceFactory};
|
||||
use socket2::{Domain, Protocol, Socket, Type};
|
||||
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
pub use actix_macros::test;
|
||||
|
||||
/// The `TestServer` type.
|
||||
///
|
||||
/// `TestServer` is very simple test server that simplify process of writing
|
||||
/// integration tests for actix-net applications.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_service::fn_service;
|
||||
/// use actix_testing::TestServer;
|
||||
///
|
||||
/// #[actix_rt::main]
|
||||
/// async fn main() {
|
||||
/// let srv = TestServer::with(|| fn_service(
|
||||
/// |sock| async move {
|
||||
/// println!("New connection: {:?}", sock);
|
||||
/// Ok::<_, ()>(())
|
||||
/// }
|
||||
/// ));
|
||||
///
|
||||
/// println!("SOCKET: {:?}", srv.connect());
|
||||
/// }
|
||||
/// ```
|
||||
pub struct TestServer;
|
||||
|
||||
/// Test server runtime
|
||||
pub struct TestServerRuntime {
|
||||
addr: net::SocketAddr,
|
||||
host: String,
|
||||
port: u16,
|
||||
system: System,
|
||||
}
|
||||
|
||||
impl TestServer {
|
||||
/// Start new server with server builder
|
||||
pub fn start<F>(mut factory: F) -> TestServerRuntime
|
||||
where
|
||||
F: FnMut(ServerBuilder) -> ServerBuilder + Send + 'static,
|
||||
{
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
// run server in separate thread
|
||||
thread::spawn(move || {
|
||||
let sys = System::new("actix-test-server");
|
||||
factory(Server::build())
|
||||
.workers(1)
|
||||
.disable_signals()
|
||||
.start();
|
||||
|
||||
tx.send(System::current()).unwrap();
|
||||
sys.run()
|
||||
});
|
||||
let system = rx.recv().unwrap();
|
||||
|
||||
TestServerRuntime {
|
||||
system,
|
||||
addr: "127.0.0.1:0".parse().unwrap(),
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Start new test server with application factory
|
||||
pub fn with<F: ServiceFactory<TcpStream>>(factory: F) -> TestServerRuntime {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
// run server in separate thread
|
||||
thread::spawn(move || {
|
||||
let mut sys = System::new("actix-test-server");
|
||||
let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let local_addr = tcp.local_addr().unwrap();
|
||||
|
||||
sys.block_on(async {
|
||||
Server::build()
|
||||
.listen("test", tcp, factory)
|
||||
.unwrap()
|
||||
.workers(1)
|
||||
.disable_signals()
|
||||
.start();
|
||||
});
|
||||
|
||||
tx.send((System::current(), local_addr)).unwrap();
|
||||
sys.run()
|
||||
});
|
||||
|
||||
let (system, addr) = rx.recv().unwrap();
|
||||
|
||||
let host = format!("{}", addr.ip());
|
||||
let port = addr.port();
|
||||
|
||||
TestServerRuntime {
|
||||
system,
|
||||
addr,
|
||||
host,
|
||||
port,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get first available unused local address
|
||||
pub fn unused_addr() -> net::SocketAddr {
|
||||
let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
|
||||
let socket =
|
||||
Socket::new(Domain::ipv4(), Type::stream(), Some(Protocol::tcp())).unwrap();
|
||||
socket.bind(&addr.into()).unwrap();
|
||||
socket.set_reuse_address(true).unwrap();
|
||||
let tcp = socket.into_tcp_listener();
|
||||
tcp.local_addr().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl TestServerRuntime {
|
||||
/// Test server host
|
||||
pub fn host(&self) -> &str {
|
||||
&self.host
|
||||
}
|
||||
|
||||
/// Test server port
|
||||
pub fn port(&self) -> u16 {
|
||||
self.port
|
||||
}
|
||||
|
||||
/// Get test server address
|
||||
pub fn addr(&self) -> net::SocketAddr {
|
||||
self.addr
|
||||
}
|
||||
|
||||
/// Stop http server
|
||||
fn stop(&mut self) {
|
||||
self.system.stop();
|
||||
}
|
||||
|
||||
/// Connect to server, return tokio TcpStream
|
||||
pub fn connect(&self) -> std::io::Result<TcpStream> {
|
||||
TcpStream::from_std(net::TcpStream::connect(self.addr)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TestServerRuntime {
|
||||
fn drop(&mut self) {
|
||||
self.stop()
|
||||
}
|
||||
}
|
@@ -1,49 +0,0 @@
|
||||
# Changes
|
||||
|
||||
## [0.3.3] - 2020-07-14
|
||||
|
||||
### Changed
|
||||
|
||||
* Update parking_lot to 0.11
|
||||
|
||||
## [0.3.2] - 2020-05-20
|
||||
|
||||
## Added
|
||||
|
||||
* Implement `std::error::Error` for `BlockingError` [#120]
|
||||
|
||||
[#120]: https://github.com/actix/actix-net/pull/120
|
||||
|
||||
## [0.3.1] - 2019-12-12
|
||||
|
||||
### Changed
|
||||
|
||||
* Update parking_lot to 0.10
|
||||
|
||||
## [0.3.0] - 2019-12-02
|
||||
|
||||
### Changed
|
||||
|
||||
* Expect `Result` type as a function return type
|
||||
|
||||
## [0.2.0] - 2019-11-21
|
||||
|
||||
### Changed
|
||||
|
||||
* Migrate to `std::future`
|
||||
|
||||
## [0.1.2] - 2019-08-05
|
||||
|
||||
### Changed
|
||||
|
||||
* Update `derive_more` to 0.15
|
||||
|
||||
* Update `parking_lot` to 0.9
|
||||
|
||||
## [0.1.1] - 2019-06-05
|
||||
|
||||
* Update parking_lot
|
||||
|
||||
## [0.1.0] - 2019-03-28
|
||||
|
||||
* Move threadpool to separate crate
|
@@ -1,27 +0,0 @@
|
||||
[package]
|
||||
name = "actix-threadpool"
|
||||
version = "0.3.3"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix thread pool for sync code"
|
||||
keywords = ["actix", "network", "framework", "async", "futures"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-net.git"
|
||||
documentation = "https://docs.rs/actix-threadpool/"
|
||||
categories = ["network-programming", "asynchronous"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
|
||||
edition = "2018"
|
||||
workspace = ".."
|
||||
|
||||
[lib]
|
||||
name = "actix_threadpool"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
derive_more = "0.99.2"
|
||||
futures-channel = "0.3.1"
|
||||
parking_lot = "0.11"
|
||||
lazy_static = "1.3"
|
||||
log = "0.4"
|
||||
num_cpus = "1.10"
|
||||
threadpool = "1.7"
|
@@ -1 +0,0 @@
|
||||
../LICENSE-APACHE
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user