mirror of
https://github.com/fafhrd91/actix-net
synced 2025-09-03 00:46:38 +02:00
Compare commits
4 Commits
macros-v0.
...
lets-encry
Author | SHA1 | Date | |
---|---|---|---|
|
bfa98627b4 | ||
|
2a26c87c36 | ||
|
e976758d92 | ||
|
e1ee3a1c32 |
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
|
@@ -1,25 +0,0 @@
|
||||
[alias]
|
||||
lint = "clippy --workspace --all-features --tests --examples --bins -- -Dclippy::todo"
|
||||
|
||||
ci-doctest = "test --workspace --all-features --doc --no-fail-fast -- --nocapture"
|
||||
|
||||
# just check the library (without dev deps)
|
||||
ci-check-min = "hack --workspace check --no-default-features"
|
||||
ci-check-lib = "hack --workspace --feature-powerset --exclude-features=io-uring check"
|
||||
ci-check-lib-linux = "hack --workspace --feature-powerset check"
|
||||
|
||||
# check everything
|
||||
ci-check = "hack --workspace --feature-powerset --exclude-features=io-uring check --tests --examples"
|
||||
ci-check-linux = "hack --workspace --feature-powerset check --tests --examples"
|
||||
|
||||
# tests avoiding io-uring feature
|
||||
ci-test = "hack test --workspace --exclude=actix-rt --exclude=actix-server --all-features --lib --tests --no-fail-fast -- --nocapture"
|
||||
ci-test-rt = " hack --feature-powerset --exclude-features=io-uring test --package=actix-rt --lib --tests --no-fail-fast -- --nocapture"
|
||||
ci-test-server = "hack --feature-powerset --exclude-features=io-uring test --package=actix-server --lib --tests --no-fail-fast -- --nocapture"
|
||||
|
||||
# test with io-uring feature
|
||||
ci-test-rt-linux = " hack --feature-powerset test --package=actix-rt --lib --tests --no-fail-fast -- --nocapture"
|
||||
ci-test-server-linux = "hack --feature-powerset test --package=actix-server --lib --tests --no-fail-fast -- --nocapture"
|
||||
|
||||
# test lower msrv
|
||||
ci-test-lower-msrv = "hack --workspace --exclude=actix-server --exclude=actix-tls --feature-powerset test --lib --tests --no-fail-fast -- --nocapture"
|
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 -->
|
209
.github/workflows/ci.yml
vendored
209
.github/workflows/ci.yml
vendored
@@ -1,209 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
push:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
- { name: Linux, os: ubuntu-latest, triple: x86_64-unknown-linux-gnu }
|
||||
- { name: macOS, os: macos-latest, triple: x86_64-apple-darwin }
|
||||
- { name: Windows, os: windows-latest, triple: x86_64-pc-windows-msvc }
|
||||
- { name: Windows (MinGW), os: windows-latest, triple: x86_64-pc-windows-gnu }
|
||||
- { name: Windows (32-bit), os: windows-latest, triple: i686-pc-windows-msvc }
|
||||
version:
|
||||
- 1.52.0 # MSRV for -server and -tls
|
||||
- stable
|
||||
- nightly
|
||||
|
||||
name: ${{ matrix.target.name }} / ${{ matrix.version }}
|
||||
runs-on: ${{ matrix.target.os }}
|
||||
|
||||
env:
|
||||
VCPKGRS_DYNAMIC: 1
|
||||
|
||||
steps:
|
||||
- name: Setup Routing
|
||||
if: matrix.target.os == 'macos-latest'
|
||||
run: sudo ifconfig lo0 alias 127.0.0.3
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
# install OpenSSL on Windows
|
||||
- name: Set vcpkg root
|
||||
if: matrix.target.triple == 'x86_64-pc-windows-msvc' || matrix.target.triple == 'i686-pc-windows-msvc'
|
||||
run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append
|
||||
- name: Install OpenSSL
|
||||
if: matrix.target.triple == 'x86_64-pc-windows-msvc'
|
||||
run: vcpkg install openssl:x64-windows
|
||||
- name: Install OpenSSL
|
||||
if: matrix.target.triple == 'i686-pc-windows-msvc'
|
||||
run: vcpkg install openssl:x86-windows
|
||||
|
||||
- name: Install ${{ matrix.version }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.version }}-${{ matrix.target.triple }}
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
# - name: Install MSYS2
|
||||
# if: matrix.target.triple == 'x86_64-pc-windows-gnu'
|
||||
# uses: msys2/setup-msys2@v2
|
||||
# - name: Install MinGW Packages
|
||||
# if: matrix.target.triple == 'x86_64-pc-windows-gnu'
|
||||
# run: |
|
||||
# msys2 -c 'pacman -Sy --noconfirm pacman'
|
||||
# msys2 -c 'pacman --noconfirm -S base-devel pkg-config'
|
||||
|
||||
# - name: Generate Cargo.lock
|
||||
# uses: actions-rs/cargo@v1
|
||||
# with: { command: generate-lockfile }
|
||||
# - name: Cache Dependencies
|
||||
# uses: Swatinem/rust-cache@v1.2.0
|
||||
|
||||
- name: Install cargo-hack
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: install
|
||||
args: cargo-hack
|
||||
|
||||
- name: check lib
|
||||
if: >
|
||||
matrix.target.os != 'ubuntu-latest'
|
||||
&& matrix.target.triple != 'x86_64-pc-windows-gnu'
|
||||
uses: actions-rs/cargo@v1
|
||||
with: { command: ci-check-lib }
|
||||
- name: check lib
|
||||
if: matrix.target.os == 'ubuntu-latest'
|
||||
uses: actions-rs/cargo@v1
|
||||
with: { command: ci-check-lib-linux }
|
||||
- name: check lib
|
||||
if: matrix.target.triple == 'x86_64-pc-windows-gnu'
|
||||
uses: actions-rs/cargo@v1
|
||||
with: { command: ci-check-min }
|
||||
|
||||
- name: check full
|
||||
# TODO: compile OpenSSL and run tests on MinGW
|
||||
if: >
|
||||
matrix.target.os != 'ubuntu-latest'
|
||||
&& matrix.target.triple != 'x86_64-pc-windows-gnu'
|
||||
uses: actions-rs/cargo@v1
|
||||
with: { command: ci-check }
|
||||
- name: check all
|
||||
if: matrix.target.os == 'ubuntu-latest'
|
||||
uses: actions-rs/cargo@v1
|
||||
with: { command: ci-check-linux }
|
||||
|
||||
- name: tests
|
||||
if: >
|
||||
matrix.target.os != 'ubuntu-latest'
|
||||
&& matrix.target.triple != 'x86_64-pc-windows-gnu'
|
||||
run: |
|
||||
cargo ci-test
|
||||
cargo ci-test-rt
|
||||
cargo ci-test-server
|
||||
- name: tests
|
||||
if: matrix.target.os == 'ubuntu-latest'
|
||||
run: |
|
||||
cargo ci-test
|
||||
cargo ci-test-rt-linux
|
||||
cargo ci-test-server-linux
|
||||
|
||||
- name: Clear the cargo caches
|
||||
run: |
|
||||
cargo install cargo-cache --version 0.6.2 --no-default-features --features ci-autoclean
|
||||
cargo-cache
|
||||
|
||||
build_and_test_lower_msrv:
|
||||
name: Linux / 1.46 (lower MSRV)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install 1.46.0 # MSRV for all but -server and -tls
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.46.0-x86_64-unknown-linux-gnu
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Install cargo-hack
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: install
|
||||
args: cargo-hack
|
||||
|
||||
- name: tests
|
||||
run: cargo ci-test-lower-msrv
|
||||
|
||||
- name: Clear the cargo caches
|
||||
run: |
|
||||
cargo install cargo-cache --version 0.6.2 --no-default-features --features ci-autoclean
|
||||
cargo-cache
|
||||
|
||||
coverage:
|
||||
name: coverage
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install Rust (nightly)
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable-x86_64-unknown-linux-gnu
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Generate Cargo.lock
|
||||
uses: actions-rs/cargo@v1
|
||||
with: { command: generate-lockfile }
|
||||
- name: Cache Dependencies
|
||||
uses: Swatinem/rust-cache@v1.3.0
|
||||
|
||||
- name: Generate coverage file
|
||||
if: github.ref == 'refs/heads/master'
|
||||
run: |
|
||||
cargo install cargo-tarpaulin
|
||||
cargo tarpaulin --out Xml --verbose
|
||||
- name: Upload to Codecov
|
||||
if: github.ref == 'refs/heads/master'
|
||||
uses: codecov/codecov-action@v1
|
||||
with: { file: cobertura.xml }
|
||||
|
||||
rustdoc:
|
||||
name: rustdoc
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install Rust (nightly)
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly-x86_64-unknown-linux-gnu
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Generate Cargo.lock
|
||||
uses: actions-rs/cargo@v1
|
||||
with: { command: generate-lockfile }
|
||||
- name: Cache Dependencies
|
||||
uses: Swatinem/rust-cache@v1.3.0
|
||||
|
||||
- name: Install cargo-hack
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: install
|
||||
args: cargo-hack
|
||||
|
||||
- name: doc tests
|
||||
uses: actions-rs/cargo@v1
|
||||
timeout-minutes: 40
|
||||
with: { command: ci-doctest }
|
42
.github/workflows/clippy-fmt.yml
vendored
42
.github/workflows/clippy-fmt.yml
vendored
@@ -1,42 +0,0 @@
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
fmt:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
components: rustfmt
|
||||
override: true
|
||||
- name: Rustfmt Check
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
components: clippy
|
||||
override: true
|
||||
- name: Clippy Check
|
||||
uses: actions-rs/clippy-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --workspace --all-features --tests --examples --bins -- -Dclippy::todo
|
35
.github/workflows/upload-doc.yml
vendored
35
.github/workflows/upload-doc.yml
vendored
@@ -1,35 +0,0 @@
|
||||
name: Upload documentation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly-x86_64-unknown-linux-gnu
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Build Docs
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: doc
|
||||
args: --workspace --all-features --no-deps
|
||||
|
||||
- name: Tweak HTML
|
||||
run: echo '<meta http-equiv="refresh" content="0;url=actix_server/index.html">' > target/doc/index.html
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
uses: JamesIves/github-pages-deploy-action@3.7.1
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BRANCH: gh-pages
|
||||
FOLDER: target/doc
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -12,5 +12,3 @@ guide/build/
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
.idea
|
||||
|
49
.travis.yml
Normal file
49
.travis.yml
Normal file
@@ -0,0 +1,49 @@
|
||||
language: rust
|
||||
sudo: required
|
||||
dist: trusty
|
||||
|
||||
cache:
|
||||
cargo: true
|
||||
apt: true
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- rust: stable
|
||||
- rust: beta
|
||||
- rust: nightly-2019-06-15
|
||||
allow_failures:
|
||||
- rust: nightly-2019-06-15
|
||||
|
||||
env:
|
||||
global:
|
||||
- RUSTFLAGS="-C link-dead-code"
|
||||
- OPENSSL_VERSION=openssl-1.0.2
|
||||
|
||||
before_install:
|
||||
- sudo add-apt-repository -y ppa:0k53d-karl-f830m/openssl
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install -y openssl libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev
|
||||
|
||||
before_cache: |
|
||||
if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-06-15" ]]; then
|
||||
RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install --version 0.6.11 cargo-tarpaulin
|
||||
fi
|
||||
|
||||
# Add clippy
|
||||
before_script:
|
||||
- export PATH=$PATH:~/.cargo/bin
|
||||
|
||||
script:
|
||||
- |
|
||||
if [[ "$TRAVIS_RUST_VERSION" != "nightly-2019-06-15" ]]; then
|
||||
cargo clean
|
||||
cargo test --all --all-features -- --nocapture
|
||||
fi
|
||||
|
||||
after_success:
|
||||
- |
|
||||
if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-06-15" ]]; then
|
||||
taskset -c 0 cargo tarpaulin --all --all-features --out Xml
|
||||
echo "Uploaded code coverage"
|
||||
bash <(curl -s https://codecov.io/bash)
|
||||
fi
|
68
CHANGES.md
Normal file
68
CHANGES.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# Changes
|
||||
|
||||
## [0.3.0] - xxx
|
||||
|
||||
* Split `Service` trait to separate crate
|
||||
|
||||
* Use new `Service<Request>` trait
|
||||
|
||||
|
||||
## [0.2.4] - 2018-11-21
|
||||
|
||||
### Added
|
||||
|
||||
* Allow to skip name resolution stage in Connector
|
||||
|
||||
|
||||
## [0.2.3] - 2018-11-17
|
||||
|
||||
### Added
|
||||
|
||||
* Framed::is_write_buf_empty() checks if write buffer is flushed
|
||||
|
||||
## [0.2.2] - 2018-11-14
|
||||
|
||||
### Added
|
||||
|
||||
* Add low/high caps to Framed
|
||||
|
||||
### Changed
|
||||
|
||||
* Refactor Connector and Resolver services
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix wrong service to socket binding
|
||||
|
||||
|
||||
## [0.2.0] - 2018-11-08
|
||||
|
||||
### Added
|
||||
|
||||
* Timeout service
|
||||
|
||||
* Added ServiceConfig and ServiceRuntime for server service configuration
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
* Connector has been refactored
|
||||
|
||||
* timer and LowResTimer renamed to time and LowResTime
|
||||
|
||||
* Refactored `Server::configure()` method
|
||||
|
||||
|
||||
## [0.1.1] - 2018-10-10
|
||||
|
||||
### Changed
|
||||
|
||||
- Set actix min version - 0.7.5
|
||||
|
||||
- Set trust-dns min version
|
||||
|
||||
|
||||
## [0.1.0] - 2018-10-08
|
||||
|
||||
* Initial impl
|
@@ -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]
|
||||
|
58
Cargo.toml
58
Cargo.toml
@@ -1,32 +1,42 @@
|
||||
[package]
|
||||
name = "actix-net"
|
||||
version = "0.3.0"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix net - framework for the composable network services for Rust"
|
||||
readme = "README.md"
|
||||
keywords = ["network", "framework", "async", "futures"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-net.git"
|
||||
documentation = "https://docs.rs/actix-net/"
|
||||
categories = ["network-programming", "asynchronous"]
|
||||
license = "MIT/Apache-2.0"
|
||||
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
|
||||
edition = "2018"
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"actix-codec",
|
||||
"actix-macros",
|
||||
"actix-connect",
|
||||
"actix-lets-encrypt",
|
||||
"actix-rt",
|
||||
"actix-server",
|
||||
"actix-service",
|
||||
"actix-tls",
|
||||
"actix-tracing",
|
||||
"actix-server",
|
||||
"actix-server-config",
|
||||
"actix-test-server",
|
||||
"actix-threadpool",
|
||||
"actix-tower",
|
||||
"actix-ioframe",
|
||||
"actix-utils",
|
||||
"bytestring",
|
||||
"local-channel",
|
||||
"local-waker",
|
||||
"router",
|
||||
]
|
||||
|
||||
[patch.crates-io]
|
||||
actix-codec = { path = "actix-codec" }
|
||||
actix-macros = { path = "actix-macros" }
|
||||
actix-rt = { path = "actix-rt" }
|
||||
actix-server = { path = "actix-server" }
|
||||
actix-service = { path = "actix-service" }
|
||||
actix-tls = { path = "actix-tls" }
|
||||
actix-tracing = { path = "actix-tracing" }
|
||||
actix-utils = { path = "actix-utils" }
|
||||
bytestring = { path = "bytestring" }
|
||||
local-channel = { path = "local-channel" }
|
||||
local-waker = { path = "local-waker" }
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
opt-level = 3
|
||||
codegen-units = 1
|
||||
[dev-dependencies]
|
||||
actix-service = "0.4.0"
|
||||
actix-codec = "0.1.1"
|
||||
actix-rt = "0.2.0"
|
||||
actix-server = { version="0.5.0", features=["ssl"] }
|
||||
env_logger = "0.6"
|
||||
futures = "0.1.25"
|
||||
openssl = "0.10"
|
||||
tokio-tcp = "0.1"
|
||||
tokio-openssl = "0.3"
|
||||
|
@@ -186,7 +186,7 @@
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2017-NOW Actix Team
|
||||
Copyright 2017-NOW Nikolay Kim
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2017-NOW Actix Team
|
||||
Copyright (c) 2017 Nikolay Kim
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
|
69
README.md
69
README.md
@@ -1,26 +1,60 @@
|
||||
# Actix Net
|
||||
# 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)
|
||||
|
||||
> A collection of lower-level libraries for composable network services.
|
||||
Actix net - framework for composable network services
|
||||
|
||||

|
||||
[](https://codecov.io/gh/actix/actix-net)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
## Documentation & community resources
|
||||
|
||||
## 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)") |
|
||||
* [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.32 or later
|
||||
|
||||
## Example
|
||||
See `actix-server/examples` and `actix-tls/examples` for some basic examples.
|
||||
|
||||
### MSRV
|
||||
This repo's Minimum Supported Rust Version (MSRV) is 1.46.0.
|
||||
```rust
|
||||
fn main() -> io::Result<()> {
|
||||
// load ssl keys
|
||||
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
|
||||
builder.set_private_key_file("./examples/key.pem", SslFiletype::PEM).unwrap();
|
||||
builder.set_certificate_chain_file("./examples/cert.pem").unwrap();
|
||||
let acceptor = builder.build();
|
||||
|
||||
let num = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
// bind socket address and start workers. By default server uses number of
|
||||
// available logical cpu as threads count. actix net start separate
|
||||
// instances of service pipeline in each worker.
|
||||
Server::build()
|
||||
.bind(
|
||||
// configure service pipeline
|
||||
"basic", "0.0.0.0:8443",
|
||||
move || {
|
||||
let num = num.clone();
|
||||
let acceptor = acceptor.clone();
|
||||
|
||||
// service for converting incoming TcpStream to a SslStream<TcpStream>
|
||||
fn_service(move |stream: Io<tokio_tcp::TcpStream>| {
|
||||
SslAcceptorExt::accept_async(&acceptor, stream.into_parts().0)
|
||||
.map_err(|e| println!("Openssl error: {}", e))
|
||||
})
|
||||
// .and_then() combinator uses other service to convert incoming `Request` to a
|
||||
// `Response` and then uses that response as an input for next
|
||||
// service. in this case, on success we use `logger` service
|
||||
.and_then(fn_service(logger))
|
||||
// Next service counts number of connections
|
||||
.and_then(move |_| {
|
||||
let num = num.fetch_add(1, Ordering::Relaxed);
|
||||
println!("got ssl connection {:?}", num);
|
||||
future::ok(())
|
||||
})
|
||||
},
|
||||
)?
|
||||
.run()
|
||||
}
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under either of
|
||||
|
||||
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0))
|
||||
@@ -30,5 +64,6 @@ at your option.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
Contribution to the actix-net repo is organized under the terms of the Contributor Covenant.
|
||||
The Actix team promises to intervene to uphold that code of conduct.
|
||||
Contribution to the actix-net crate is organized under the terms of the
|
||||
Contributor Covenant, the maintainer of actix-net, @fafhrd91, promises to
|
||||
intervene to uphold that code of conduct.
|
||||
|
@@ -1,63 +1,15 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
## [0.1.2] - 2019-03-27
|
||||
|
||||
|
||||
## 0.4.0 - 2021-04-20
|
||||
* No significant changes since v0.4.0-beta.1.
|
||||
|
||||
|
||||
## 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
|
||||
## [0.1.1] - 2019-03-06
|
||||
|
||||
* Added `FramedParts::with_read_buffer()` method.
|
||||
|
||||
|
||||
## 0.1.0 - 2018-12-09
|
||||
* Move codec to separate crate.
|
||||
## [0.1.0] - 2018-12-09
|
||||
|
||||
* Move codec to separate crate
|
||||
|
@@ -1,24 +1,25 @@
|
||||
[package]
|
||||
name = "actix-codec"
|
||||
version = "0.4.0"
|
||||
version = "0.1.2"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Codec utilities for working with framed protocols"
|
||||
description = "Utilities for encoding and decoding frames"
|
||||
keywords = ["network", "framework", "async", "futures"]
|
||||
repository = "https://github.com/actix/actix-net"
|
||||
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"
|
||||
license = "MIT/Apache-2.0"
|
||||
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
|
||||
edition = "2018"
|
||||
workspace = ".."
|
||||
|
||||
[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.5.1"
|
||||
tokio-util = { version = "0.6", features = ["codec", "io"] }
|
||||
bytes = "0.4.12"
|
||||
futures = "0.1.24"
|
||||
tokio-io = "0.1.12"
|
||||
tokio-codec = "0.1.1"
|
||||
log = "0.4"
|
@@ -1,7 +1,7 @@
|
||||
use bytes::{Buf, Bytes, BytesMut};
|
||||
use std::io;
|
||||
|
||||
use super::{Decoder, Encoder};
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use tokio_codec::{Decoder, Encoder};
|
||||
|
||||
/// Bytes codec.
|
||||
///
|
||||
@@ -9,12 +9,12 @@ use super::{Decoder, Encoder};
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct BytesCodec;
|
||||
|
||||
impl Encoder<Bytes> for BytesCodec {
|
||||
impl Encoder for BytesCodec {
|
||||
type Item = Bytes;
|
||||
type Error = io::Error;
|
||||
|
||||
#[inline]
|
||||
fn encode(&mut self, item: Bytes, dst: &mut BytesMut) -> Result<(), Self::Error> {
|
||||
dst.extend_from_slice(item.chunk());
|
||||
dst.extend_from_slice(&item[..]);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ impl Decoder for BytesCodec {
|
||||
if src.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(src.split()))
|
||||
Ok(Some(src.take()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,282 +1,223 @@
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use std::{fmt, io};
|
||||
#![allow(deprecated)]
|
||||
|
||||
use bytes::{Buf, BytesMut};
|
||||
use futures_core::{ready, Stream};
|
||||
use futures_sink::Sink;
|
||||
use std::fmt;
|
||||
use std::io::{self, Read, Write};
|
||||
|
||||
use crate::{AsyncRead, AsyncWrite, Decoder, Encoder};
|
||||
use bytes::BytesMut;
|
||||
use futures::{Poll, Sink, StartSend, Stream};
|
||||
use tokio_codec::{Decoder, Encoder};
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
|
||||
use super::framed_read::{framed_read2, framed_read2_with_buffer, FramedRead2};
|
||||
use super::framed_write::{framed_write2, framed_write2_with_buffer, FramedWrite2};
|
||||
|
||||
/// Low-water mark
|
||||
const LW: usize = 1024;
|
||||
/// High-water mark
|
||||
const HW: usize = 8 * 1024;
|
||||
|
||||
bitflags::bitflags! {
|
||||
struct Flags: u8 {
|
||||
const EOF = 0b0001;
|
||||
const READABLE = 0b0010;
|
||||
}
|
||||
/// A unified `Stream` and `Sink` interface to an underlying I/O object, using
|
||||
/// the `Encoder` and `Decoder` traits to encode and decode frames.
|
||||
///
|
||||
/// You can create a `Framed` instance by using the `AsyncRead::framed` adapter.
|
||||
pub struct Framed<T, U> {
|
||||
inner: FramedRead2<FramedWrite2<Fuse<T, U>>>,
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
pub struct Fuse<T, U>(pub T, pub U);
|
||||
|
||||
impl<T, U> Framed<T, U>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
U: Decoder,
|
||||
U: Decoder + Encoder,
|
||||
{
|
||||
/// 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> {
|
||||
/// Provides a `Stream` and `Sink` interface for reading and writing to this
|
||||
/// `Io` object, using `Decode` and `Encode` to read and write the raw data.
|
||||
///
|
||||
/// Raw I/O objects work with byte sequences, but higher-level code usually
|
||||
/// wants to batch these into meaningful chunks, called "frames". This
|
||||
/// method layers framing on top of an I/O object, by using the `Codec`
|
||||
/// traits to handle encoding and decoding of messages frames. Note that
|
||||
/// the incoming and outgoing frame types may be distinct.
|
||||
///
|
||||
/// This function returns a *single* object that is both `Stream` and
|
||||
/// `Sink`; grouping this into a single object is often useful for layering
|
||||
/// things like gzip or TLS, which require both read and write access to the
|
||||
/// underlying object.
|
||||
///
|
||||
/// If you want to work more directly with the streams and sink, consider
|
||||
/// calling `split` on the `Framed` returned by this method, which will
|
||||
/// break them into separate objects, allowing them to interact more easily.
|
||||
pub fn new(inner: T, codec: U) -> Framed<T, U> {
|
||||
Framed {
|
||||
io,
|
||||
codec,
|
||||
flags: Flags::empty(),
|
||||
read_buf: BytesMut::with_capacity(HW),
|
||||
write_buf: BytesMut::with_capacity(HW),
|
||||
inner: framed_read2(framed_write2(Fuse(inner, codec), LW, HW)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as `Framed::new()` with ability to specify write buffer low/high capacity watermarks.
|
||||
pub fn new_with_caps(inner: T, codec: U, lw: usize, hw: usize) -> Framed<T, U> {
|
||||
debug_assert!((lw < hw) && hw != 0);
|
||||
Framed {
|
||||
inner: framed_read2(framed_write2(Fuse(inner, codec), lw, hw)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Force send item
|
||||
pub fn force_send(
|
||||
&mut self,
|
||||
item: <U as Encoder>::Item,
|
||||
) -> Result<(), <U as Encoder>::Error> {
|
||||
self.inner.get_mut().force_send(item)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Framed<T, U> {
|
||||
/// Provides a `Stream` and `Sink` interface for reading and writing to this
|
||||
/// `Io` object, using `Decode` and `Encode` to read and write the raw data.
|
||||
///
|
||||
/// Raw I/O objects work with byte sequences, but higher-level code usually
|
||||
/// wants to batch these into meaningful chunks, called "frames". This
|
||||
/// method layers framing on top of an I/O object, by using the `Codec`
|
||||
/// traits to handle encoding and decoding of messages frames. Note that
|
||||
/// the incoming and outgoing frame types may be distinct.
|
||||
///
|
||||
/// This function returns a *single* object that is both `Stream` and
|
||||
/// `Sink`; grouping this into a single object is often useful for layering
|
||||
/// things like gzip or TLS, which require both read and write access to the
|
||||
/// underlying object.
|
||||
///
|
||||
/// This objects takes a stream and a readbuffer and a writebuffer. These
|
||||
/// field can be obtained from an existing `Framed` with the
|
||||
/// `into_parts` method.
|
||||
///
|
||||
/// If you want to work more directly with the streams and sink, consider
|
||||
/// calling `split` on the `Framed` returned by this method, which will
|
||||
/// break them into separate objects, allowing them to interact more easily.
|
||||
pub fn from_parts(parts: FramedParts<T, U>) -> Framed<T, U> {
|
||||
Framed {
|
||||
inner: framed_read2_with_buffer(
|
||||
framed_write2_with_buffer(
|
||||
Fuse(parts.io, parts.codec),
|
||||
parts.write_buf,
|
||||
parts.write_buf_lw,
|
||||
parts.write_buf_hw,
|
||||
),
|
||||
parts.read_buf,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying codec.
|
||||
pub fn codec_ref(&self) -> &U {
|
||||
&self.codec
|
||||
pub fn get_codec(&self) -> &U {
|
||||
&self.inner.get_ref().get_ref().1
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying codec.
|
||||
pub fn codec_mut(&mut self) -> &mut U {
|
||||
&mut self.codec
|
||||
pub fn get_codec_mut(&mut self) -> &mut U {
|
||||
&mut self.inner.get_mut().get_mut().1
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying I/O stream wrapped by `Frame`.
|
||||
/// 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
|
||||
/// Note that care should be taken to not tamper with the underlying stream
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
pub fn get_ref(&self) -> &T {
|
||||
&self.inner.get_ref().get_ref().0
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying I/O stream.
|
||||
/// Returns a mutable reference to the underlying I/O stream wrapped by
|
||||
/// `Frame`.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying stream 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()
|
||||
/// Note that care should be taken to not tamper with the underlying stream
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
pub fn get_mut(&mut self) -> &mut T {
|
||||
&mut self.inner.get_mut().get_mut().0
|
||||
}
|
||||
|
||||
/// Check if write buffer is empty.
|
||||
pub fn is_write_buf_empty(&self) -> bool {
|
||||
self.write_buf.is_empty()
|
||||
self.inner.get_ref().is_empty()
|
||||
}
|
||||
|
||||
/// Check if write buffer is full.
|
||||
pub fn is_write_buf_full(&self) -> bool {
|
||||
self.write_buf.len() >= HW
|
||||
self.inner.get_ref().is_full()
|
||||
}
|
||||
|
||||
/// Check if framed is able to write more data.
|
||||
/// Consumes the `Frame`, returning its underlying I/O stream.
|
||||
///
|
||||
/// `Framed` object considers ready if there is free space in write buffer.
|
||||
pub fn is_write_ready(&self) -> bool {
|
||||
self.write_buf.len() < HW
|
||||
/// Note that care should be taken to not tamper with the underlying stream
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
pub fn into_inner(self) -> T {
|
||||
self.inner.into_inner().into_inner().0
|
||||
}
|
||||
|
||||
/// Consume the `Frame`, returning `Frame` with different codec.
|
||||
pub fn replace_codec<U2>(self, codec: U2) -> Framed<T, U2> {
|
||||
pub fn into_framed<U2>(self, codec: U2) -> Framed<T, U2> {
|
||||
let (inner, read_buf) = self.inner.into_parts();
|
||||
let (inner, write_buf, lw, hw) = inner.into_parts();
|
||||
|
||||
Framed {
|
||||
codec,
|
||||
io: self.io,
|
||||
flags: self.flags,
|
||||
read_buf: self.read_buf,
|
||||
write_buf: self.write_buf,
|
||||
inner: framed_read2_with_buffer(
|
||||
framed_write2_with_buffer(Fuse(inner.0, codec), write_buf, lw, hw),
|
||||
read_buf,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Consume the `Frame`, returning `Frame` with different io.
|
||||
pub fn into_map_io<F, T2>(self, f: F) -> Framed<T2, U>
|
||||
pub fn map_io<F, T2>(self, f: F) -> Framed<T2, U>
|
||||
where
|
||||
F: Fn(T) -> T2,
|
||||
{
|
||||
let (inner, read_buf) = self.inner.into_parts();
|
||||
let (inner, write_buf, lw, hw) = inner.into_parts();
|
||||
|
||||
Framed {
|
||||
io: f(self.io),
|
||||
codec: self.codec,
|
||||
flags: self.flags,
|
||||
read_buf: self.read_buf,
|
||||
write_buf: self.write_buf,
|
||||
inner: framed_read2_with_buffer(
|
||||
framed_write2_with_buffer(Fuse(f(inner.0), inner.1), write_buf, lw, hw),
|
||||
read_buf,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Consume the `Frame`, returning `Frame` with different codec.
|
||||
pub fn into_map_codec<F, U2>(self, f: F) -> Framed<T, U2>
|
||||
pub fn map_codec<F, U2>(self, f: F) -> Framed<T, U2>
|
||||
where
|
||||
F: Fn(U) -> U2,
|
||||
{
|
||||
let (inner, read_buf) = self.inner.into_parts();
|
||||
let (inner, write_buf, lw, hw) = inner.into_parts();
|
||||
|
||||
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 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(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(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);
|
||||
inner: framed_read2_with_buffer(
|
||||
framed_write2_with_buffer(Fuse(inner.0, f(inner.1)), write_buf, lw, hw),
|
||||
read_buf,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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");
|
||||
/// 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> {
|
||||
let (inner, read_buf) = self.inner.into_parts();
|
||||
let (inner, write_buf, write_buf_lw, write_buf_hw) = inner.into_parts();
|
||||
|
||||
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);
|
||||
FramedParts {
|
||||
io: inner.0,
|
||||
codec: inner.1,
|
||||
read_buf,
|
||||
write_buf,
|
||||
write_buf_lw,
|
||||
write_buf_hw,
|
||||
_priv: (),
|
||||
}
|
||||
|
||||
// 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(()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,39 +226,36 @@ where
|
||||
T: AsyncRead,
|
||||
U: Decoder,
|
||||
{
|
||||
type Item = Result<U::Item, U::Error>;
|
||||
type Item = U::Item;
|
||||
type Error = U::Error;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
self.next_item(cx)
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
self.inner.poll()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U, I> Sink<I> for Framed<T, U>
|
||||
impl<T, U> Sink for Framed<T, U>
|
||||
where
|
||||
T: AsyncWrite,
|
||||
U: Encoder<I>,
|
||||
U: Encoder,
|
||||
U::Error: From<io::Error>,
|
||||
{
|
||||
type Error = U::Error;
|
||||
type SinkItem = U::Item;
|
||||
type SinkError = 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(
|
||||
&mut self,
|
||||
item: Self::SinkItem,
|
||||
) -> StartSend<Self::SinkItem, Self::SinkError> {
|
||||
self.inner.get_mut().start_send(item)
|
||||
}
|
||||
|
||||
fn start_send(self: Pin<&mut Self>, item: I) -> Result<(), Self::Error> {
|
||||
self.write(item)
|
||||
fn poll_complete(&mut self) -> Poll<(), Self::SinkError> {
|
||||
self.inner.get_mut().poll_complete()
|
||||
}
|
||||
|
||||
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)
|
||||
fn close(&mut self) -> Poll<(), Self::SinkError> {
|
||||
self.inner.get_mut().close()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,57 +264,75 @@ where
|
||||
T: fmt::Debug,
|
||||
U: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("Framed")
|
||||
.field("io", &self.io)
|
||||
.field("codec", &self.codec)
|
||||
.field("io", &self.inner.get_ref().get_ref().0)
|
||||
.field("codec", &self.inner.get_ref().get_ref().1)
|
||||
.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,
|
||||
}
|
||||
// ===== impl Fuse =====
|
||||
|
||||
impl<T: Read, U> Read for Fuse<T, U> {
|
||||
fn read(&mut self, dst: &mut [u8]) -> io::Result<usize> {
|
||||
self.0.read(dst)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsyncRead, U> AsyncRead for Fuse<T, U> {
|
||||
unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool {
|
||||
self.0.prepare_uninitialized_buffer(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Write, U> Write for Fuse<T, U> {
|
||||
fn write(&mut self, src: &[u8]) -> io::Result<usize> {
|
||||
self.0.write(src)
|
||||
}
|
||||
|
||||
/// 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,
|
||||
}
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.0.flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsyncWrite, U> AsyncWrite for Fuse<T, U> {
|
||||
fn shutdown(&mut self) -> Poll<(), io::Error> {
|
||||
self.0.shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U: Decoder> Decoder for Fuse<T, U> {
|
||||
type Item = U::Item;
|
||||
type Error = U::Error;
|
||||
|
||||
fn decode(&mut self, buffer: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
||||
self.1.decode(buffer)
|
||||
}
|
||||
|
||||
fn decode_eof(&mut self, buffer: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
||||
self.1.decode_eof(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U: Encoder> Encoder for Fuse<T, U> {
|
||||
type Item = U::Item;
|
||||
type Error = U::Error;
|
||||
|
||||
fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> {
|
||||
self.1.encode(item, dst)
|
||||
}
|
||||
}
|
||||
|
||||
/// `FramedParts` contains an export of the data of a Framed transport.
|
||||
///
|
||||
/// It can be used to construct a new `Framed` with a different codec. It contains all current
|
||||
/// buffers and the inner 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.
|
||||
/// The inner transport used to read bytes to and write bytes to
|
||||
pub io: T,
|
||||
|
||||
/// The codec object.
|
||||
/// The codec
|
||||
pub codec: U,
|
||||
|
||||
/// The buffer with read but unprocessed data.
|
||||
@@ -385,29 +341,41 @@ pub struct FramedParts<T, U> {
|
||||
/// A buffer with unprocessed data which are not written yet.
|
||||
pub write_buf: BytesMut,
|
||||
|
||||
flags: Flags,
|
||||
/// A buffer low watermark capacity
|
||||
pub write_buf_lw: usize,
|
||||
|
||||
/// A buffer high watermark capacity
|
||||
pub write_buf_hw: usize,
|
||||
|
||||
/// This private field allows us to add additional fields in the future in a
|
||||
/// backwards compatible way.
|
||||
_priv: (),
|
||||
}
|
||||
|
||||
impl<T, U> FramedParts<T, U> {
|
||||
/// Creates a new default `FramedParts`.
|
||||
/// 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(),
|
||||
write_buf_lw: LW,
|
||||
write_buf_hw: HW,
|
||||
_priv: (),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new `FramedParts` with read buffer.
|
||||
/// 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(),
|
||||
write_buf_lw: LW,
|
||||
write_buf_hw: HW,
|
||||
_priv: (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
218
actix-codec/src/framed_read.rs
Normal file
218
actix-codec/src/framed_read.rs
Normal file
@@ -0,0 +1,218 @@
|
||||
use std::fmt;
|
||||
|
||||
use bytes::BytesMut;
|
||||
use futures::{try_ready, Async, Poll, Sink, StartSend, Stream};
|
||||
use log::trace;
|
||||
use tokio_codec::Decoder;
|
||||
use tokio_io::AsyncRead;
|
||||
|
||||
use super::framed::Fuse;
|
||||
|
||||
/// A `Stream` of messages decoded from an `AsyncRead`.
|
||||
pub struct FramedRead<T, D> {
|
||||
inner: FramedRead2<Fuse<T, D>>,
|
||||
}
|
||||
|
||||
pub struct FramedRead2<T> {
|
||||
inner: T,
|
||||
eof: bool,
|
||||
is_readable: bool,
|
||||
buffer: BytesMut,
|
||||
}
|
||||
|
||||
const INITIAL_CAPACITY: usize = 8 * 1024;
|
||||
|
||||
// ===== impl FramedRead =====
|
||||
|
||||
impl<T, D> FramedRead<T, D>
|
||||
where
|
||||
T: AsyncRead,
|
||||
D: Decoder,
|
||||
{
|
||||
/// Creates a new `FramedRead` with the given `decoder`.
|
||||
pub fn new(inner: T, decoder: D) -> FramedRead<T, D> {
|
||||
FramedRead {
|
||||
inner: framed_read2(Fuse(inner, decoder)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, D> FramedRead<T, D> {
|
||||
/// Returns a reference to the underlying I/O stream wrapped by
|
||||
/// `FramedRead`.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying stream
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
pub fn get_ref(&self) -> &T {
|
||||
&self.inner.inner.0
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying I/O stream wrapped by
|
||||
/// `FramedRead`.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying stream
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
pub fn get_mut(&mut self) -> &mut T {
|
||||
&mut self.inner.inner.0
|
||||
}
|
||||
|
||||
/// Consumes the `FramedRead`, returning its underlying I/O stream.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying stream
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
pub fn into_inner(self) -> T {
|
||||
self.inner.inner.0
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying decoder.
|
||||
pub fn decoder(&self) -> &D {
|
||||
&self.inner.inner.1
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying decoder.
|
||||
pub fn decoder_mut(&mut self) -> &mut D {
|
||||
&mut self.inner.inner.1
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, D> Stream for FramedRead<T, D>
|
||||
where
|
||||
T: AsyncRead,
|
||||
D: Decoder,
|
||||
{
|
||||
type Item = D::Item;
|
||||
type Error = D::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
self.inner.poll()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, D> Sink for FramedRead<T, D>
|
||||
where
|
||||
T: Sink,
|
||||
{
|
||||
type SinkItem = T::SinkItem;
|
||||
type SinkError = T::SinkError;
|
||||
|
||||
fn start_send(
|
||||
&mut self,
|
||||
item: Self::SinkItem,
|
||||
) -> StartSend<Self::SinkItem, Self::SinkError> {
|
||||
self.inner.inner.0.start_send(item)
|
||||
}
|
||||
|
||||
fn poll_complete(&mut self) -> Poll<(), Self::SinkError> {
|
||||
self.inner.inner.0.poll_complete()
|
||||
}
|
||||
|
||||
fn close(&mut self) -> Poll<(), Self::SinkError> {
|
||||
self.inner.inner.0.close()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, D> fmt::Debug for FramedRead<T, D>
|
||||
where
|
||||
T: fmt::Debug,
|
||||
D: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("FramedRead")
|
||||
.field("inner", &self.inner.inner.0)
|
||||
.field("decoder", &self.inner.inner.1)
|
||||
.field("eof", &self.inner.eof)
|
||||
.field("is_readable", &self.inner.is_readable)
|
||||
.field("buffer", &self.inner.buffer)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl FramedRead2 =====
|
||||
|
||||
pub fn framed_read2<T>(inner: T) -> FramedRead2<T> {
|
||||
FramedRead2 {
|
||||
inner,
|
||||
eof: false,
|
||||
is_readable: false,
|
||||
buffer: BytesMut::with_capacity(INITIAL_CAPACITY),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn framed_read2_with_buffer<T>(inner: T, mut buf: BytesMut) -> FramedRead2<T> {
|
||||
if buf.capacity() < INITIAL_CAPACITY {
|
||||
let bytes_to_reserve = INITIAL_CAPACITY - buf.capacity();
|
||||
buf.reserve(bytes_to_reserve);
|
||||
}
|
||||
FramedRead2 {
|
||||
inner,
|
||||
eof: false,
|
||||
is_readable: !buf.is_empty(),
|
||||
buffer: buf,
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FramedRead2<T> {
|
||||
pub fn get_ref(&self) -> &T {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> T {
|
||||
self.inner
|
||||
}
|
||||
|
||||
pub fn into_parts(self) -> (T, BytesMut) {
|
||||
(self.inner, self.buffer)
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self) -> &mut T {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Stream for FramedRead2<T>
|
||||
where
|
||||
T: AsyncRead + Decoder,
|
||||
{
|
||||
type Item = T::Item;
|
||||
type Error = T::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
loop {
|
||||
// Repeatedly call `decode` or `decode_eof` as long as it is
|
||||
// "readable". Readable is defined as not having returned `None`. If
|
||||
// the upstream has returned EOF, and the decoder is no longer
|
||||
// readable, it can be assumed that the decoder will never become
|
||||
// readable again, at which point the stream is terminated.
|
||||
if self.is_readable {
|
||||
if self.eof {
|
||||
let frame = self.inner.decode_eof(&mut self.buffer)?;
|
||||
return Ok(Async::Ready(frame));
|
||||
}
|
||||
|
||||
trace!("attempting to decode a frame");
|
||||
|
||||
if let Some(frame) = self.inner.decode(&mut self.buffer)? {
|
||||
trace!("frame decoded from buffer");
|
||||
return Ok(Async::Ready(Some(frame)));
|
||||
}
|
||||
|
||||
self.is_readable = false;
|
||||
}
|
||||
|
||||
assert!(!self.eof);
|
||||
|
||||
// Otherwise, try to read more data and try again. Make sure we've
|
||||
// got room for at least one byte to read to ensure that we don't
|
||||
// get a spurious 0 that looks like EOF
|
||||
self.buffer.reserve(1);
|
||||
if 0 == try_ready!(self.inner.read_buf(&mut self.buffer)) {
|
||||
self.eof = true;
|
||||
}
|
||||
|
||||
self.is_readable = true;
|
||||
}
|
||||
}
|
||||
}
|
303
actix-codec/src/framed_write.rs
Normal file
303
actix-codec/src/framed_write.rs
Normal file
@@ -0,0 +1,303 @@
|
||||
use std::fmt;
|
||||
use std::io::{self, Read};
|
||||
|
||||
use bytes::BytesMut;
|
||||
use futures::{try_ready, Async, AsyncSink, Poll, Sink, StartSend, Stream};
|
||||
use log::trace;
|
||||
use tokio_codec::{Decoder, Encoder};
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
|
||||
use super::framed::Fuse;
|
||||
|
||||
/// A `Sink` of frames encoded to an `AsyncWrite`.
|
||||
pub struct FramedWrite<T, E> {
|
||||
inner: FramedWrite2<Fuse<T, E>>,
|
||||
}
|
||||
|
||||
pub struct FramedWrite2<T> {
|
||||
inner: T,
|
||||
buffer: BytesMut,
|
||||
low_watermark: usize,
|
||||
high_watermark: usize,
|
||||
}
|
||||
|
||||
impl<T, E> FramedWrite<T, E>
|
||||
where
|
||||
T: AsyncWrite,
|
||||
E: Encoder,
|
||||
{
|
||||
/// Creates a new `FramedWrite` with the given `encoder`.
|
||||
pub fn new(inner: T, encoder: E, lw: usize, hw: usize) -> FramedWrite<T, E> {
|
||||
FramedWrite {
|
||||
inner: framed_write2(Fuse(inner, encoder), lw, hw),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> FramedWrite<T, E> {
|
||||
/// Returns a reference to the underlying I/O stream wrapped by
|
||||
/// `FramedWrite`.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying stream
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
pub fn get_ref(&self) -> &T {
|
||||
&self.inner.inner.0
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying I/O stream wrapped by
|
||||
/// `FramedWrite`.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying stream
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
pub fn get_mut(&mut self) -> &mut T {
|
||||
&mut self.inner.inner.0
|
||||
}
|
||||
|
||||
/// Consumes the `FramedWrite`, returning its underlying I/O stream.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying stream
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
pub fn into_inner(self) -> T {
|
||||
self.inner.inner.0
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying decoder.
|
||||
pub fn encoder(&self) -> &E {
|
||||
&self.inner.inner.1
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying decoder.
|
||||
pub fn encoder_mut(&mut self) -> &mut E {
|
||||
&mut self.inner.inner.1
|
||||
}
|
||||
|
||||
/// Check if write buffer is full
|
||||
pub fn is_full(&self) -> bool {
|
||||
self.inner.is_full()
|
||||
}
|
||||
|
||||
/// Check if write buffer is empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.inner.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> FramedWrite<T, E>
|
||||
where
|
||||
E: Encoder,
|
||||
{
|
||||
/// Force send item
|
||||
pub fn force_send(&mut self, item: E::Item) -> Result<(), E::Error> {
|
||||
self.inner.force_send(item)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> Sink for FramedWrite<T, E>
|
||||
where
|
||||
T: AsyncWrite,
|
||||
E: Encoder,
|
||||
{
|
||||
type SinkItem = E::Item;
|
||||
type SinkError = E::Error;
|
||||
|
||||
fn start_send(&mut self, item: E::Item) -> StartSend<E::Item, E::Error> {
|
||||
self.inner.start_send(item)
|
||||
}
|
||||
|
||||
fn poll_complete(&mut self) -> Poll<(), Self::SinkError> {
|
||||
self.inner.poll_complete()
|
||||
}
|
||||
|
||||
fn close(&mut self) -> Poll<(), Self::SinkError> {
|
||||
Ok(self.inner.close()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, D> Stream for FramedWrite<T, D>
|
||||
where
|
||||
T: Stream,
|
||||
{
|
||||
type Item = T::Item;
|
||||
type Error = T::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
self.inner.inner.0.poll()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> fmt::Debug for FramedWrite<T, U>
|
||||
where
|
||||
T: fmt::Debug,
|
||||
U: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("FramedWrite")
|
||||
.field("inner", &self.inner.get_ref().0)
|
||||
.field("encoder", &self.inner.get_ref().1)
|
||||
.field("buffer", &self.inner.buffer)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl FramedWrite2 =====
|
||||
|
||||
pub fn framed_write2<T>(
|
||||
inner: T,
|
||||
low_watermark: usize,
|
||||
high_watermark: usize,
|
||||
) -> FramedWrite2<T> {
|
||||
FramedWrite2 {
|
||||
inner,
|
||||
low_watermark,
|
||||
high_watermark,
|
||||
buffer: BytesMut::with_capacity(high_watermark),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn framed_write2_with_buffer<T>(
|
||||
inner: T,
|
||||
mut buffer: BytesMut,
|
||||
low_watermark: usize,
|
||||
high_watermark: usize,
|
||||
) -> FramedWrite2<T> {
|
||||
if buffer.capacity() < high_watermark {
|
||||
let bytes_to_reserve = high_watermark - buffer.capacity();
|
||||
buffer.reserve(bytes_to_reserve);
|
||||
}
|
||||
FramedWrite2 {
|
||||
inner,
|
||||
buffer,
|
||||
low_watermark,
|
||||
high_watermark,
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FramedWrite2<T> {
|
||||
pub fn get_ref(&self) -> &T {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> T {
|
||||
self.inner
|
||||
}
|
||||
|
||||
pub fn into_parts(self) -> (T, BytesMut, usize, usize) {
|
||||
(
|
||||
self.inner,
|
||||
self.buffer,
|
||||
self.low_watermark,
|
||||
self.high_watermark,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self) -> &mut T {
|
||||
&mut self.inner
|
||||
}
|
||||
|
||||
pub fn is_full(&self) -> bool {
|
||||
self.buffer.len() >= self.high_watermark
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.buffer.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FramedWrite2<T>
|
||||
where
|
||||
T: Encoder,
|
||||
{
|
||||
pub fn force_send(&mut self, item: T::Item) -> Result<(), T::Error> {
|
||||
let len = self.buffer.len();
|
||||
if len < self.low_watermark {
|
||||
self.buffer.reserve(self.high_watermark - len)
|
||||
}
|
||||
self.inner.encode(item, &mut self.buffer)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Sink for FramedWrite2<T>
|
||||
where
|
||||
T: AsyncWrite + Encoder,
|
||||
{
|
||||
type SinkItem = T::Item;
|
||||
type SinkError = T::Error;
|
||||
|
||||
fn start_send(&mut self, item: T::Item) -> StartSend<T::Item, T::Error> {
|
||||
// Check the buffer capacity
|
||||
let len = self.buffer.len();
|
||||
if len >= self.high_watermark {
|
||||
return Ok(AsyncSink::NotReady(item));
|
||||
}
|
||||
if len < self.low_watermark {
|
||||
self.buffer.reserve(self.high_watermark - len)
|
||||
}
|
||||
|
||||
self.inner.encode(item, &mut self.buffer)?;
|
||||
|
||||
Ok(AsyncSink::Ready)
|
||||
}
|
||||
|
||||
fn poll_complete(&mut self) -> Poll<(), Self::SinkError> {
|
||||
trace!("flushing framed transport");
|
||||
|
||||
while !self.buffer.is_empty() {
|
||||
trace!("writing; remaining={}", self.buffer.len());
|
||||
|
||||
let n = try_ready!(self.inner.poll_write(&self.buffer));
|
||||
|
||||
if n == 0 {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::WriteZero,
|
||||
"failed to \
|
||||
write frame to transport",
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
// TODO: Add a way to `bytes` to do this w/o returning the drained
|
||||
// data.
|
||||
let _ = self.buffer.split_to(n);
|
||||
}
|
||||
|
||||
// Try flushing the underlying IO
|
||||
try_ready!(self.inner.poll_flush());
|
||||
|
||||
trace!("framed transport flushed");
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
fn close(&mut self) -> Poll<(), Self::SinkError> {
|
||||
try_ready!(self.poll_complete());
|
||||
Ok(self.inner.shutdown()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Decoder> Decoder for FramedWrite2<T> {
|
||||
type Item = T::Item;
|
||||
type Error = T::Error;
|
||||
|
||||
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<T::Item>, T::Error> {
|
||||
self.inner.decode(src)
|
||||
}
|
||||
|
||||
fn decode_eof(&mut self, src: &mut BytesMut) -> Result<Option<T::Item>, T::Error> {
|
||||
self.inner.decode_eof(src)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Read> Read for FramedWrite2<T> {
|
||||
fn read(&mut self, dst: &mut [u8]) -> io::Result<usize> {
|
||||
self.inner.read(dst)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsyncRead> AsyncRead for FramedWrite2<T> {
|
||||
unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool {
|
||||
self.inner.prepare_uninitialized_buffer(buf)
|
||||
}
|
||||
}
|
@@ -1,23 +1,24 @@
|
||||
//! Codec utilities for working with framed protocols.
|
||||
//! Utilities for encoding and decoding frames.
|
||||
//!
|
||||
//! 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`.
|
||||
//! Framed streams are also known as [transports].
|
||||
//!
|
||||
//! [`Sink`]: futures_sink::Sink
|
||||
//! [`Stream`]: futures_core::Stream
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style, future_incompatible)]
|
||||
#![warn(missing_docs)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
//! [`AsyncRead`]: #
|
||||
//! [`AsyncWrite`]: #
|
||||
//! [`Sink`]: #
|
||||
//! [`Stream`]: #
|
||||
//! [transports]: #
|
||||
|
||||
mod bcodec;
|
||||
mod framed;
|
||||
mod framed_read;
|
||||
mod framed_write;
|
||||
|
||||
pub use self::bcodec::BytesCodec;
|
||||
pub use self::framed::{Framed, FramedParts};
|
||||
pub use self::framed_read::FramedRead;
|
||||
pub use self::framed_write::FramedWrite;
|
||||
|
||||
pub use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
pub use tokio_util::codec::{Decoder, Encoder};
|
||||
pub use tokio_util::io::poll_read_buf;
|
||||
pub use tokio_codec::{Decoder, Encoder};
|
||||
pub use tokio_io::{AsyncRead, AsyncWrite};
|
||||
|
75
actix-connect/CHANGES.md
Normal file
75
actix-connect/CHANGES.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# Changes
|
||||
|
||||
## [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
|
60
actix-connect/Cargo.toml
Normal file
60
actix-connect/Cargo.toml
Normal file
@@ -0,0 +1,60 @@
|
||||
[package]
|
||||
name = "actix-connect"
|
||||
version = "0.2.3"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix Connector - tcp connector service"
|
||||
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/Apache-2.0"
|
||||
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
|
||||
edition = "2018"
|
||||
workspace = ".."
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["ssl", "uri"]
|
||||
|
||||
[lib]
|
||||
name = "actix_connect"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = ["uri"]
|
||||
|
||||
# openssl
|
||||
ssl = ["openssl", "tokio-openssl"]
|
||||
|
||||
#rustls
|
||||
rust-tls = ["rustls", "tokio-rustls", "webpki"]
|
||||
|
||||
# support http::Uri as connect address
|
||||
uri = ["http"]
|
||||
|
||||
[dependencies]
|
||||
actix-service = "0.4.0"
|
||||
actix-codec = "0.1.2"
|
||||
actix-utils = "0.4.0"
|
||||
derive_more = "0.15"
|
||||
either = "1.5.2"
|
||||
futures = "0.1.25"
|
||||
http = { version = "0.1.17", optional = true }
|
||||
log = "0.4"
|
||||
tokio-tcp = "0.1.3"
|
||||
tokio-current-thread = "0.1.5"
|
||||
trust-dns-resolver = { version="0.11.0", default-features = false }
|
||||
|
||||
# openssl
|
||||
openssl = { version="0.10", optional = true }
|
||||
tokio-openssl = { version="0.3", optional = true }
|
||||
|
||||
#rustls
|
||||
rustls = { version = "0.15.2", optional = true }
|
||||
tokio-rustls = { version = "0.9.1", optional = true }
|
||||
webpki = { version = "0.19", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
bytes = "0.4"
|
||||
actix-test-server = { version="0.2.2", features=["ssl"] }
|
||||
actix-server-config = "0.1.0"
|
281
actix-connect/src/connect.rs
Normal file
281
actix-connect/src/connect.rs
Normal file
@@ -0,0 +1,281 @@
|
||||
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 {
|
||||
/// 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 spliting 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)
|
||||
}
|
||||
|
||||
/// Preresolved 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 preresolved 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`](struct.Connect.html) 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`](struct.Connect.html) 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)
|
||||
}
|
||||
}
|
168
actix-connect/src/connector.rs
Normal file
168
actix-connect/src/connector.rs
Normal file
@@ -0,0 +1,168 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::marker::PhantomData;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use actix_service::{NewService, Service};
|
||||
use futures::future::{err, ok, Either, FutureResult};
|
||||
use futures::{Async, Future, Poll};
|
||||
use tokio_tcp::{ConnectFuture, TcpStream};
|
||||
|
||||
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> NewService for TcpConnectorFactory<T> {
|
||||
type Request = Connect<T>;
|
||||
type Response = Connection<T, TcpStream>;
|
||||
type Error = ConnectError;
|
||||
type Config = ();
|
||||
type Service = TcpConnector<T>;
|
||||
type InitError = ();
|
||||
type Future = FutureResult<Self::Service, Self::InitError>;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
ok(self.service())
|
||||
}
|
||||
}
|
||||
|
||||
/// Tcp connector service
|
||||
#[derive(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 for TcpConnector<T> {
|
||||
type Request = Connect<T>;
|
||||
type Response = Connection<T, TcpStream>;
|
||||
type Error = ConnectError;
|
||||
type Future = Either<TcpConnectorResponse<T>, FutureResult<Self::Response, Self::Error>>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::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::A(TcpConnectorResponse::new(req, port, addr))
|
||||
} else {
|
||||
error!("TCP connector: got unresolved address");
|
||||
Either::B(err(ConnectError::Unresolverd))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Tcp stream connector response future
|
||||
pub struct TcpConnectorResponse<T> {
|
||||
req: Option<T>,
|
||||
port: u16,
|
||||
addrs: Option<VecDeque<SocketAddr>>,
|
||||
stream: Option<ConnectFuture>,
|
||||
}
|
||||
|
||||
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)),
|
||||
},
|
||||
either::Either::Right(addrs) => TcpConnectorResponse {
|
||||
req: Some(req),
|
||||
port,
|
||||
addrs: Some(addrs),
|
||||
stream: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Address> Future for TcpConnectorResponse<T> {
|
||||
type Item = Connection<T, TcpStream>;
|
||||
type Error = ConnectError;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
// connect
|
||||
loop {
|
||||
if let Some(new) = self.stream.as_mut() {
|
||||
match new.poll() {
|
||||
Ok(Async::Ready(sock)) => {
|
||||
let req = self.req.take().unwrap();
|
||||
trace!(
|
||||
"TCP connector - successfully connected to connecting to {:?} - {:?}",
|
||||
req.host(), sock.peer_addr()
|
||||
);
|
||||
return Ok(Async::Ready(Connection::new(sock, req)));
|
||||
}
|
||||
Ok(Async::NotReady) => return Ok(Async::NotReady),
|
||||
Err(err) => {
|
||||
trace!(
|
||||
"TCP connector - failed to connect to connecting to {:?} port: {}",
|
||||
self.req.as_ref().unwrap().host(),
|
||||
self.port,
|
||||
);
|
||||
if self.addrs.is_none() || self.addrs.as_ref().unwrap().is_empty() {
|
||||
return Err(err.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// try to connect
|
||||
self.stream = Some(TcpStream::connect(
|
||||
&self.addrs.as_mut().unwrap().pop_front().unwrap(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,12 +1,13 @@
|
||||
use std::io;
|
||||
|
||||
use derive_more::Display;
|
||||
use derive_more::{Display, From};
|
||||
use trust_dns_resolver::error::ResolveError;
|
||||
|
||||
#[derive(Debug, Display)]
|
||||
#[derive(Debug, From, Display)]
|
||||
pub enum ConnectError {
|
||||
/// Failed to resolve the hostname
|
||||
#[display(fmt = "Failed resolving hostname: {}", _0)]
|
||||
Resolver(Box<dyn std::error::Error>),
|
||||
Resolver(ResolveError),
|
||||
|
||||
/// No dns records
|
||||
#[display(fmt = "No dns records found for the input")]
|
||||
@@ -17,9 +18,9 @@ pub enum ConnectError {
|
||||
|
||||
/// Unresolved host name
|
||||
#[display(fmt = "Connector received `Connect` method with unresolved host")]
|
||||
Unresolved,
|
||||
Unresolverd,
|
||||
|
||||
/// Connection IO error
|
||||
/// Connection io error
|
||||
#[display(fmt = "{}", _0)]
|
||||
Io(io::Error),
|
||||
}
|
111
actix-connect/src/lib.rs
Normal file
111
actix-connect/src/lib.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
//! Actix connect - tcp connector service
|
||||
//!
|
||||
//! ## Package feature
|
||||
//!
|
||||
//! * `ssl` - enables ssl support via `openssl` crate
|
||||
//! * `rust-tls` - enables ssl support via `rustls` crate
|
||||
|
||||
#![recursion_limit = "128"]
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
use std::cell::RefCell;
|
||||
|
||||
mod connect;
|
||||
mod connector;
|
||||
mod error;
|
||||
mod resolver;
|
||||
mod service;
|
||||
pub mod ssl;
|
||||
|
||||
#[cfg(feature = "uri")]
|
||||
mod uri;
|
||||
|
||||
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::resolver::{Resolver, ResolverFactory};
|
||||
pub use self::service::{ConnectService, ConnectServiceFactory};
|
||||
|
||||
use actix_service::{NewService, Service, ServiceExt};
|
||||
use tokio_tcp::TcpStream;
|
||||
|
||||
pub fn start_resolver(cfg: ResolverConfig, opts: ResolverOpts) -> AsyncResolver {
|
||||
let (resolver, bg) = AsyncResolver::new(cfg, opts);
|
||||
tokio_current_thread::spawn(bg);
|
||||
resolver
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static DEFAULT_RESOLVER: RefCell<Option<AsyncResolver>> = RefCell::new(None);
|
||||
}
|
||||
|
||||
pub(crate) fn get_default_resolver() -> AsyncResolver {
|
||||
DEFAULT_RESOLVER.with(|cell| {
|
||||
if let Some(ref resolver) = *cell.borrow() {
|
||||
return resolver.clone();
|
||||
}
|
||||
|
||||
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, bg) = AsyncResolver::new(cfg, opts);
|
||||
tokio_current_thread::spawn(bg);
|
||||
|
||||
*cell.borrow_mut() = Some(resolver.clone());
|
||||
resolver
|
||||
})
|
||||
}
|
||||
|
||||
pub fn start_default_resolver() -> AsyncResolver {
|
||||
get_default_resolver()
|
||||
}
|
||||
|
||||
/// Create tcp connector service
|
||||
pub fn new_connector<T: Address>(
|
||||
resolver: AsyncResolver,
|
||||
) -> impl Service<Request = Connect<T>, Response = Connection<T, TcpStream>, Error = ConnectError>
|
||||
+ Clone {
|
||||
Resolver::new(resolver).and_then(TcpConnector::new())
|
||||
}
|
||||
|
||||
/// Create tcp connector service
|
||||
pub fn new_connector_factory<T: Address>(
|
||||
resolver: AsyncResolver,
|
||||
) -> impl NewService<
|
||||
Config = (),
|
||||
Request = Connect<T>,
|
||||
Response = Connection<T, TcpStream>,
|
||||
Error = ConnectError,
|
||||
InitError = (),
|
||||
> + Clone {
|
||||
ResolverFactory::new(resolver).and_then(TcpConnectorFactory::new())
|
||||
}
|
||||
|
||||
/// Create connector service with default parameters
|
||||
pub fn default_connector<T: Address>(
|
||||
) -> impl Service<Request = Connect<T>, Response = Connection<T, TcpStream>, Error = ConnectError>
|
||||
+ Clone {
|
||||
Resolver::default().and_then(TcpConnector::new())
|
||||
}
|
||||
|
||||
/// Create connector service factory with default parameters
|
||||
pub fn default_connector_factory<T: Address>() -> impl NewService<
|
||||
Config = (),
|
||||
Request = Connect<T>,
|
||||
Response = Connection<T, TcpStream>,
|
||||
Error = ConnectError,
|
||||
InitError = (),
|
||||
> + Clone {
|
||||
ResolverFactory::default().and_then(TcpConnectorFactory::new())
|
||||
}
|
187
actix-connect/src/resolver.rs
Normal file
187
actix-connect/src/resolver.rs
Normal file
@@ -0,0 +1,187 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use actix_service::{NewService, Service};
|
||||
use futures::future::{ok, Either, FutureResult};
|
||||
use futures::{Async, Future, Poll};
|
||||
use trust_dns_resolver::lookup_ip::LookupIpFuture;
|
||||
use trust_dns_resolver::{AsyncResolver, Background};
|
||||
|
||||
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> NewService for ResolverFactory<T> {
|
||||
type Request = Connect<T>;
|
||||
type Response = Connect<T>;
|
||||
type Error = ConnectError;
|
||||
type Config = ();
|
||||
type Service = Resolver<T>;
|
||||
type InitError = ();
|
||||
type Future = FutureResult<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 for Resolver<T> {
|
||||
type Request = Connect<T>;
|
||||
type Response = Connect<T>;
|
||||
type Error = ConnectError;
|
||||
type Future = Either<ResolverFuture<T>, FutureResult<Connect<T>, Self::Error>>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, mut req: Connect<T>) -> Self::Future {
|
||||
if req.addr.is_some() {
|
||||
Either::B(ok(req))
|
||||
} else {
|
||||
if let Ok(ip) = req.host().parse() {
|
||||
req.addr = Some(either::Either::Left(SocketAddr::new(ip, req.port())));
|
||||
Either::B(ok(req))
|
||||
} else {
|
||||
trace!("DNS resolver: resolving host {:?}", req.host());
|
||||
if self.resolver.is_none() {
|
||||
self.resolver = Some(get_default_resolver());
|
||||
}
|
||||
Either::A(ResolverFuture::new(req, self.resolver.as_ref().unwrap()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Resolver future
|
||||
pub struct ResolverFuture<T: Address> {
|
||||
req: Option<Connect<T>>,
|
||||
lookup: Background<LookupIpFuture>,
|
||||
}
|
||||
|
||||
impl<T: Address> ResolverFuture<T> {
|
||||
pub fn new(req: Connect<T>, resolver: &AsyncResolver) -> Self {
|
||||
let lookup = if let Some(host) = req.host().splitn(2, ':').next() {
|
||||
resolver.lookup_ip(host)
|
||||
} else {
|
||||
resolver.lookup_ip(req.host())
|
||||
};
|
||||
|
||||
ResolverFuture {
|
||||
lookup,
|
||||
req: Some(req),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Address> Future for ResolverFuture<T> {
|
||||
type Item = Connect<T>;
|
||||
type Error = ConnectError;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
match self.lookup.poll().map_err(|e| {
|
||||
trace!(
|
||||
"DNS resolver: failed to resolve host {:?} err: {}",
|
||||
self.req.as_ref().unwrap().host(),
|
||||
e
|
||||
);
|
||||
e
|
||||
})? {
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
Async::Ready(ips) => {
|
||||
let req = self.req.take().unwrap();
|
||||
|
||||
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() {
|
||||
Err(ConnectError::NoRecords)
|
||||
} else {
|
||||
Ok(Async::Ready(req))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
123
actix-connect/src/service.rs
Normal file
123
actix-connect/src/service.rs
Normal file
@@ -0,0 +1,123 @@
|
||||
use actix_service::{NewService, Service};
|
||||
use futures::future::{ok, FutureResult};
|
||||
use futures::{try_ready, Async, Future, Poll};
|
||||
use tokio_tcp::TcpStream;
|
||||
use trust_dns_resolver::AsyncResolver;
|
||||
|
||||
use crate::connect::{Address, Connect, Connection};
|
||||
use crate::connector::{TcpConnector, TcpConnectorFactory};
|
||||
use crate::error::ConnectError;
|
||||
use crate::resolver::{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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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> NewService for ConnectServiceFactory<T> {
|
||||
type Request = Connect<T>;
|
||||
type Response = Connection<T, TcpStream>;
|
||||
type Error = ConnectError;
|
||||
type Config = ();
|
||||
type Service = ConnectService<T>;
|
||||
type InitError = ();
|
||||
type Future = FutureResult<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 for ConnectService<T> {
|
||||
type Request = Connect<T>;
|
||||
type Response = Connection<T, TcpStream>;
|
||||
type Error = ConnectError;
|
||||
type Future = ConnectServiceResponse<T>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Connect<T>) -> Self::Future {
|
||||
ConnectServiceResponse {
|
||||
fut1: Some(self.resolver.call(req)),
|
||||
fut2: None,
|
||||
tcp: self.tcp.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConnectServiceResponse<T: Address> {
|
||||
fut1: Option<<Resolver<T> as Service>::Future>,
|
||||
fut2: Option<<TcpConnector<T> as Service>::Future>,
|
||||
tcp: TcpConnector<T>,
|
||||
}
|
||||
|
||||
impl<T: Address> Future for ConnectServiceResponse<T> {
|
||||
type Item = Connection<T, TcpStream>;
|
||||
type Error = ConnectError;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
if let Some(ref mut fut) = self.fut1 {
|
||||
let res = try_ready!(fut.poll());
|
||||
let _ = self.fut1.take();
|
||||
self.fut2 = Some(self.tcp.call(res));
|
||||
}
|
||||
|
||||
if let Some(ref mut fut) = self.fut2 {
|
||||
return fut.poll();
|
||||
}
|
||||
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
12
actix-connect/src/ssl/mod.rs
Normal file
12
actix-connect/src/ssl/mod.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
//! SSL Services
|
||||
|
||||
#[cfg(feature = "ssl")]
|
||||
mod openssl;
|
||||
#[cfg(feature = "ssl")]
|
||||
pub use self::openssl::{
|
||||
OpensslConnectService, OpensslConnectServiceFactory, OpensslConnector,
|
||||
};
|
||||
#[cfg(feature = "rust-tls")]
|
||||
mod rustls;
|
||||
#[cfg(feature = "rust-tls")]
|
||||
pub use self::rustls::RustlsConnector;
|
251
actix-connect/src/ssl/openssl.rs
Normal file
251
actix-connect/src/ssl/openssl.rs
Normal file
@@ -0,0 +1,251 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::{fmt, io};
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite};
|
||||
use actix_service::{NewService, Service};
|
||||
use futures::{future::ok, future::FutureResult, try_ready, Async, Future, Poll};
|
||||
use openssl::ssl::{HandshakeError, SslConnector};
|
||||
use tokio_openssl::{ConnectAsync, SslConnectorExt, SslStream};
|
||||
use tokio_tcp::TcpStream;
|
||||
use trust_dns_resolver::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,
|
||||
U: AsyncRead + AsyncWrite + fmt::Debug,
|
||||
{
|
||||
pub fn service(
|
||||
connector: SslConnector,
|
||||
) -> impl Service<
|
||||
Request = Connection<T, U>,
|
||||
Response = Connection<T, SslStream<U>>,
|
||||
Error = HandshakeError<U>,
|
||||
> {
|
||||
OpensslConnectorService {
|
||||
connector: connector,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Clone for OpensslConnector<T, U> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
connector: self.connector.clone(),
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Address, U> NewService for OpensslConnector<T, U>
|
||||
where
|
||||
U: AsyncRead + AsyncWrite + fmt::Debug,
|
||||
{
|
||||
type Request = Connection<T, U>;
|
||||
type Response = Connection<T, SslStream<U>>;
|
||||
type Error = HandshakeError<U>;
|
||||
type Config = ();
|
||||
type Service = OpensslConnectorService<T, U>;
|
||||
type InitError = ();
|
||||
type Future = FutureResult<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: Address, U> Service for OpensslConnectorService<T, U>
|
||||
where
|
||||
U: AsyncRead + AsyncWrite + fmt::Debug,
|
||||
{
|
||||
type Request = Connection<T, U>;
|
||||
type Response = Connection<T, SslStream<U>>;
|
||||
type Error = HandshakeError<U>;
|
||||
type Future = ConnectAsyncExt<T, U>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, stream: Connection<T, U>) -> Self::Future {
|
||||
trace!("SSL Handshake start for: {:?}", stream.host());
|
||||
let (io, stream) = stream.replace(());
|
||||
ConnectAsyncExt {
|
||||
fut: SslConnectorExt::connect_async(&self.connector, stream.host(), io),
|
||||
stream: Some(stream),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConnectAsyncExt<T, U> {
|
||||
fut: ConnectAsync<U>,
|
||||
stream: Option<Connection<T, ()>>,
|
||||
}
|
||||
|
||||
impl<T: Address, U> Future for ConnectAsyncExt<T, U>
|
||||
where
|
||||
U: AsyncRead + AsyncWrite + fmt::Debug,
|
||||
{
|
||||
type Item = Connection<T, SslStream<U>>;
|
||||
type Error = HandshakeError<U>;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
match self.fut.poll().map_err(|e| {
|
||||
trace!("SSL Handshake error: {:?}", e);
|
||||
e
|
||||
})? {
|
||||
Async::Ready(stream) => {
|
||||
let s = self.stream.take().unwrap();
|
||||
trace!("SSL Handshake success: {:?}", s.host());
|
||||
Ok(Async::Ready(s.replace(stream).1))
|
||||
}
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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> NewService for OpensslConnectServiceFactory<T> {
|
||||
type Request = Connect<T>;
|
||||
type Response = SslStream<TcpStream>;
|
||||
type Error = ConnectError;
|
||||
type Config = ();
|
||||
type Service = OpensslConnectService<T>;
|
||||
type InitError = ();
|
||||
type Future = FutureResult<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> Service for OpensslConnectService<T> {
|
||||
type Request = Connect<T>;
|
||||
type Response = SslStream<TcpStream>;
|
||||
type Error = ConnectError;
|
||||
type Future = OpensslConnectServiceResponse<T>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::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> {
|
||||
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 Item = SslStream<TcpStream>;
|
||||
type Error = ConnectError;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
if let Some(ref mut fut) = self.fut1 {
|
||||
let res = try_ready!(fut.poll());
|
||||
let _ = self.fut1.take();
|
||||
self.fut2 = Some(self.openssl.call(res));
|
||||
}
|
||||
|
||||
if let Some(ref mut fut) = self.fut2 {
|
||||
let connect = try_ready!(fut
|
||||
.poll()
|
||||
.map_err(|e| ConnectError::Io(io::Error::new(io::ErrorKind::Other, e))));
|
||||
Ok(Async::Ready(connect.into_parts().0))
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
}
|
133
actix-connect/src/ssl/rustls.rs
Normal file
133
actix-connect/src/ssl/rustls.rs
Normal file
@@ -0,0 +1,133 @@
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite};
|
||||
use actix_service::{NewService, Service};
|
||||
use futures::{future::ok, future::FutureResult, Async, Future, Poll};
|
||||
use std::sync::Arc;
|
||||
use tokio_rustls::{
|
||||
rustls::{ClientConfig, ClientSession},
|
||||
Connect, TlsConnector, TlsStream,
|
||||
};
|
||||
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 + fmt::Debug,
|
||||
{
|
||||
pub fn service(
|
||||
connector: Arc<ClientConfig>,
|
||||
) -> impl Service<
|
||||
Request = Connection<T, U>,
|
||||
Response = Connection<T, TlsStream<U, ClientSession>>,
|
||||
Error = std::io::Error,
|
||||
> {
|
||||
RustlsConnectorService {
|
||||
connector: 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> NewService for RustlsConnector<T, U>
|
||||
where
|
||||
U: AsyncRead + AsyncWrite + fmt::Debug,
|
||||
{
|
||||
type Request = Connection<T, U>;
|
||||
type Response = Connection<T, TlsStream<U, ClientSession>>;
|
||||
type Error = std::io::Error;
|
||||
type Config = ();
|
||||
type Service = RustlsConnectorService<T, U>;
|
||||
type InitError = ();
|
||||
type Future = FutureResult<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: Address, U> Service for RustlsConnectorService<T, U>
|
||||
where
|
||||
U: AsyncRead + AsyncWrite + fmt::Debug,
|
||||
{
|
||||
type Request = Connection<T, U>;
|
||||
type Response = Connection<T, TlsStream<U, ClientSession>>;
|
||||
type Error = std::io::Error;
|
||||
type Future = ConnectAsyncExt<T, U>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::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()).unwrap();
|
||||
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 + fmt::Debug,
|
||||
{
|
||||
type Item = Connection<T, TlsStream<U, ClientSession>>;
|
||||
type Error = std::io::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
match self.fut.poll().map_err(|e| {
|
||||
trace!("SSL Handshake error: {:?}", e);
|
||||
e
|
||||
})? {
|
||||
Async::Ready(stream) => {
|
||||
let s = self.stream.take().unwrap();
|
||||
trace!("SSL Handshake success: {:?}", s.host());
|
||||
Ok(Async::Ready(s.replace(stream).1))
|
||||
}
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
}
|
37
actix-connect/src/uri.rs
Normal file
37
actix-connect/src/uri.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
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
|
||||
}
|
||||
}
|
142
actix-connect/tests/test_connect.rs
Normal file
142
actix-connect/tests/test_connect.rs
Normal file
@@ -0,0 +1,142 @@
|
||||
use actix_codec::{BytesCodec, Framed};
|
||||
use actix_server_config::Io;
|
||||
use actix_service::{service_fn, NewService, Service};
|
||||
use actix_test_server::TestServer;
|
||||
use bytes::Bytes;
|
||||
use futures::{future::lazy, Future, Sink};
|
||||
use http::{HttpTryFrom, Uri};
|
||||
use trust_dns_resolver::config::{ResolverConfig, ResolverOpts};
|
||||
|
||||
use actix_connect::{default_connector, Connect};
|
||||
|
||||
#[cfg(feature = "ssl")]
|
||||
#[test]
|
||||
fn test_string() {
|
||||
let mut srv = TestServer::with(|| {
|
||||
service_fn(|io: Io<tokio_tcp::TcpStream>| {
|
||||
Framed::new(io.into_parts().0, BytesCodec)
|
||||
.send(Bytes::from_static(b"test"))
|
||||
.then(|_| Ok::<_, ()>(()))
|
||||
})
|
||||
});
|
||||
|
||||
let mut conn = default_connector();
|
||||
let addr = format!("localhost:{}", srv.port());
|
||||
let con = srv.run_on(move || conn.call(addr.into())).unwrap();
|
||||
assert_eq!(con.peer_addr().unwrap(), srv.addr());
|
||||
}
|
||||
|
||||
#[cfg(feature = "rust-tls")]
|
||||
#[test]
|
||||
fn test_rustls_string() {
|
||||
let mut srv = TestServer::with(|| {
|
||||
service_fn(|io: Io<tokio_tcp::TcpStream>| {
|
||||
Framed::new(io.into_parts().0, BytesCodec)
|
||||
.send(Bytes::from_static(b"test"))
|
||||
.then(|_| Ok::<_, ()>(()))
|
||||
})
|
||||
});
|
||||
|
||||
let mut conn = default_connector();
|
||||
let addr = format!("localhost:{}", srv.port());
|
||||
let con = srv.run_on(move || conn.call(addr.into())).unwrap();
|
||||
assert_eq!(con.peer_addr().unwrap(), srv.addr());
|
||||
}
|
||||
#[test]
|
||||
fn test_static_str() {
|
||||
let mut srv = TestServer::with(|| {
|
||||
service_fn(|io: Io<tokio_tcp::TcpStream>| {
|
||||
Framed::new(io.into_parts().0, BytesCodec)
|
||||
.send(Bytes::from_static(b"test"))
|
||||
.then(|_| Ok::<_, ()>(()))
|
||||
})
|
||||
});
|
||||
|
||||
let resolver = srv
|
||||
.block_on(lazy(
|
||||
|| Ok::<_, ()>(actix_connect::start_default_resolver()),
|
||||
))
|
||||
.unwrap();
|
||||
let mut conn = srv
|
||||
.block_on(lazy(|| {
|
||||
Ok::<_, ()>(actix_connect::new_connector(resolver.clone()))
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
let con = srv
|
||||
.block_on(conn.call(Connect::with("10", srv.addr())))
|
||||
.unwrap();
|
||||
assert_eq!(con.peer_addr().unwrap(), srv.addr());
|
||||
|
||||
let connect = Connect::new(srv.host().to_owned());
|
||||
let mut conn = srv
|
||||
.block_on(lazy(|| Ok::<_, ()>(actix_connect::new_connector(resolver))))
|
||||
.unwrap();
|
||||
let con = srv.block_on(conn.call(connect));
|
||||
assert!(con.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_service() {
|
||||
let mut srv = TestServer::with(|| {
|
||||
service_fn(|io: Io<tokio_tcp::TcpStream>| {
|
||||
Framed::new(io.into_parts().0, BytesCodec)
|
||||
.send(Bytes::from_static(b"test"))
|
||||
.then(|_| Ok::<_, ()>(()))
|
||||
})
|
||||
});
|
||||
|
||||
let resolver = srv
|
||||
.block_on(lazy(|| {
|
||||
Ok::<_, ()>(actix_connect::start_resolver(
|
||||
ResolverConfig::default(),
|
||||
ResolverOpts::default(),
|
||||
))
|
||||
}))
|
||||
.unwrap();
|
||||
let factory = srv
|
||||
.block_on(lazy(|| {
|
||||
Ok::<_, ()>(actix_connect::new_connector_factory(resolver))
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
let mut conn = srv.block_on(factory.new_service(&())).unwrap();
|
||||
let con = srv
|
||||
.block_on(conn.call(Connect::with("10", srv.addr())))
|
||||
.unwrap();
|
||||
assert_eq!(con.peer_addr().unwrap(), srv.addr());
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssl")]
|
||||
#[test]
|
||||
fn test_uri() {
|
||||
let mut srv = TestServer::with(|| {
|
||||
service_fn(|io: Io<tokio_tcp::TcpStream>| {
|
||||
Framed::new(io.into_parts().0, BytesCodec)
|
||||
.send(Bytes::from_static(b"test"))
|
||||
.then(|_| Ok::<_, ()>(()))
|
||||
})
|
||||
});
|
||||
|
||||
let mut conn = default_connector();
|
||||
let addr = Uri::try_from(format!("https://localhost:{}", srv.port())).unwrap();
|
||||
let con = srv.run_on(move || conn.call(addr.into())).unwrap();
|
||||
assert_eq!(con.peer_addr().unwrap(), srv.addr());
|
||||
}
|
||||
|
||||
#[cfg(feature = "rust-tls")]
|
||||
#[test]
|
||||
fn test_rustls_uri() {
|
||||
let mut srv = TestServer::with(|| {
|
||||
service_fn(|io: Io<tokio_tcp::TcpStream>| {
|
||||
Framed::new(io.into_parts().0, BytesCodec)
|
||||
.send(Bytes::from_static(b"test"))
|
||||
.then(|_| Ok::<_, ()>(()))
|
||||
})
|
||||
});
|
||||
|
||||
let mut conn = default_connector();
|
||||
let addr = Uri::try_from(format!("https://localhost:{}", srv.port())).unwrap();
|
||||
let con = srv.run_on(move || conn.call(addr.into())).unwrap();
|
||||
assert_eq!(con.peer_addr().unwrap(), srv.addr());
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
# Changes
|
||||
|
||||
## [0.1.0] - 2020-01-15
|
||||
## [0.1.0] - 2019-07-17
|
||||
|
||||
* Initial release
|
35
actix-ioframe/Cargo.toml
Normal file
35
actix-ioframe/Cargo.toml
Normal file
@@ -0,0 +1,35 @@
|
||||
[package]
|
||||
name = "actix-ioframe"
|
||||
version = "0.1.0"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix framed service"
|
||||
keywords = ["network", "framework", "async", "futures"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-net.git"
|
||||
documentation = "https://docs.rs/actix-ioframed/"
|
||||
categories = ["network-programming", "asynchronous"]
|
||||
license = "MIT/Apache-2.0"
|
||||
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
|
||||
edition = "2018"
|
||||
workspace = ".."
|
||||
|
||||
[lib]
|
||||
name = "actix_ioframe"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
actix-service = "0.4.1"
|
||||
actix-codec = "0.1.2"
|
||||
bytes = "0.4"
|
||||
either = "1.5.2"
|
||||
futures = "0.1.25"
|
||||
tokio-current-thread = "0.1.4"
|
||||
log = "0.4"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "0.2.2"
|
||||
actix-connect = "0.2.0"
|
||||
actix-test-server = "0.2.2"
|
||||
actix-server-config = "0.1.1"
|
||||
tokio-tcp = "0.1"
|
||||
tokio-timer = "0.2"
|
35
actix-ioframe/src/cell.rs
Normal file
35
actix-ioframe/src/cell.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
//! Custom cell impl
|
||||
|
||||
use std::cell::UnsafeCell;
|
||||
use std::fmt;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub(crate) struct Cell<T> {
|
||||
inner: Rc<UnsafeCell<T>>,
|
||||
}
|
||||
|
||||
impl<T> Clone for Cell<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug> fmt::Debug for Cell<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.inner.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Cell<T> {
|
||||
pub fn new(inner: T) -> Self {
|
||||
Self {
|
||||
inner: Rc::new(UnsafeCell::new(inner)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn get_mut(&mut self) -> &mut T {
|
||||
&mut *self.inner.as_ref().get()
|
||||
}
|
||||
}
|
110
actix-ioframe/src/connect.rs
Normal file
110
actix-ioframe/src/connect.rs
Normal file
@@ -0,0 +1,110 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed};
|
||||
use futures::unsync::mpsc;
|
||||
|
||||
use crate::dispatcher::FramedMessage;
|
||||
use crate::sink::Sink;
|
||||
|
||||
pub struct Connect<Io, St = (), Codec = ()> {
|
||||
io: Io,
|
||||
_t: PhantomData<(St, Codec)>,
|
||||
}
|
||||
|
||||
impl<Io> Connect<Io>
|
||||
where
|
||||
Io: AsyncRead + AsyncWrite,
|
||||
{
|
||||
pub(crate) fn new(io: Io) -> Self {
|
||||
Self {
|
||||
io,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn codec<Codec>(self, codec: Codec) -> ConnectResult<Io, (), Codec>
|
||||
where
|
||||
Codec: Encoder + Decoder,
|
||||
{
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
let sink = Sink::new(tx);
|
||||
|
||||
ConnectResult {
|
||||
state: (),
|
||||
framed: Framed::new(self.io, codec),
|
||||
rx,
|
||||
sink,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConnectResult<Io, St, Codec: Encoder + Decoder> {
|
||||
pub(crate) state: St,
|
||||
pub(crate) framed: Framed<Io, Codec>,
|
||||
pub(crate) rx: mpsc::UnboundedReceiver<FramedMessage<<Codec as Encoder>::Item>>,
|
||||
pub(crate) sink: Sink<<Codec as Encoder>::Item>,
|
||||
}
|
||||
|
||||
impl<Io, St, Codec: Encoder + Decoder> ConnectResult<Io, St, Codec> {
|
||||
#[inline]
|
||||
pub fn sink(&self) -> &Sink<<Codec as Encoder>::Item> {
|
||||
&self.sink
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_ref(&self) -> &Io {
|
||||
self.framed.get_ref()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_mut(&mut self) -> &mut Io {
|
||||
self.framed.get_mut()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn state<S>(self, state: S) -> ConnectResult<Io, S, Codec> {
|
||||
ConnectResult {
|
||||
state,
|
||||
framed: self.framed,
|
||||
rx: self.rx,
|
||||
sink: self.sink,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Io, St, Codec> futures::Stream for ConnectResult<Io, St, Codec>
|
||||
where
|
||||
Io: AsyncRead + AsyncWrite,
|
||||
Codec: Encoder + Decoder,
|
||||
{
|
||||
type Item = <Codec as Decoder>::Item;
|
||||
type Error = <Codec as Decoder>::Error;
|
||||
|
||||
fn poll(&mut self) -> futures::Poll<Option<Self::Item>, Self::Error> {
|
||||
self.framed.poll()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Io, St, Codec> futures::Sink for ConnectResult<Io, St, Codec>
|
||||
where
|
||||
Io: AsyncRead + AsyncWrite,
|
||||
Codec: Encoder + Decoder,
|
||||
{
|
||||
type SinkItem = <Codec as Encoder>::Item;
|
||||
type SinkError = <Codec as Encoder>::Error;
|
||||
|
||||
fn start_send(
|
||||
&mut self,
|
||||
item: Self::SinkItem,
|
||||
) -> futures::StartSend<Self::SinkItem, Self::SinkError> {
|
||||
self.framed.start_send(item)
|
||||
}
|
||||
|
||||
fn poll_complete(&mut self) -> futures::Poll<(), Self::SinkError> {
|
||||
self.framed.poll_complete()
|
||||
}
|
||||
|
||||
fn close(&mut self) -> futures::Poll<(), Self::SinkError> {
|
||||
self.framed.close()
|
||||
}
|
||||
}
|
325
actix-ioframe/src/dispatcher.rs
Normal file
325
actix-ioframe/src/dispatcher.rs
Normal file
@@ -0,0 +1,325 @@
|
||||
//! Framed dispatcher service and related utilities
|
||||
use std::collections::VecDeque;
|
||||
use std::mem;
|
||||
use std::rc::Rc;
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed};
|
||||
use actix_service::{IntoService, Service};
|
||||
use futures::task::AtomicTask;
|
||||
use futures::unsync::{mpsc, oneshot};
|
||||
use futures::{Async, Future, Poll, Sink as FutureSink, Stream};
|
||||
use log::debug;
|
||||
|
||||
use crate::cell::Cell;
|
||||
use crate::error::ServiceError;
|
||||
use crate::item::Item;
|
||||
use crate::sink::Sink;
|
||||
use crate::state::State;
|
||||
|
||||
type Request<S, U> = Item<S, U>;
|
||||
type Response<U> = <U as Encoder>::Item;
|
||||
|
||||
pub(crate) enum FramedMessage<T> {
|
||||
Message(T),
|
||||
Close,
|
||||
WaitClose(oneshot::Sender<()>),
|
||||
}
|
||||
|
||||
/// FramedTransport - is a future that reads frames from Framed object
|
||||
/// and pass then to the service.
|
||||
pub(crate) struct FramedDispatcher<St, S, T, U>
|
||||
where
|
||||
S: Service<Request = Request<St, U>, Response = Option<Response<U>>>,
|
||||
S::Error: 'static,
|
||||
S::Future: 'static,
|
||||
T: AsyncRead + AsyncWrite,
|
||||
U: Encoder + Decoder,
|
||||
<U as Encoder>::Item: 'static,
|
||||
<U as Encoder>::Error: std::fmt::Debug,
|
||||
{
|
||||
service: S,
|
||||
sink: Sink<<U as Encoder>::Item>,
|
||||
state: State<St>,
|
||||
dispatch_state: FramedState<S, U>,
|
||||
framed: Framed<T, U>,
|
||||
rx: Option<mpsc::UnboundedReceiver<FramedMessage<<U as Encoder>::Item>>>,
|
||||
inner: Cell<FramedDispatcherInner<<U as Encoder>::Item, S::Error>>,
|
||||
disconnect: Option<Rc<Fn(&mut St, bool)>>,
|
||||
}
|
||||
|
||||
impl<St, S, T, U> FramedDispatcher<St, S, T, U>
|
||||
where
|
||||
S: Service<Request = Request<St, U>, Response = Option<Response<U>>>,
|
||||
S::Error: 'static,
|
||||
S::Future: 'static,
|
||||
T: AsyncRead + AsyncWrite,
|
||||
U: Decoder + Encoder,
|
||||
<U as Encoder>::Item: 'static,
|
||||
<U as Encoder>::Error: std::fmt::Debug,
|
||||
{
|
||||
pub(crate) fn new<F: IntoService<S>>(
|
||||
framed: Framed<T, U>,
|
||||
state: State<St>,
|
||||
service: F,
|
||||
rx: mpsc::UnboundedReceiver<FramedMessage<<U as Encoder>::Item>>,
|
||||
sink: Sink<<U as Encoder>::Item>,
|
||||
disconnect: Option<Rc<Fn(&mut St, bool)>>,
|
||||
) -> Self {
|
||||
FramedDispatcher {
|
||||
framed,
|
||||
state,
|
||||
sink,
|
||||
disconnect,
|
||||
rx: Some(rx),
|
||||
service: service.into_service(),
|
||||
dispatch_state: FramedState::Processing,
|
||||
inner: Cell::new(FramedDispatcherInner {
|
||||
buf: VecDeque::new(),
|
||||
task: AtomicTask::new(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum FramedState<S: Service, U: Encoder + Decoder> {
|
||||
Processing,
|
||||
Error(ServiceError<S::Error, U>),
|
||||
FramedError(ServiceError<S::Error, U>),
|
||||
FlushAndStop(Vec<oneshot::Sender<()>>),
|
||||
Stopping,
|
||||
}
|
||||
|
||||
impl<S: Service, U: Encoder + Decoder> FramedState<S, U> {
|
||||
fn stop(&mut self, tx: Option<oneshot::Sender<()>>) {
|
||||
match self {
|
||||
FramedState::FlushAndStop(ref mut vec) => {
|
||||
if let Some(tx) = tx {
|
||||
vec.push(tx)
|
||||
}
|
||||
}
|
||||
FramedState::Processing => {
|
||||
*self = FramedState::FlushAndStop(if let Some(tx) = tx {
|
||||
vec![tx]
|
||||
} else {
|
||||
Vec::new()
|
||||
})
|
||||
}
|
||||
FramedState::Error(_) | FramedState::FramedError(_) | FramedState::Stopping => {
|
||||
if let Some(tx) = tx {
|
||||
let _ = tx.send(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FramedDispatcherInner<I, E> {
|
||||
buf: VecDeque<Result<I, E>>,
|
||||
task: AtomicTask,
|
||||
}
|
||||
|
||||
impl<St, S, T, U> FramedDispatcher<St, S, T, U>
|
||||
where
|
||||
S: Service<Request = Request<St, U>, Response = Option<Response<U>>>,
|
||||
S::Error: 'static,
|
||||
S::Future: 'static,
|
||||
T: AsyncRead + AsyncWrite,
|
||||
U: Decoder + Encoder,
|
||||
<U as Encoder>::Item: 'static,
|
||||
<U as Encoder>::Error: std::fmt::Debug,
|
||||
{
|
||||
fn disconnect(&mut self, error: bool) {
|
||||
if let Some(ref disconnect) = self.disconnect {
|
||||
(&*disconnect)(&mut *self.state.get_mut(), error);
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_read(&mut self) -> bool {
|
||||
loop {
|
||||
match self.service.poll_ready() {
|
||||
Ok(Async::Ready(_)) => {
|
||||
let item = match self.framed.poll() {
|
||||
Ok(Async::Ready(Some(el))) => el,
|
||||
Err(err) => {
|
||||
self.dispatch_state =
|
||||
FramedState::FramedError(ServiceError::Decoder(err));
|
||||
return true;
|
||||
}
|
||||
Ok(Async::NotReady) => return false,
|
||||
Ok(Async::Ready(None)) => {
|
||||
self.dispatch_state = FramedState::Stopping;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
let mut cell = self.inner.clone();
|
||||
unsafe { cell.get_mut().task.register() };
|
||||
tokio_current_thread::spawn(
|
||||
self.service
|
||||
.call(Item::new(self.state.clone(), self.sink.clone(), item))
|
||||
.then(move |item| {
|
||||
let item = match item {
|
||||
Ok(Some(item)) => Ok(item),
|
||||
Ok(None) => return Ok(()),
|
||||
Err(err) => Err(err),
|
||||
};
|
||||
unsafe {
|
||||
let inner = cell.get_mut();
|
||||
inner.buf.push_back(item);
|
||||
inner.task.notify();
|
||||
}
|
||||
Ok(())
|
||||
}),
|
||||
);
|
||||
}
|
||||
Ok(Async::NotReady) => return false,
|
||||
Err(err) => {
|
||||
self.dispatch_state = FramedState::Error(ServiceError::Service(err));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// write to framed object
|
||||
fn poll_write(&mut self) -> bool {
|
||||
let inner = unsafe { self.inner.get_mut() };
|
||||
let mut rx_done = self.rx.is_none();
|
||||
let mut buf_empty = inner.buf.is_empty();
|
||||
loop {
|
||||
while !self.framed.is_write_buf_full() {
|
||||
if !buf_empty {
|
||||
match inner.buf.pop_front().unwrap() {
|
||||
Ok(msg) => {
|
||||
if let Err(err) = self.framed.force_send(msg) {
|
||||
self.dispatch_state =
|
||||
FramedState::FramedError(ServiceError::Encoder(err));
|
||||
return true;
|
||||
}
|
||||
buf_empty = inner.buf.is_empty();
|
||||
}
|
||||
Err(err) => {
|
||||
self.dispatch_state =
|
||||
FramedState::Error(ServiceError::Service(err));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !rx_done && self.rx.is_some() {
|
||||
match self.rx.as_mut().unwrap().poll() {
|
||||
Ok(Async::Ready(Some(FramedMessage::Message(msg)))) => {
|
||||
if let Err(err) = self.framed.force_send(msg) {
|
||||
self.dispatch_state =
|
||||
FramedState::FramedError(ServiceError::Encoder(err));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Ok(Async::Ready(Some(FramedMessage::Close))) => {
|
||||
self.dispatch_state.stop(None);
|
||||
return true;
|
||||
}
|
||||
Ok(Async::Ready(Some(FramedMessage::WaitClose(tx)))) => {
|
||||
self.dispatch_state.stop(Some(tx));
|
||||
return true;
|
||||
}
|
||||
Ok(Async::Ready(None)) => {
|
||||
rx_done = true;
|
||||
let _ = self.rx.take();
|
||||
}
|
||||
Ok(Async::NotReady) => rx_done = true,
|
||||
Err(_e) => {
|
||||
rx_done = true;
|
||||
let _ = self.rx.take();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if rx_done && buf_empty {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if !self.framed.is_write_buf_empty() {
|
||||
match self.framed.poll_complete() {
|
||||
Ok(Async::NotReady) => break,
|
||||
Err(err) => {
|
||||
debug!("Error sending data: {:?}", err);
|
||||
self.dispatch_state =
|
||||
FramedState::FramedError(ServiceError::Encoder(err));
|
||||
return true;
|
||||
}
|
||||
Ok(Async::Ready(_)) => (),
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl<St, S, T, U> Future for FramedDispatcher<St, S, T, U>
|
||||
where
|
||||
S: Service<Request = Request<St, U>, Response = Option<Response<U>>>,
|
||||
S::Error: 'static,
|
||||
S::Future: 'static,
|
||||
T: AsyncRead + AsyncWrite,
|
||||
U: Decoder + Encoder,
|
||||
<U as Encoder>::Item: 'static,
|
||||
<U as Encoder>::Error: std::fmt::Debug,
|
||||
{
|
||||
type Item = ();
|
||||
type Error = ServiceError<S::Error, U>;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
match mem::replace(&mut self.dispatch_state, FramedState::Processing) {
|
||||
FramedState::Processing => {
|
||||
if self.poll_read() || self.poll_write() {
|
||||
self.poll()
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
FramedState::Error(err) => {
|
||||
if self.framed.is_write_buf_empty()
|
||||
|| (self.poll_write() || self.framed.is_write_buf_empty())
|
||||
{
|
||||
self.disconnect(true);
|
||||
Err(err)
|
||||
} else {
|
||||
self.dispatch_state = FramedState::Error(err);
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
FramedState::FlushAndStop(mut vec) => {
|
||||
if !self.framed.is_write_buf_empty() {
|
||||
match self.framed.poll_complete() {
|
||||
Err(err) => {
|
||||
debug!("Error sending data: {:?}", err);
|
||||
}
|
||||
Ok(Async::NotReady) => {
|
||||
self.dispatch_state = FramedState::FlushAndStop(vec);
|
||||
return Ok(Async::NotReady);
|
||||
}
|
||||
Ok(Async::Ready(_)) => (),
|
||||
}
|
||||
};
|
||||
for tx in vec.drain(..) {
|
||||
let _ = tx.send(());
|
||||
}
|
||||
self.disconnect(false);
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
FramedState::FramedError(err) => {
|
||||
self.disconnect(true);
|
||||
Err(err)
|
||||
}
|
||||
FramedState::Stopping => {
|
||||
self.disconnect(false);
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
49
actix-ioframe/src/error.rs
Normal file
49
actix-ioframe/src/error.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use std::fmt;
|
||||
|
||||
use actix_codec::{Decoder, Encoder};
|
||||
|
||||
/// Framed service errors
|
||||
pub enum ServiceError<E, U: Encoder + Decoder> {
|
||||
/// Inner service error
|
||||
Service(E),
|
||||
/// Encoder parse error
|
||||
Encoder(<U as Encoder>::Error),
|
||||
/// Decoder parse error
|
||||
Decoder(<U as Decoder>::Error),
|
||||
}
|
||||
|
||||
impl<E, U: Encoder + Decoder> From<E> for ServiceError<E, U> {
|
||||
fn from(err: E) -> Self {
|
||||
ServiceError::Service(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, U: Encoder + Decoder> fmt::Debug for ServiceError<E, U>
|
||||
where
|
||||
E: fmt::Debug,
|
||||
<U as Encoder>::Error: fmt::Debug,
|
||||
<U as Decoder>::Error: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
ServiceError::Service(ref e) => write!(fmt, "ServiceError::Service({:?})", e),
|
||||
ServiceError::Encoder(ref e) => write!(fmt, "ServiceError::Encoder({:?})", e),
|
||||
ServiceError::Decoder(ref e) => write!(fmt, "ServiceError::Encoder({:?})", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, U: Encoder + Decoder> fmt::Display for ServiceError<E, U>
|
||||
where
|
||||
E: fmt::Display,
|
||||
<U as Encoder>::Error: fmt::Debug,
|
||||
<U as Decoder>::Error: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
ServiceError::Service(ref e) => write!(fmt, "{}", e),
|
||||
ServiceError::Encoder(ref e) => write!(fmt, "{:?}", e),
|
||||
ServiceError::Decoder(ref e) => write!(fmt, "{:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
90
actix-ioframe/src/item.rs
Normal file
90
actix-ioframe/src/item.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
use std::cell::{Ref, RefMut};
|
||||
use std::fmt;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use actix_codec::{Decoder, Encoder};
|
||||
|
||||
use crate::sink::Sink;
|
||||
use crate::state::State;
|
||||
|
||||
pub struct Item<St, Codec: Encoder + Decoder> {
|
||||
state: State<St>,
|
||||
sink: Sink<<Codec as Encoder>::Item>,
|
||||
item: <Codec as Decoder>::Item,
|
||||
}
|
||||
|
||||
impl<St, Codec> Item<St, Codec>
|
||||
where
|
||||
Codec: Encoder + Decoder,
|
||||
{
|
||||
pub(crate) fn new(
|
||||
state: State<St>,
|
||||
sink: Sink<<Codec as Encoder>::Item>,
|
||||
item: <Codec as Decoder>::Item,
|
||||
) -> Self {
|
||||
Item { state, sink, item }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn state(&self) -> Ref<St> {
|
||||
self.state.get_ref()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn state_mut(&mut self) -> RefMut<St> {
|
||||
self.state.get_mut()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sink(&self) -> &Sink<<Codec as Encoder>::Item> {
|
||||
&self.sink
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn into_inner(self) -> <Codec as Decoder>::Item {
|
||||
self.item
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn into_parts(
|
||||
self,
|
||||
) -> (
|
||||
State<St>,
|
||||
Sink<<Codec as Encoder>::Item>,
|
||||
<Codec as Decoder>::Item,
|
||||
) {
|
||||
(self.state, self.sink, self.item)
|
||||
}
|
||||
}
|
||||
|
||||
impl<St, Codec> Deref for Item<St, Codec>
|
||||
where
|
||||
Codec: Encoder + Decoder,
|
||||
{
|
||||
type Target = <Codec as Decoder>::Item;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &<Codec as Decoder>::Item {
|
||||
&self.item
|
||||
}
|
||||
}
|
||||
|
||||
impl<St, Codec> DerefMut for Item<St, Codec>
|
||||
where
|
||||
Codec: Encoder + Decoder,
|
||||
{
|
||||
#[inline]
|
||||
fn deref_mut(&mut self) -> &mut <Codec as Decoder>::Item {
|
||||
&mut self.item
|
||||
}
|
||||
}
|
||||
|
||||
impl<St, Codec> fmt::Debug for Item<St, Codec>
|
||||
where
|
||||
Codec: Encoder + Decoder,
|
||||
<Codec as Decoder>::Item: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_tuple("FramedItem").field(&self.item).finish()
|
||||
}
|
||||
}
|
15
actix-ioframe/src/lib.rs
Normal file
15
actix-ioframe/src/lib.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
mod cell;
|
||||
mod connect;
|
||||
mod dispatcher;
|
||||
mod error;
|
||||
mod item;
|
||||
mod service;
|
||||
mod sink;
|
||||
mod state;
|
||||
|
||||
pub use self::connect::{Connect, ConnectResult};
|
||||
pub use self::error::ServiceError;
|
||||
pub use self::item::Item;
|
||||
pub use self::service::{Builder, NewServiceBuilder, ServiceBuilder};
|
||||
pub use self::sink::Sink;
|
||||
pub use self::state::State;
|
363
actix-ioframe/src/service.rs
Normal file
363
actix-ioframe/src/service.rs
Normal file
@@ -0,0 +1,363 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::rc::Rc;
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder};
|
||||
use actix_service::{IntoNewService, IntoService, NewService, Service};
|
||||
use futures::{Async, Future, Poll};
|
||||
|
||||
use crate::connect::{Connect, ConnectResult};
|
||||
use crate::dispatcher::FramedDispatcher;
|
||||
use crate::error::ServiceError;
|
||||
use crate::item::Item;
|
||||
use crate::state::State;
|
||||
|
||||
type RequestItem<S, U> = Item<S, U>;
|
||||
type ResponseItem<U> = Option<<U as Encoder>::Item>;
|
||||
|
||||
/// Service builder - structure that follows the builder pattern
|
||||
/// for building instances for framed services.
|
||||
pub struct Builder<St, Codec>(PhantomData<(St, Codec)>);
|
||||
|
||||
impl<St, Codec> Builder<St, Codec> {
|
||||
pub fn new() -> Builder<St, Codec> {
|
||||
Builder(PhantomData)
|
||||
}
|
||||
|
||||
/// Construct framed handler service with specified connect service
|
||||
pub fn service<Io, C, F>(self, connect: F) -> ServiceBuilder<St, C, Io, Codec>
|
||||
where
|
||||
F: IntoService<C>,
|
||||
Io: AsyncRead + AsyncWrite,
|
||||
C: Service<Request = Connect<Io>, Response = ConnectResult<Io, St, Codec>>,
|
||||
Codec: Decoder + Encoder,
|
||||
{
|
||||
ServiceBuilder {
|
||||
connect: connect.into_service(),
|
||||
disconnect: None,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct framed handler new service with specified connect service
|
||||
pub fn factory<Io, C, F>(self, connect: F) -> NewServiceBuilder<St, C, Io, Codec>
|
||||
where
|
||||
F: IntoNewService<C>,
|
||||
Io: AsyncRead + AsyncWrite,
|
||||
C: NewService<
|
||||
Config = (),
|
||||
Request = Connect<Io>,
|
||||
Response = ConnectResult<Io, St, Codec>,
|
||||
>,
|
||||
C::Error: 'static,
|
||||
C::Future: 'static,
|
||||
Codec: Decoder + Encoder,
|
||||
{
|
||||
NewServiceBuilder {
|
||||
connect: connect.into_new_service(),
|
||||
disconnect: None,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ServiceBuilder<St, C, Io, Codec> {
|
||||
connect: C,
|
||||
disconnect: Option<Rc<Fn(&mut St, bool)>>,
|
||||
_t: PhantomData<(St, Io, Codec)>,
|
||||
}
|
||||
|
||||
impl<St, C, Io, Codec> ServiceBuilder<St, C, Io, Codec>
|
||||
where
|
||||
St: 'static,
|
||||
Io: AsyncRead + AsyncWrite,
|
||||
C: Service<Request = Connect<Io>, Response = ConnectResult<Io, St, Codec>>,
|
||||
C::Error: 'static,
|
||||
Codec: Decoder + Encoder,
|
||||
<Codec as Encoder>::Item: 'static,
|
||||
<Codec as Encoder>::Error: std::fmt::Debug,
|
||||
{
|
||||
/// Callback to execute on disconnect
|
||||
///
|
||||
/// Second parameter indicates error occured during disconnect.
|
||||
pub fn disconnect<F, Out>(mut self, disconnect: F) -> Self
|
||||
where
|
||||
F: Fn(&mut St, bool) + 'static,
|
||||
{
|
||||
self.disconnect = Some(Rc::new(disconnect));
|
||||
self
|
||||
}
|
||||
|
||||
/// Provide stream items handler service and construct service factory.
|
||||
pub fn finish<F, T>(
|
||||
self,
|
||||
service: F,
|
||||
) -> impl Service<Request = Io, Response = (), Error = ServiceError<C::Error, Codec>>
|
||||
where
|
||||
F: IntoNewService<T>,
|
||||
T: NewService<
|
||||
Config = St,
|
||||
Request = RequestItem<St, Codec>,
|
||||
Response = ResponseItem<Codec>,
|
||||
Error = C::Error,
|
||||
InitError = C::Error,
|
||||
> + 'static,
|
||||
{
|
||||
FramedServiceImpl {
|
||||
connect: self.connect,
|
||||
handler: Rc::new(service.into_new_service()),
|
||||
disconnect: self.disconnect.clone(),
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NewServiceBuilder<St, C, Io, Codec> {
|
||||
connect: C,
|
||||
disconnect: Option<Rc<Fn(&mut St, bool)>>,
|
||||
_t: PhantomData<(St, Io, Codec)>,
|
||||
}
|
||||
|
||||
impl<St, C, Io, Codec> NewServiceBuilder<St, C, Io, Codec>
|
||||
where
|
||||
St: 'static,
|
||||
Io: AsyncRead + AsyncWrite,
|
||||
C: NewService<Config = (), Request = Connect<Io>, Response = ConnectResult<Io, St, Codec>>,
|
||||
C::Error: 'static,
|
||||
C::Future: 'static,
|
||||
Codec: Decoder + Encoder,
|
||||
<Codec as Encoder>::Item: 'static,
|
||||
<Codec as Encoder>::Error: std::fmt::Debug,
|
||||
{
|
||||
/// Callback to execute on disconnect
|
||||
///
|
||||
/// Second parameter indicates error occured during disconnect.
|
||||
pub fn disconnect<F>(mut self, disconnect: F) -> Self
|
||||
where
|
||||
F: Fn(&mut St, bool) + 'static,
|
||||
{
|
||||
self.disconnect = Some(Rc::new(disconnect));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn finish<F, T, Cfg>(
|
||||
self,
|
||||
service: F,
|
||||
) -> impl NewService<
|
||||
Config = Cfg,
|
||||
Request = Io,
|
||||
Response = (),
|
||||
Error = ServiceError<C::Error, Codec>,
|
||||
>
|
||||
where
|
||||
F: IntoNewService<T>,
|
||||
T: NewService<
|
||||
Config = St,
|
||||
Request = RequestItem<St, Codec>,
|
||||
Response = ResponseItem<Codec>,
|
||||
Error = C::Error,
|
||||
InitError = C::Error,
|
||||
> + 'static,
|
||||
{
|
||||
FramedService {
|
||||
connect: self.connect,
|
||||
handler: Rc::new(service.into_new_service()),
|
||||
disconnect: self.disconnect,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct FramedService<St, C, T, Io, Codec, Cfg> {
|
||||
connect: C,
|
||||
handler: Rc<T>,
|
||||
disconnect: Option<Rc<Fn(&mut St, bool)>>,
|
||||
_t: PhantomData<(St, Io, Codec, Cfg)>,
|
||||
}
|
||||
|
||||
impl<St, C, T, Io, Codec, Cfg> NewService for FramedService<St, C, T, Io, Codec, Cfg>
|
||||
where
|
||||
St: 'static,
|
||||
Io: AsyncRead + AsyncWrite,
|
||||
C: NewService<Config = (), Request = Connect<Io>, Response = ConnectResult<Io, St, Codec>>,
|
||||
C::Error: 'static,
|
||||
C::Future: 'static,
|
||||
T: NewService<
|
||||
Config = St,
|
||||
Request = RequestItem<St, Codec>,
|
||||
Response = ResponseItem<Codec>,
|
||||
Error = C::Error,
|
||||
InitError = C::Error,
|
||||
> + 'static,
|
||||
Codec: Decoder + Encoder,
|
||||
<Codec as Encoder>::Item: 'static,
|
||||
<Codec as Encoder>::Error: std::fmt::Debug,
|
||||
{
|
||||
type Config = Cfg;
|
||||
type Request = Io;
|
||||
type Response = ();
|
||||
type Error = ServiceError<C::Error, Codec>;
|
||||
type InitError = C::InitError;
|
||||
type Service = FramedServiceImpl<St, C::Service, T, Io, Codec>;
|
||||
type Future = Box<Future<Item = Self::Service, Error = Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: &Cfg) -> Self::Future {
|
||||
let handler = self.handler.clone();
|
||||
let disconnect = self.disconnect.clone();
|
||||
|
||||
// create connect service and then create service impl
|
||||
Box::new(
|
||||
self.connect
|
||||
.new_service(&())
|
||||
.map(move |connect| FramedServiceImpl {
|
||||
connect,
|
||||
handler,
|
||||
disconnect,
|
||||
_t: PhantomData,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FramedServiceImpl<St, C, T, Io, Codec> {
|
||||
connect: C,
|
||||
handler: Rc<T>,
|
||||
disconnect: Option<Rc<Fn(&mut St, bool)>>,
|
||||
_t: PhantomData<(St, Io, Codec)>,
|
||||
}
|
||||
|
||||
impl<St, C, T, Io, Codec> Service for FramedServiceImpl<St, C, T, Io, Codec>
|
||||
where
|
||||
Io: AsyncRead + AsyncWrite,
|
||||
C: Service<Request = Connect<Io>, Response = ConnectResult<Io, St, Codec>>,
|
||||
C::Error: 'static,
|
||||
T: NewService<
|
||||
Config = St,
|
||||
Request = RequestItem<St, Codec>,
|
||||
Response = ResponseItem<Codec>,
|
||||
Error = C::Error,
|
||||
InitError = C::Error,
|
||||
>,
|
||||
<<T as NewService>::Service as Service>::Future: 'static,
|
||||
Codec: Decoder + Encoder,
|
||||
<Codec as Encoder>::Item: 'static,
|
||||
<Codec as Encoder>::Error: std::fmt::Debug,
|
||||
{
|
||||
type Request = Io;
|
||||
type Response = ();
|
||||
type Error = ServiceError<C::Error, Codec>;
|
||||
type Future = FramedServiceImplResponse<St, Io, Codec, C, T>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
self.connect.poll_ready().map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Io) -> Self::Future {
|
||||
FramedServiceImplResponse {
|
||||
inner: FramedServiceImplResponseInner::Connect(
|
||||
self.connect.call(Connect::new(req)),
|
||||
self.handler.clone(),
|
||||
),
|
||||
disconnect: self.disconnect.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FramedServiceImplResponse<St, Io, Codec, C, T>
|
||||
where
|
||||
C: Service<Request = Connect<Io>, Response = ConnectResult<Io, St, Codec>>,
|
||||
C::Error: 'static,
|
||||
T: NewService<
|
||||
Config = St,
|
||||
Request = RequestItem<St, Codec>,
|
||||
Response = ResponseItem<Codec>,
|
||||
Error = C::Error,
|
||||
InitError = C::Error,
|
||||
>,
|
||||
<<T as NewService>::Service as Service>::Future: 'static,
|
||||
Io: AsyncRead + AsyncWrite,
|
||||
Codec: Encoder + Decoder,
|
||||
<Codec as Encoder>::Item: 'static,
|
||||
<Codec as Encoder>::Error: std::fmt::Debug,
|
||||
{
|
||||
inner: FramedServiceImplResponseInner<St, Io, Codec, C, T>,
|
||||
disconnect: Option<Rc<Fn(&mut St, bool)>>,
|
||||
}
|
||||
|
||||
enum FramedServiceImplResponseInner<St, Io, Codec, C, T>
|
||||
where
|
||||
C: Service<Request = Connect<Io>, Response = ConnectResult<Io, St, Codec>>,
|
||||
C::Error: 'static,
|
||||
T: NewService<
|
||||
Config = St,
|
||||
Request = RequestItem<St, Codec>,
|
||||
Response = ResponseItem<Codec>,
|
||||
Error = C::Error,
|
||||
InitError = C::Error,
|
||||
>,
|
||||
<<T as NewService>::Service as Service>::Future: 'static,
|
||||
Io: AsyncRead + AsyncWrite,
|
||||
Codec: Encoder + Decoder,
|
||||
<Codec as Encoder>::Item: 'static,
|
||||
<Codec as Encoder>::Error: std::fmt::Debug,
|
||||
{
|
||||
Connect(C::Future, Rc<T>),
|
||||
Handler(T::Future, Option<ConnectResult<Io, St, Codec>>),
|
||||
Dispatcher(FramedDispatcher<St, T::Service, Io, Codec>),
|
||||
}
|
||||
|
||||
impl<St, Io, Codec, C, T> Future for FramedServiceImplResponse<St, Io, Codec, C, T>
|
||||
where
|
||||
C: Service<Request = Connect<Io>, Response = ConnectResult<Io, St, Codec>>,
|
||||
C::Error: 'static,
|
||||
T: NewService<
|
||||
Config = St,
|
||||
Request = RequestItem<St, Codec>,
|
||||
Response = ResponseItem<Codec>,
|
||||
Error = C::Error,
|
||||
InitError = C::Error,
|
||||
>,
|
||||
<<T as NewService>::Service as Service>::Future: 'static,
|
||||
Io: AsyncRead + AsyncWrite,
|
||||
Codec: Encoder + Decoder,
|
||||
<Codec as Encoder>::Item: 'static,
|
||||
<Codec as Encoder>::Error: std::fmt::Debug,
|
||||
{
|
||||
type Item = ();
|
||||
type Error = ServiceError<C::Error, Codec>;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
match self.inner {
|
||||
FramedServiceImplResponseInner::Connect(ref mut fut, ref handler) => {
|
||||
match fut.poll()? {
|
||||
Async::Ready(res) => {
|
||||
self.inner = FramedServiceImplResponseInner::Handler(
|
||||
handler.new_service(&res.state),
|
||||
Some(res),
|
||||
);
|
||||
self.poll()
|
||||
}
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
FramedServiceImplResponseInner::Handler(ref mut fut, ref mut res) => {
|
||||
match fut.poll()? {
|
||||
Async::Ready(handler) => {
|
||||
let res = res.take().unwrap();
|
||||
self.inner =
|
||||
FramedServiceImplResponseInner::Dispatcher(FramedDispatcher::new(
|
||||
res.framed,
|
||||
State::new(res.state),
|
||||
handler,
|
||||
res.rx,
|
||||
res.sink,
|
||||
self.disconnect.clone(),
|
||||
));
|
||||
self.poll()
|
||||
}
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
FramedServiceImplResponseInner::Dispatcher(ref mut fut) => fut.poll(),
|
||||
}
|
||||
}
|
||||
}
|
44
actix-ioframe/src/sink.rs
Normal file
44
actix-ioframe/src/sink.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
use std::fmt;
|
||||
|
||||
use futures::unsync::{mpsc, oneshot};
|
||||
use futures::Future;
|
||||
|
||||
use crate::dispatcher::FramedMessage;
|
||||
|
||||
pub struct Sink<T>(mpsc::UnboundedSender<FramedMessage<T>>);
|
||||
|
||||
impl<T> Clone for Sink<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Sink(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Sink<T> {
|
||||
pub(crate) fn new(tx: mpsc::UnboundedSender<FramedMessage<T>>) -> Self {
|
||||
Sink(tx)
|
||||
}
|
||||
|
||||
/// Close connection
|
||||
pub fn close(&self) {
|
||||
let _ = self.0.unbounded_send(FramedMessage::Close);
|
||||
}
|
||||
|
||||
/// Close connection
|
||||
pub fn wait_close(&self) -> impl Future<Item = (), Error = ()> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let _ = self.0.unbounded_send(FramedMessage::WaitClose(tx));
|
||||
|
||||
rx.map_err(|_| ())
|
||||
}
|
||||
|
||||
/// Send item
|
||||
pub fn send(&self, item: T) {
|
||||
let _ = self.0.unbounded_send(FramedMessage::Message(item));
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for Sink<T> {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt.debug_struct("Sink").finish()
|
||||
}
|
||||
}
|
30
actix-ioframe/src/state.rs
Normal file
30
actix-ioframe/src/state.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use std::cell::{Ref, RefCell, RefMut};
|
||||
use std::rc::Rc;
|
||||
|
||||
/// Connection state
|
||||
///
|
||||
/// Connection state is an arbitrary data attached to the each incoming message.
|
||||
#[derive(Debug)]
|
||||
pub struct State<T>(Rc<RefCell<T>>);
|
||||
|
||||
impl<T> State<T> {
|
||||
pub(crate) fn new(st: T) -> Self {
|
||||
State(Rc::new(RefCell::new(st)))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_ref(&self) -> Ref<T> {
|
||||
self.0.borrow()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_mut(&mut self) -> RefMut<T> {
|
||||
self.0.borrow_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for State<T> {
|
||||
fn clone(&self) -> Self {
|
||||
State(self.0.clone())
|
||||
}
|
||||
}
|
60
actix-ioframe/tests/test_server.rs
Normal file
60
actix-ioframe/tests/test_server.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use actix_codec::BytesCodec;
|
||||
use actix_server_config::Io;
|
||||
use actix_service::{new_apply_fn, Service};
|
||||
use actix_test_server::TestServer;
|
||||
use futures::Future;
|
||||
use tokio_tcp::TcpStream;
|
||||
use tokio_timer::sleep;
|
||||
|
||||
use actix_ioframe::{Builder, Connect};
|
||||
|
||||
struct State;
|
||||
|
||||
#[test]
|
||||
fn test_disconnect() -> std::io::Result<()> {
|
||||
let disconnect = Arc::new(AtomicBool::new(false));
|
||||
let disconnect1 = disconnect.clone();
|
||||
|
||||
let mut srv = TestServer::with(move || {
|
||||
let disconnect1 = disconnect1.clone();
|
||||
|
||||
new_apply_fn(
|
||||
Builder::new()
|
||||
.factory(|conn: Connect<_>| Ok(conn.codec(BytesCodec).state(State)))
|
||||
.disconnect(move |_, _| {
|
||||
disconnect1.store(true, Ordering::Relaxed);
|
||||
})
|
||||
.finish(|_t| Ok(None)),
|
||||
|io: Io<TcpStream>, srv| srv.call(io.into_parts().0),
|
||||
)
|
||||
});
|
||||
|
||||
let mut client = Builder::new()
|
||||
.service(|conn: Connect<_>| {
|
||||
let conn = conn.codec(BytesCodec).state(State);
|
||||
conn.sink().close();
|
||||
Ok(conn)
|
||||
})
|
||||
.finish(|_t| Ok(None));
|
||||
|
||||
let conn = srv
|
||||
.block_on(
|
||||
actix_connect::default_connector()
|
||||
.call(actix_connect::Connect::with(String::new(), srv.addr())),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
srv.block_on(client.call(conn.into_parts().0)).unwrap();
|
||||
let _ = srv.block_on(
|
||||
sleep(Duration::from_millis(100))
|
||||
.map(|_| ())
|
||||
.map_err(|_| ()),
|
||||
);
|
||||
assert!(disconnect.load(Ordering::Relaxed));
|
||||
|
||||
Ok(())
|
||||
}
|
21
actix-lets-encrypt/Cargo.toml
Normal file
21
actix-lets-encrypt/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "actix-lets-encrypt"
|
||||
version = "0.1.0"
|
||||
authors = ["Jordan Deitch <jd@rsa.pub>"]
|
||||
description = "Actix Let's Encrypt"
|
||||
keywords = ["network", "framework", "async", "futures"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-net.git"
|
||||
documentation = "https://docs.rs/actix-lets-encrypt/"
|
||||
categories = ["network-programming", "asynchronous"]
|
||||
license = "MIT/Apache-2.0"
|
||||
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
|
||||
edition = "2018"
|
||||
workspace = ".."
|
||||
|
||||
[lib]
|
||||
name = "actix_lets_encrypt"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
acme-client = {version = "0.5", default-features = false}
|
0
actix-lets-encrypt/src/authorization.rs
Normal file
0
actix-lets-encrypt/src/authorization.rs
Normal file
64
actix-lets-encrypt/src/certificate_signer.rs
Normal file
64
actix-lets-encrypt/src/certificate_signer.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
use acme_client::Directory;
|
||||
|
||||
struct CertificateError {
|
||||
message: String,
|
||||
}
|
||||
|
||||
impl std::error::Error for CertificateError {
|
||||
fn description(&self) -> &str { self.message.as_str() }
|
||||
fn cause(&self) -> Option<&dyn std::error::Error> { None }
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
|
||||
}
|
||||
|
||||
impl std::fmt::Display for CertificateError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "An Error Occurred, Please Try Again!")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for CertificateError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "{{ file: {}, line: {} }}", file!(), line!())
|
||||
}
|
||||
}
|
||||
|
||||
impl CertificateError {
|
||||
fn new(message: String) -> Self {
|
||||
CertificateError { message }
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<acme_client::error::Error> for CertificateError {
|
||||
fn from(e: acme_client::error::Error) -> Self {
|
||||
return CertificateError::new(e.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
struct CertificateRequest<'a> {
|
||||
domain: &'a str,
|
||||
email: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> CertificateRequest<'a> {
|
||||
fn new(email: &'a str, domain: &'a str) -> Self {
|
||||
return CertificateRequest { domain, email };
|
||||
}
|
||||
|
||||
fn sign(self: &Self) -> Result<(), CertificateError> {
|
||||
let directory = Directory::lets_encrypt()?;
|
||||
let account = directory.account_registration()
|
||||
.email(self.email)
|
||||
.register()?;
|
||||
let authorization = account.authorization(self.domain)?;
|
||||
|
||||
let http_challenge = authorization.get_http_challenge().ok_or("HTTP challenge failed")?;
|
||||
http_challenge.save_key_authorization("/var/www")?;
|
||||
http_challenge.validate()?;
|
||||
|
||||
let cert = account.certificate_signer(&[self.domain]).sign_certificate()?;
|
||||
cert.save_signed_certificate("certificate.pem")?;
|
||||
cert.save_private_key("certificate.key")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
2
actix-lets-encrypt/src/lib.rs
Normal file
2
actix-lets-encrypt/src/lib.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
mod certificate_signer;
|
||||
mod authorization;
|
1
actix-macros/.gitignore
vendored
1
actix-macros/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
/wip
|
@@ -1,46 +0,0 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 0.2.3 - 2021-10-19
|
||||
* Fix test macro in presence of other imports named "test". [#399]
|
||||
|
||||
[#399]: https://github.com/actix/actix-net/pull/399
|
||||
|
||||
|
||||
## 0.2.2 - 2021-10-14
|
||||
* Improve error recovery potential when macro input is invalid. [#391]
|
||||
* Allow custom `System`s on test macro. [#391]
|
||||
|
||||
[#391]: https://github.com/actix/actix-net/pull/391
|
||||
|
||||
|
||||
## 0.2.1 - 2021-02-02
|
||||
* Add optional argument `system` to `main` macro which can be used to specify the path to `actix_rt::System` (useful for re-exports). [#363]
|
||||
|
||||
[#363]: https://github.com/actix/actix-net/pull/363
|
||||
|
||||
|
||||
## 0.2.0 - 2021-02-02
|
||||
* Update to latest `actix_rt::System::new` signature. [#261]
|
||||
|
||||
[#261]: https://github.com/actix/actix-net/pull/261
|
||||
|
||||
|
||||
## 0.2.0-beta.1 - 2021-01-09
|
||||
* Remove `actix-reexport` feature. [#218]
|
||||
|
||||
[#218]: https://github.com/actix/actix-net/pull/218
|
||||
|
||||
|
||||
## 0.1.3 - 2020-12-03
|
||||
* Add `actix-reexport` feature. [#218]
|
||||
|
||||
[#218]: https://github.com/actix/actix-net/pull/218
|
||||
|
||||
|
||||
## 0.1.2 - 2020-05-18
|
||||
* Forward actix_rt::test arguments to test function [#127]
|
||||
|
||||
[#127]: https://github.com/actix/actix-net/pull/127
|
@@ -1,27 +0,0 @@
|
||||
[package]
|
||||
name = "actix-macros"
|
||||
version = "0.2.3"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Ibraheem Ahmed <ibrah1440@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
]
|
||||
description = "Macros for Actix system and runtime"
|
||||
repository = "https://github.com/actix/actix-net.git"
|
||||
categories = ["network-programming", "asynchronous"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
quote = "1.0.3"
|
||||
syn = { version = "^1", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "2.0.0"
|
||||
|
||||
futures-util = { version = "0.3.7", default-features = false }
|
||||
rustversion = "1"
|
||||
trybuild = "1"
|
@@ -1,202 +0,0 @@
|
||||
//! Macros for Actix system and runtime.
|
||||
//!
|
||||
//! The [`actix-rt`](https://docs.rs/actix-rt) crate must be available for macro output to compile.
|
||||
//!
|
||||
//! # Entry-point
|
||||
//! See docs for the [`#[main]`](macro@main) macro.
|
||||
//!
|
||||
//! # Tests
|
||||
//! See docs for the [`#[test]`](macro@test) macro.
|
||||
|
||||
#![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 entry-point function to be executed by Actix system.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// #[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(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let mut input = match syn::parse::<syn::ItemFn>(item.clone()) {
|
||||
Ok(input) => input,
|
||||
// on parse err, make IDEs happy; see fn docs
|
||||
Err(err) => return input_and_compile_error(item, err),
|
||||
};
|
||||
|
||||
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
|
||||
|
||||
let attrs = &input.attrs;
|
||||
let vis = &input.vis;
|
||||
let sig = &mut input.sig;
|
||||
let body = &input.block;
|
||||
|
||||
if sig.asyncness.is_none() {
|
||||
return syn::Error::new_spanned(
|
||||
sig.fn_token,
|
||||
"the async keyword is missing from the function declaration",
|
||||
)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
|
||||
let mut system = syn::parse_str::<syn::Path>("::actix_rt::System").unwrap();
|
||||
|
||||
for arg in &args {
|
||||
match arg {
|
||||
syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue {
|
||||
lit: syn::Lit::Str(lit),
|
||||
path,
|
||||
..
|
||||
})) => match path
|
||||
.get_ident()
|
||||
.map(|i| i.to_string().to_lowercase())
|
||||
.as_deref()
|
||||
{
|
||||
Some("system") => match lit.parse() {
|
||||
Ok(path) => system = path,
|
||||
Err(_) => {
|
||||
return syn::Error::new_spanned(lit, "Expected path")
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
return syn::Error::new_spanned(arg, "Unknown attribute specified")
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
return syn::Error::new_spanned(arg, "Unknown attribute specified")
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sig.asyncness = None;
|
||||
|
||||
(quote! {
|
||||
#(#attrs)*
|
||||
#vis #sig {
|
||||
<#system>::new().block_on(async move { #body })
|
||||
}
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Marks async test function to be executed in an Actix system.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// #[actix_rt::test]
|
||||
/// async fn my_test() {
|
||||
/// assert!(true);
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let mut input = match syn::parse::<syn::ItemFn>(item.clone()) {
|
||||
Ok(input) => input,
|
||||
// on parse err, make IDEs happy; see fn docs
|
||||
Err(err) => return input_and_compile_error(item, err),
|
||||
};
|
||||
|
||||
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
|
||||
|
||||
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,
|
||||
"the async keyword is missing from the function declaration",
|
||||
)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
|
||||
sig.asyncness = None;
|
||||
|
||||
let missing_test_attr = if has_test_attr {
|
||||
quote! {}
|
||||
} else {
|
||||
quote! { #[::core::prelude::v1::test] }
|
||||
};
|
||||
|
||||
let mut system = syn::parse_str::<syn::Path>("::actix_rt::System").unwrap();
|
||||
|
||||
for arg in &args {
|
||||
match arg {
|
||||
syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue {
|
||||
lit: syn::Lit::Str(lit),
|
||||
path,
|
||||
..
|
||||
})) => match path
|
||||
.get_ident()
|
||||
.map(|i| i.to_string().to_lowercase())
|
||||
.as_deref()
|
||||
{
|
||||
Some("system") => match lit.parse() {
|
||||
Ok(path) => system = path,
|
||||
Err(_) => {
|
||||
return syn::Error::new_spanned(lit, "Expected path")
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
return syn::Error::new_spanned(arg, "Unknown attribute specified")
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
return syn::Error::new_spanned(arg, "Unknown attribute specified")
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(quote! {
|
||||
#missing_test_attr
|
||||
#(#attrs)*
|
||||
#vis #sig {
|
||||
<#system>::new().block_on(async { #body })
|
||||
}
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Converts the error to a token stream and appends it to the original input.
|
||||
///
|
||||
/// Returning the original input in addition to the error is good for IDEs which can gracefully
|
||||
/// recover and show more precise errors within the macro body.
|
||||
///
|
||||
/// See <https://github.com/rust-analyzer/rust-analyzer/issues/10468> for more info.
|
||||
fn input_and_compile_error(mut item: TokenStream, err: syn::Error) -> TokenStream {
|
||||
let compile_err = TokenStream::from(err.to_compile_error());
|
||||
item.extend(compile_err);
|
||||
item
|
||||
}
|
@@ -1,18 +0,0 @@
|
||||
#[rustversion::stable(1.46)] // MSRV
|
||||
#[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/main-03-fn-params.rs");
|
||||
t.pass("tests/trybuild/main-04-system-path.rs");
|
||||
t.compile_fail("tests/trybuild/main-05-system-expect-path.rs");
|
||||
t.compile_fail("tests/trybuild/main-06-unknown-attr.rs");
|
||||
|
||||
t.pass("tests/trybuild/test-01-basic.rs");
|
||||
t.pass("tests/trybuild/test-02-keep-attrs.rs");
|
||||
t.compile_fail("tests/trybuild/test-03-only-async.rs");
|
||||
t.pass("tests/trybuild/test-04-system-path.rs");
|
||||
t.compile_fail("tests/trybuild/test-05-system-expect-path.rs");
|
||||
t.compile_fail("tests/trybuild/test-06-unknown-attr.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: the async keyword is missing from the function declaration
|
||||
--> $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::main]
|
||||
async fn main2(_param: bool) {
|
||||
futures_util::future::ready(()).await
|
||||
}
|
||||
|
||||
fn main() {}
|
@@ -1,8 +0,0 @@
|
||||
mod system {
|
||||
pub use actix_rt::System as MySystem;
|
||||
}
|
||||
|
||||
#[actix_rt::main(system = "system::MySystem")]
|
||||
async fn main() {
|
||||
futures_util::future::ready(()).await
|
||||
}
|
@@ -1,4 +0,0 @@
|
||||
#[actix_rt::main(system = "!@#*&")]
|
||||
async fn main2() {}
|
||||
|
||||
fn main() {}
|
@@ -1,5 +0,0 @@
|
||||
error: Expected path
|
||||
--> $DIR/main-05-system-expect-path.rs:1:27
|
||||
|
|
||||
1 | #[actix_rt::main(system = "!@#*&")]
|
||||
| ^^^^^^^
|
@@ -1,7 +0,0 @@
|
||||
#[actix_rt::main(foo = "bar")]
|
||||
async fn async_main() {}
|
||||
|
||||
#[actix_rt::main(bar::baz)]
|
||||
async fn async_main2() {}
|
||||
|
||||
fn main() {}
|
@@ -1,11 +0,0 @@
|
||||
error: Unknown attribute specified
|
||||
--> $DIR/main-06-unknown-attr.rs:1:18
|
||||
|
|
||||
1 | #[actix_rt::main(foo = "bar")]
|
||||
| ^^^^^^^^^^^
|
||||
|
||||
error: Unknown attribute specified
|
||||
--> $DIR/main-06-unknown-attr.rs:4:18
|
||||
|
|
||||
4 | #[actix_rt::main(bar::baz)]
|
||||
| ^^^^^^^^
|
@@ -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,6 +0,0 @@
|
||||
#[actix_rt::test]
|
||||
fn my_test() {
|
||||
futures_util::future::ready(()).await
|
||||
}
|
||||
|
||||
fn main() {}
|
@@ -1,5 +0,0 @@
|
||||
error: the async keyword is missing from the function declaration
|
||||
--> $DIR/test-03-only-async.rs:2:1
|
||||
|
|
||||
2 | fn my_test() {
|
||||
| ^^
|
@@ -1,10 +0,0 @@
|
||||
mod system {
|
||||
pub use actix_rt::System as MySystem;
|
||||
}
|
||||
|
||||
#[actix_rt::test(system = "system::MySystem")]
|
||||
async fn my_test() {
|
||||
futures_util::future::ready(()).await
|
||||
}
|
||||
|
||||
fn main() {}
|
@@ -1,4 +0,0 @@
|
||||
#[actix_rt::test(system = "!@#*&")]
|
||||
async fn my_test() {}
|
||||
|
||||
fn main() {}
|
@@ -1,5 +0,0 @@
|
||||
error: Expected path
|
||||
--> $DIR/test-05-system-expect-path.rs:1:27
|
||||
|
|
||||
1 | #[actix_rt::test(system = "!@#*&")]
|
||||
| ^^^^^^^
|
@@ -1,7 +0,0 @@
|
||||
#[actix_rt::test(foo = "bar")]
|
||||
async fn my_test_1() {}
|
||||
|
||||
#[actix_rt::test(bar::baz)]
|
||||
async fn my_test_2() {}
|
||||
|
||||
fn main() {}
|
@@ -1,11 +0,0 @@
|
||||
error: Unknown attribute specified
|
||||
--> $DIR/test-06-unknown-attr.rs:1:18
|
||||
|
|
||||
1 | #[actix_rt::test(foo = "bar")]
|
||||
| ^^^^^^^^^^^
|
||||
|
||||
error: Unknown attribute specified
|
||||
--> $DIR/test-06-unknown-attr.rs:4:18
|
||||
|
|
||||
4 | #[actix_rt::test(bar::baz)]
|
||||
| ^^^^^^^^
|
@@ -1,166 +1,44 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
## [0.2.4] - 2019-07-17
|
||||
|
||||
### Changed
|
||||
|
||||
## 2.3.0 - 2021-10-11
|
||||
* The `spawn` method can now resolve with non-unit outputs. [#369]
|
||||
* Add experimental (semver-exempt) `io-uring` feature for enabling async file I/O on linux. [#374]
|
||||
|
||||
[#369]: https://github.com/actix/actix-net/pull/369
|
||||
[#374]: https://github.com/actix/actix-net/pull/374
|
||||
|
||||
|
||||
## 2.2.0 - 2021-03-29
|
||||
* **BREAKING** `ActixStream::{poll_read_ready, poll_write_ready}` methods now return
|
||||
`Ready` object in ok variant. [#293]
|
||||
* Breakage is acceptable since `ActixStream` was not intended to be public.
|
||||
|
||||
[#293]: https://github.com/actix/actix-net/pull/293
|
||||
|
||||
|
||||
## 2.1.0 - 2021-02-24
|
||||
* Add `ActixStream` extension trait to include readiness methods. [#276]
|
||||
* Re-export `tokio::net::TcpSocket` in `net` module [#282]
|
||||
|
||||
[#276]: https://github.com/actix/actix-net/pull/276
|
||||
[#282]: https://github.com/actix/actix-net/pull/282
|
||||
|
||||
|
||||
## 2.0.2 - 2021-02-06
|
||||
* Add `Arbiter::handle` to get a handle of an owned Arbiter. [#274]
|
||||
* Add `System::try_current` for situations where actix may or may not be running a System. [#275]
|
||||
|
||||
[#274]: https://github.com/actix/actix-net/pull/274
|
||||
[#275]: https://github.com/actix/actix-net/pull/275
|
||||
|
||||
|
||||
## 2.0.1 - 2021-02-06
|
||||
* Expose `JoinError` from Tokio. [#271]
|
||||
|
||||
[#271]: https://github.com/actix/actix-net/pull/271
|
||||
|
||||
|
||||
## 2.0.0 - 2021-02-02
|
||||
* Remove all Arbiter-local storage methods. [#262]
|
||||
* Re-export `tokio::pin`. [#262]
|
||||
|
||||
[#262]: https://github.com/actix/actix-net/pull/262
|
||||
|
||||
|
||||
## 2.0.0-beta.3 - 2021-01-31
|
||||
* Remove `run_in_tokio`, `attach_to_tokio` and `AsyncSystemRunner`. [#253]
|
||||
* Return `JoinHandle` from `actix_rt::spawn`. [#253]
|
||||
* Remove old `Arbiter::spawn`. Implementation is now inlined into `actix_rt::spawn`. [#253]
|
||||
* Rename `Arbiter::{send => spawn}` and `Arbiter::{exec_fn => spawn_fn}`. [#253]
|
||||
* Remove `Arbiter::exec`. [#253]
|
||||
* Remove deprecated `Arbiter::local_join` and `Arbiter::is_running`. [#253]
|
||||
* `Arbiter::spawn` now accepts !Unpin futures. [#256]
|
||||
* `System::new` no longer takes arguments. [#257]
|
||||
* Remove `System::with_current`. [#257]
|
||||
* Remove `Builder`. [#257]
|
||||
* Add `System::with_init` as replacement for `Builder::run`. [#257]
|
||||
* Rename `System::{is_set => is_registered}`. [#257]
|
||||
* Add `ArbiterHandle` for sending messages to non-current-thread arbiters. [#257].
|
||||
* `System::arbiter` now returns an `&ArbiterHandle`. [#257]
|
||||
* `Arbiter::current` now returns an `ArbiterHandle` instead. [#257]
|
||||
* `Arbiter::join` now takes self by value. [#257]
|
||||
|
||||
[#253]: https://github.com/actix/actix-net/pull/253
|
||||
[#254]: https://github.com/actix/actix-net/pull/254
|
||||
[#256]: https://github.com/actix/actix-net/pull/256
|
||||
[#257]: https://github.com/actix/actix-net/pull/257
|
||||
|
||||
|
||||
## 2.0.0-beta.2 - 2021-01-09
|
||||
* Add `task` mod with re-export of `tokio::task::{spawn_blocking, yield_now, JoinHandle}` [#245]
|
||||
* Add default "macros" feature to allow faster compile times when using `default-features=false`.
|
||||
|
||||
[#245]: https://github.com/actix/actix-net/pull/245
|
||||
|
||||
|
||||
## 2.0.0-beta.1 - 2020-12-28
|
||||
* Add `System::attach_to_tokio` method. [#173]
|
||||
* 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]
|
||||
* 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
|
||||
* 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 (YANKED)
|
||||
* 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
|
||||
* Migrate to tokio 0.2
|
||||
* Fix compilation on non-unix platforms
|
||||
|
||||
|
||||
## 1.0.0-alpha.2 - 2019-12-02
|
||||
* 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
|
||||
* Migrate to std::future and tokio 0.2
|
||||
|
||||
|
||||
## 0.2.6 - 2019-11-14
|
||||
* Allow to join arbiter's thread. #60
|
||||
* Fix arbiter's thread panic message.
|
||||
|
||||
|
||||
## 0.2.5 - 2019-09-02
|
||||
* Add arbiter specific storage
|
||||
|
||||
|
||||
## 0.2.4 - 2019-07-17
|
||||
* Avoid a copy of the Future when initializing the Box. #29
|
||||
|
||||
|
||||
## 0.2.3 - 2019-06-22
|
||||
* Allow to start System using existing CurrentThread Handle #22
|
||||
## [0.2.3] - 2019-06-22
|
||||
|
||||
### Added
|
||||
|
||||
* Allow to start System using exsiting CurrentThread Handle #22
|
||||
|
||||
|
||||
## 0.2.2 - 2019-03-28
|
||||
## [0.2.2] - 2019-03-28
|
||||
|
||||
### Changed
|
||||
|
||||
* Moved `blocking` module to `actix-threadpool` crate
|
||||
|
||||
|
||||
## 0.2.1 - 2019-03-11
|
||||
## [0.2.1] - 2019-03-11
|
||||
|
||||
### Added
|
||||
|
||||
* Added `blocking` module
|
||||
* Added `Arbiter::exec_fn` - execute fn on the arbiter's thread
|
||||
* Added `Arbiter::exec` - execute fn on the arbiter's thread and wait result
|
||||
|
||||
* 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
|
||||
## [0.2.0] - 2019-03-06
|
||||
|
||||
* `run` method returns `io::Result<()>`
|
||||
|
||||
* Removed `Handle`
|
||||
|
||||
|
||||
## 0.1.0 - 2018-12-09
|
||||
## [0.1.0] - 2018-12-09
|
||||
|
||||
* Initial release
|
||||
|
@@ -1,36 +1,27 @@
|
||||
[package]
|
||||
name = "actix-rt"
|
||||
version = "2.3.0"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
]
|
||||
description = "Tokio-based single-threaded async runtime for the Actix ecosystem"
|
||||
keywords = ["async", "futures", "io", "runtime"]
|
||||
version = "0.2.4"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix runtime"
|
||||
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"
|
||||
license = "MIT/Apache-2.0"
|
||||
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
|
||||
edition = "2018"
|
||||
workspace = ".."
|
||||
|
||||
[lib]
|
||||
name = "actix_rt"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = ["macros"]
|
||||
macros = ["actix-macros"]
|
||||
io-uring = ["tokio-uring"]
|
||||
|
||||
[dependencies]
|
||||
actix-macros = { version = "0.2.3", optional = true }
|
||||
|
||||
futures-core = { version = "0.3", default-features = false }
|
||||
tokio = { version = "1.5.1", features = ["rt", "net", "parking_lot", "signal", "sync", "time"] }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
tokio-uring = { version = "0.1", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.5.1", features = ["full"] }
|
||||
hyper = { version = "0.14", default-features = false, features = ["server", "tcp", "http1"] }
|
||||
actix-threadpool = "0.1.1"
|
||||
futures = "0.1.25"
|
||||
tokio-current-thread = "0.1"
|
||||
tokio-executor = "0.1.5"
|
||||
tokio-reactor = "0.1.7"
|
||||
tokio-timer = "0.2.8"
|
||||
copyless = "0.1.4"
|
||||
|
@@ -1,14 +0,0 @@
|
||||
# actix-rt
|
||||
|
||||
> Tokio-based single-threaded async runtime for the Actix ecosystem.
|
||||
|
||||
[](https://crates.io/crates/actix-rt)
|
||||
[](https://docs.rs/actix-rt/2.3.0)
|
||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
||||

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

|
||||
[](https://discord.gg/WghFtEH6Hb)
|
||||
|
||||
See crate documentation for more: https://docs.rs/actix-rt.
|
@@ -1,28 +0,0 @@
|
||||
use hyper::service::{make_service_fn, service_fn};
|
||||
use hyper::{Body, Request, Response, Server};
|
||||
use std::convert::Infallible;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
async fn handle(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
|
||||
Ok(Response::new(Body::from("Hello World")))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
actix_rt::System::with_tokio_rt(|| {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
})
|
||||
.block_on(async {
|
||||
let make_service =
|
||||
make_service_fn(|_conn| async { Ok::<_, Infallible>(service_fn(handle)) });
|
||||
|
||||
let server =
|
||||
Server::bind(&SocketAddr::from(([127, 0, 0, 1], 3000))).serve(make_service);
|
||||
|
||||
if let Err(e) = server.await {
|
||||
eprintln!("server error: {}", e);
|
||||
}
|
||||
})
|
||||
}
|
@@ -1,60 +0,0 @@
|
||||
//! An example on how to build a multi-thread tokio runtime for Actix System.
|
||||
//! Then spawn async task that can make use of work stealing of tokio runtime.
|
||||
|
||||
use actix_rt::System;
|
||||
|
||||
fn main() {
|
||||
System::with_tokio_rt(|| {
|
||||
// build system with a multi-thread tokio runtime.
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(2)
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
})
|
||||
.block_on(async_main());
|
||||
}
|
||||
|
||||
// async main function that acts like #[actix_web::main] or #[tokio::main]
|
||||
async fn async_main() {
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
|
||||
// get a handle to system arbiter and spawn async task on it
|
||||
System::current().arbiter().spawn(async {
|
||||
// use tokio::spawn to get inside the context of multi thread tokio runtime
|
||||
let h1 = tokio::spawn(async {
|
||||
println!("thread id is {:?}", std::thread::current().id());
|
||||
std::thread::sleep(std::time::Duration::from_secs(2));
|
||||
});
|
||||
|
||||
// work stealing occurs for this task spawn
|
||||
let h2 = tokio::spawn(async {
|
||||
println!("thread id is {:?}", std::thread::current().id());
|
||||
});
|
||||
|
||||
h1.await.unwrap();
|
||||
h2.await.unwrap();
|
||||
let _ = tx.send(());
|
||||
});
|
||||
|
||||
rx.await.unwrap();
|
||||
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
let now = std::time::Instant::now();
|
||||
|
||||
// without additional tokio::spawn, all spawned tasks run on single thread
|
||||
System::current().arbiter().spawn(async {
|
||||
println!("thread id is {:?}", std::thread::current().id());
|
||||
std::thread::sleep(std::time::Duration::from_secs(2));
|
||||
let _ = tx.send(());
|
||||
});
|
||||
|
||||
// previous spawn task has blocked the system arbiter thread
|
||||
// so this task will wait for 2 seconds until it can be run
|
||||
System::current().arbiter().spawn(async move {
|
||||
println!("thread id is {:?}", std::thread::current().id());
|
||||
assert!(now.elapsed() > std::time::Duration::from_secs(2));
|
||||
});
|
||||
|
||||
rx.await.unwrap();
|
||||
}
|
@@ -1,312 +1,319 @@
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
fmt,
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
sync::atomic::{AtomicUsize, Ordering},
|
||||
task::{Context, Poll},
|
||||
thread,
|
||||
};
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::{fmt, thread};
|
||||
|
||||
use futures_core::ready;
|
||||
use tokio::sync::mpsc;
|
||||
use futures::sync::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
|
||||
use futures::sync::oneshot::{channel, Canceled, Sender};
|
||||
use futures::{future, Async, Future, IntoFuture, Poll, Stream};
|
||||
use tokio_current_thread::spawn;
|
||||
|
||||
use crate::system::{System, SystemCommand};
|
||||
use crate::builder::Builder;
|
||||
use crate::system::System;
|
||||
|
||||
use copyless::BoxHelper;
|
||||
|
||||
thread_local!(
|
||||
static ADDR: RefCell<Option<Arbiter>> = RefCell::new(None);
|
||||
static RUNNING: Cell<bool> = Cell::new(false);
|
||||
static Q: RefCell<Vec<Box<Future<Item = (), Error = ()>>>> = RefCell::new(Vec::new());
|
||||
);
|
||||
|
||||
pub(crate) static COUNT: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
thread_local!(
|
||||
static HANDLE: RefCell<Option<ArbiterHandle>> = RefCell::new(None);
|
||||
);
|
||||
|
||||
pub(crate) enum ArbiterCommand {
|
||||
Stop,
|
||||
Execute(Pin<Box<dyn Future<Output = ()> + Send>>),
|
||||
Execute(Box<Future<Item = (), Error = ()> + Send>),
|
||||
ExecuteFn(Box<FnExec>),
|
||||
}
|
||||
|
||||
impl fmt::Debug for ArbiterCommand {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
ArbiterCommand::Stop => write!(f, "ArbiterCommand::Stop"),
|
||||
ArbiterCommand::Execute(_) => write!(f, "ArbiterCommand::Execute"),
|
||||
ArbiterCommand::ExecuteFn(_) => write!(f, "ArbiterCommand::ExecuteFn"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle for sending spawn and stop messages to an [Arbiter].
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ArbiterHandle {
|
||||
tx: mpsc::UnboundedSender<ArbiterCommand>,
|
||||
}
|
||||
/// Arbiters provide an asynchronous execution environment for actors, functions
|
||||
/// and futures. When an Arbiter is created, they spawn a new OS thread, and
|
||||
/// host an event loop. Some Arbiter functions execute on the current thread.
|
||||
pub struct Arbiter(UnboundedSender<ArbiterCommand>);
|
||||
|
||||
impl ArbiterHandle {
|
||||
pub(crate) fn new(tx: mpsc::UnboundedSender<ArbiterCommand>) -> Self {
|
||||
Self { tx }
|
||||
impl Default for Arbiter {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
|
||||
/// Send a future to the [Arbiter]'s thread and spawn it.
|
||||
///
|
||||
/// If you require a result, include a response channel in the future.
|
||||
///
|
||||
/// Returns true if future was sent successfully and false if the [Arbiter] has died.
|
||||
pub fn spawn<Fut>(&self, future: Fut) -> bool
|
||||
where
|
||||
Fut: Future<Output = ()> + Send + 'static,
|
||||
{
|
||||
self.tx
|
||||
.send(ArbiterCommand::Execute(Box::pin(future)))
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
/// Send a function to the [Arbiter]'s thread and execute it.
|
||||
///
|
||||
/// Any result from the function is discarded. If you require a result, include a response
|
||||
/// channel in the function.
|
||||
///
|
||||
/// Returns true if function was sent successfully and false if the [Arbiter] has died.
|
||||
pub fn spawn_fn<F>(&self, f: F) -> bool
|
||||
where
|
||||
F: FnOnce() + Send + 'static,
|
||||
{
|
||||
self.spawn(async { f() })
|
||||
}
|
||||
|
||||
/// Instruct [Arbiter] to stop processing it's event loop.
|
||||
///
|
||||
/// Returns true if stop message was sent successfully and false if the [Arbiter] has
|
||||
/// been dropped.
|
||||
pub fn stop(&self) -> bool {
|
||||
self.tx.send(ArbiterCommand::Stop).is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
/// An Arbiter represents a thread that provides an asynchronous execution environment for futures
|
||||
/// and functions.
|
||||
///
|
||||
/// When an arbiter is created, it spawns a new [OS thread](thread), and hosts an event loop.
|
||||
#[derive(Debug)]
|
||||
pub struct Arbiter {
|
||||
tx: mpsc::UnboundedSender<ArbiterCommand>,
|
||||
thread_handle: thread::JoinHandle<()>,
|
||||
}
|
||||
|
||||
impl Arbiter {
|
||||
/// Spawn a new Arbiter thread and start its event loop.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if a [System] is not registered on the current thread.
|
||||
#[cfg(not(all(target_os = "linux", feature = "io-uring")))]
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub fn new() -> Arbiter {
|
||||
Self::with_tokio_rt(|| {
|
||||
crate::runtime::default_tokio_runtime()
|
||||
.expect("Cannot create new Arbiter's Runtime.")
|
||||
pub(crate) fn new_system() -> Self {
|
||||
let (tx, rx) = unbounded();
|
||||
|
||||
let arb = Arbiter(tx);
|
||||
ADDR.with(|cell| *cell.borrow_mut() = Some(arb.clone()));
|
||||
RUNNING.with(|cell| cell.set(false));
|
||||
Arbiter::spawn(ArbiterController { stop: None, 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"),
|
||||
})
|
||||
}
|
||||
|
||||
/// Spawn a new Arbiter using the [Tokio Runtime](tokio-runtime) returned from a closure.
|
||||
///
|
||||
/// [tokio-runtime]: tokio::runtime::Runtime
|
||||
#[cfg(not(all(target_os = "linux", feature = "io-uring")))]
|
||||
#[doc(hidden)]
|
||||
pub fn with_tokio_rt<F>(runtime_factory: F) -> Arbiter
|
||||
where
|
||||
F: Fn() -> tokio::runtime::Runtime + Send + 'static,
|
||||
{
|
||||
let sys = System::current();
|
||||
let system_id = sys.id();
|
||||
let arb_id = COUNT.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
let name = format!("actix-rt|system:{}|arbiter:{}", system_id, arb_id);
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
|
||||
let (ready_tx, ready_rx) = std::sync::mpsc::channel::<()>();
|
||||
|
||||
let thread_handle = thread::Builder::new()
|
||||
.name(name.clone())
|
||||
.spawn({
|
||||
let tx = tx.clone();
|
||||
move || {
|
||||
let rt = crate::runtime::Runtime::from(runtime_factory());
|
||||
let hnd = ArbiterHandle::new(tx);
|
||||
|
||||
System::set_current(sys);
|
||||
|
||||
HANDLE.with(|cell| *cell.borrow_mut() = Some(hnd.clone()));
|
||||
|
||||
// register arbiter
|
||||
let _ = System::current()
|
||||
.tx()
|
||||
.send(SystemCommand::RegisterArbiter(arb_id, hnd));
|
||||
|
||||
ready_tx.send(()).unwrap();
|
||||
|
||||
// run arbiter event processing loop
|
||||
rt.block_on(ArbiterRunner { rx });
|
||||
|
||||
// deregister arbiter
|
||||
let _ = System::current()
|
||||
.tx()
|
||||
.send(SystemCommand::DeregisterArbiter(arb_id));
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|err| {
|
||||
panic!("Cannot spawn Arbiter's thread: {:?}. {:?}", &name, err)
|
||||
});
|
||||
|
||||
ready_rx.recv().unwrap();
|
||||
|
||||
Arbiter { tx, thread_handle }
|
||||
/// Stop arbiter from continuing it's event loop.
|
||||
pub fn stop(&self) {
|
||||
let _ = self.0.unbounded_send(ArbiterCommand::Stop);
|
||||
}
|
||||
|
||||
/// Spawn a new Arbiter thread and start its event loop with `tokio-uring` runtime.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if a [System] is not registered on the current thread.
|
||||
#[cfg(all(target_os = "linux", feature = "io-uring"))]
|
||||
#[allow(clippy::new_without_default)]
|
||||
/// 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 system_id = sys.id();
|
||||
let arb_id = COUNT.fetch_add(1, Ordering::Relaxed);
|
||||
let (arb_tx, arb_rx) = unbounded();
|
||||
let arb_tx2 = arb_tx.clone();
|
||||
|
||||
let name = format!("actix-rt|system:{}|arbiter:{}", system_id, arb_id);
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
let _ = thread::Builder::new().name(name.clone()).spawn(move || {
|
||||
let mut rt = Builder::new().build_rt().expect("Can not create Runtime");
|
||||
let arb = Arbiter(arb_tx);
|
||||
|
||||
let (ready_tx, ready_rx) = std::sync::mpsc::channel::<()>();
|
||||
let (stop, stop_rx) = channel();
|
||||
RUNNING.with(|cell| cell.set(true));
|
||||
|
||||
let thread_handle = thread::Builder::new()
|
||||
.name(name.clone())
|
||||
.spawn({
|
||||
let tx = tx.clone();
|
||||
move || {
|
||||
let hnd = ArbiterHandle::new(tx);
|
||||
System::set_current(sys);
|
||||
|
||||
System::set_current(sys);
|
||||
|
||||
HANDLE.with(|cell| *cell.borrow_mut() = Some(hnd.clone()));
|
||||
|
||||
// register arbiter
|
||||
let _ = System::current()
|
||||
.tx()
|
||||
.send(SystemCommand::RegisterArbiter(arb_id, hnd));
|
||||
|
||||
ready_tx.send(()).unwrap();
|
||||
|
||||
// run arbiter event processing loop
|
||||
tokio_uring::start(ArbiterRunner { rx });
|
||||
|
||||
// deregister arbiter
|
||||
let _ = System::current()
|
||||
.tx()
|
||||
.send(SystemCommand::DeregisterArbiter(arb_id));
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|err| {
|
||||
panic!("Cannot spawn Arbiter's thread: {:?}. {:?}", &name, err)
|
||||
// start arbiter controller
|
||||
rt.spawn(ArbiterController {
|
||||
stop: Some(stop),
|
||||
rx: arb_rx,
|
||||
});
|
||||
ADDR.with(|cell| *cell.borrow_mut() = Some(arb.clone()));
|
||||
|
||||
ready_rx.recv().unwrap();
|
||||
// register arbiter
|
||||
let _ = System::current()
|
||||
.sys()
|
||||
.unbounded_send(SystemCommand::RegisterArbiter(id, arb.clone()));
|
||||
|
||||
Arbiter { tx, thread_handle }
|
||||
// run loop
|
||||
let _ = match rt.block_on(stop_rx) {
|
||||
Ok(code) => code,
|
||||
Err(_) => 1,
|
||||
};
|
||||
|
||||
// unregister arbiter
|
||||
let _ = System::current()
|
||||
.sys()
|
||||
.unbounded_send(SystemCommand::UnregisterArbiter(id));
|
||||
});
|
||||
|
||||
Arbiter(arb_tx2)
|
||||
}
|
||||
|
||||
/// Sets up an Arbiter runner in a new System using the environment's local set.
|
||||
pub(crate) fn in_new_system() -> ArbiterHandle {
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
|
||||
let hnd = ArbiterHandle::new(tx);
|
||||
|
||||
HANDLE.with(|cell| *cell.borrow_mut() = Some(hnd.clone()));
|
||||
|
||||
crate::spawn(ArbiterRunner { rx });
|
||||
|
||||
hnd
|
||||
pub(crate) fn run_system() {
|
||||
RUNNING.with(|cell| cell.set(true));
|
||||
Q.with(|cell| {
|
||||
let mut v = cell.borrow_mut();
|
||||
for fut in v.drain(..) {
|
||||
spawn(fut);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Return a handle to the this Arbiter's message sender.
|
||||
pub fn handle(&self) -> ArbiterHandle {
|
||||
ArbiterHandle::new(self.tx.clone())
|
||||
pub(crate) fn stop_system() {
|
||||
RUNNING.with(|cell| cell.set(false));
|
||||
}
|
||||
|
||||
/// Return a handle to the current thread's Arbiter's message sender.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if no Arbiter is running on the current thread.
|
||||
pub fn current() -> ArbiterHandle {
|
||||
HANDLE.with(|cell| match *cell.borrow() {
|
||||
Some(ref hnd) => hnd.clone(),
|
||||
None => panic!("Arbiter is not running."),
|
||||
})
|
||||
}
|
||||
|
||||
/// Stop Arbiter from continuing it's event loop.
|
||||
///
|
||||
/// Returns true if stop message was sent successfully and false if the Arbiter has been dropped.
|
||||
pub fn stop(&self) -> bool {
|
||||
self.tx.send(ArbiterCommand::Stop).is_ok()
|
||||
}
|
||||
|
||||
/// Send a future to the Arbiter's thread and spawn it.
|
||||
///
|
||||
/// If you require a result, include a response channel in the future.
|
||||
///
|
||||
/// Returns true if future was sent successfully and false if the Arbiter has died.
|
||||
pub fn spawn<Fut>(&self, future: Fut) -> bool
|
||||
/// 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
|
||||
Fut: Future<Output = ()> + Send + 'static,
|
||||
F: Future<Item = (), Error = ()> + 'static,
|
||||
{
|
||||
self.tx
|
||||
.send(ArbiterCommand::Execute(Box::pin(future)))
|
||||
.is_ok()
|
||||
RUNNING.with(move |cell| {
|
||||
if cell.get() {
|
||||
spawn(Box::alloc().init(future));
|
||||
} else {
|
||||
Q.with(move |cell| cell.borrow_mut().push(Box::alloc().init(future)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Send a function to the Arbiter's thread and execute it.
|
||||
///
|
||||
/// Any result from the function is discarded. If you require a result, include a response
|
||||
/// channel in the function.
|
||||
///
|
||||
/// Returns true if function was sent successfully and false if the Arbiter has died.
|
||||
pub fn spawn_fn<F>(&self, f: F) -> bool
|
||||
/// 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: IntoFuture<Item = (), Error = ()> + 'static,
|
||||
{
|
||||
Arbiter::spawn(future::lazy(f))
|
||||
}
|
||||
|
||||
/// Send a future to the Arbiter's thread, and spawn it.
|
||||
pub fn send<F>(&self, future: F)
|
||||
where
|
||||
F: Future<Item = (), Error = ()> + Send + 'static,
|
||||
{
|
||||
let _ = self
|
||||
.0
|
||||
.unbounded_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,
|
||||
{
|
||||
self.spawn(async { f() })
|
||||
let _ = self
|
||||
.0
|
||||
.unbounded_send(ArbiterCommand::ExecuteFn(Box::new(move || {
|
||||
let _ = f();
|
||||
})));
|
||||
}
|
||||
|
||||
/// Wait for Arbiter's event loop to complete.
|
||||
///
|
||||
/// Joins the underlying OS thread handle. See [`JoinHandle::join`](thread::JoinHandle::join).
|
||||
pub fn join(self) -> thread::Result<()> {
|
||||
self.thread_handle.join()
|
||||
/// 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<Item = R, Error = Canceled>
|
||||
where
|
||||
F: FnOnce() -> R + Send + 'static,
|
||||
R: Send + 'static,
|
||||
{
|
||||
let (tx, rx) = channel();
|
||||
let _ = self
|
||||
.0
|
||||
.unbounded_send(ArbiterCommand::ExecuteFn(Box::new(move || {
|
||||
if !tx.is_canceled() {
|
||||
let _ = tx.send(f());
|
||||
}
|
||||
})));
|
||||
rx
|
||||
}
|
||||
}
|
||||
|
||||
/// A persistent future that processes [Arbiter] commands.
|
||||
struct ArbiterRunner {
|
||||
rx: mpsc::UnboundedReceiver<ArbiterCommand>,
|
||||
struct ArbiterController {
|
||||
stop: Option<Sender<i32>>,
|
||||
rx: UnboundedReceiver<ArbiterCommand>,
|
||||
}
|
||||
|
||||
impl Future for ArbiterRunner {
|
||||
type Output = ();
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
// process all items currently buffered in channel
|
||||
loop {
|
||||
match ready!(Pin::new(&mut self.rx).poll_recv(cx)) {
|
||||
// channel closed; no more messages can be received
|
||||
None => return Poll::Ready(()),
|
||||
|
||||
// process arbiter command
|
||||
Some(item) => match item {
|
||||
ArbiterCommand::Stop => {
|
||||
return Poll::Ready(());
|
||||
}
|
||||
ArbiterCommand::Execute(task_fut) => {
|
||||
tokio::task::spawn_local(task_fut);
|
||||
}
|
||||
},
|
||||
impl Drop for ArbiterController {
|
||||
fn drop(&mut self) {
|
||||
if thread::panicking() {
|
||||
eprintln!("Panic in Arbiter thread, shutting down system.");
|
||||
if System::current().stop_on_panic() {
|
||||
System::current().stop_with_code(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for ArbiterController {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
loop {
|
||||
match self.rx.poll() {
|
||||
Ok(Async::Ready(None)) | Err(_) => return Ok(Async::Ready(())),
|
||||
Ok(Async::Ready(Some(item))) => match item {
|
||||
ArbiterCommand::Stop => {
|
||||
if let Some(stop) = self.stop.take() {
|
||||
let _ = stop.send(0);
|
||||
};
|
||||
return Ok(Async::Ready(()));
|
||||
}
|
||||
ArbiterCommand::Execute(fut) => {
|
||||
spawn(fut);
|
||||
}
|
||||
ArbiterCommand::ExecuteFn(f) => {
|
||||
f.call_box();
|
||||
}
|
||||
},
|
||||
Ok(Async::NotReady) => return Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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 Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
loop {
|
||||
match self.commands.poll() {
|
||||
Ok(Async::Ready(None)) | Err(_) => return Ok(Async::Ready(())),
|
||||
Ok(Async::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);
|
||||
}
|
||||
},
|
||||
Ok(Async::NotReady) => return Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FnExec: Send + 'static {
|
||||
fn call_box(self: Box<Self>);
|
||||
}
|
||||
|
||||
impl<F> FnExec for F
|
||||
where
|
||||
F: FnOnce() + Send + 'static,
|
||||
{
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(boxed_local))]
|
||||
fn call_box(self: Box<Self>) {
|
||||
(*self)()
|
||||
}
|
||||
}
|
||||
|
242
actix-rt/src/builder.rs
Normal file
242
actix-rt/src/builder.rs
Normal file
@@ -0,0 +1,242 @@
|
||||
use std::borrow::Cow;
|
||||
use std::io;
|
||||
|
||||
use futures::future;
|
||||
use futures::future::{lazy, Future};
|
||||
use futures::sync::mpsc::unbounded;
|
||||
use futures::sync::oneshot::{channel, Receiver};
|
||||
|
||||
use tokio_current_thread::{CurrentThread, Handle};
|
||||
use tokio_reactor::Reactor;
|
||||
use tokio_timer::clock::Clock;
|
||||
use tokio_timer::timer::Timer;
|
||||
|
||||
use crate::arbiter::{Arbiter, SystemArbiter};
|
||||
use crate::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>,
|
||||
|
||||
/// The clock to use
|
||||
clock: Clock,
|
||||
|
||||
/// 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"),
|
||||
clock: Clock::new(),
|
||||
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
|
||||
}
|
||||
|
||||
/// Set the Clock instance that will be used by this System.
|
||||
///
|
||||
/// Defaults to the system clock.
|
||||
pub fn clock(mut self, clock: Clock) -> Self {
|
||||
self.clock = clock;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the option 'stop_on_panic' which controls whether the System is stopped when an
|
||||
/// 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, executor: Handle) -> AsyncSystemRunner {
|
||||
self.create_async_runtime(executor)
|
||||
}
|
||||
|
||||
/// 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() + 'static,
|
||||
{
|
||||
self.create_runtime(f).run()
|
||||
}
|
||||
|
||||
fn create_async_runtime(self, executor: Handle) -> AsyncSystemRunner {
|
||||
let (stop_tx, stop) = channel();
|
||||
let (sys_sender, sys_receiver) = unbounded();
|
||||
|
||||
let system = System::construct(sys_sender, Arbiter::new_system(), self.stop_on_panic);
|
||||
|
||||
// system arbiter
|
||||
let arb = SystemArbiter::new(stop_tx, sys_receiver);
|
||||
|
||||
// start the system arbiter
|
||||
executor.spawn(arb).expect("could not start system arbiter");
|
||||
|
||||
AsyncSystemRunner { stop, system }
|
||||
}
|
||||
|
||||
fn create_runtime<F>(self, f: F) -> SystemRunner
|
||||
where
|
||||
F: FnOnce() + 'static,
|
||||
{
|
||||
let (stop_tx, stop) = channel();
|
||||
let (sys_sender, sys_receiver) = unbounded();
|
||||
|
||||
let system = System::construct(sys_sender, Arbiter::new_system(), self.stop_on_panic);
|
||||
|
||||
// system arbiter
|
||||
let arb = SystemArbiter::new(stop_tx, sys_receiver);
|
||||
|
||||
let mut rt = self.build_rt().unwrap();
|
||||
rt.spawn(arb);
|
||||
|
||||
// init system arbiter and run configuration method
|
||||
let _ = rt.block_on(lazy(move || {
|
||||
f();
|
||||
Ok::<_, ()>(())
|
||||
}));
|
||||
|
||||
SystemRunner { rt, stop, system }
|
||||
}
|
||||
|
||||
pub(crate) fn build_rt(&self) -> io::Result<Runtime> {
|
||||
// We need a reactor to receive events about IO objects from kernel
|
||||
let reactor = Reactor::new()?;
|
||||
let reactor_handle = reactor.handle();
|
||||
|
||||
// Place a timer wheel on top of the reactor. If there are no timeouts to fire, it'll let the
|
||||
// reactor pick up some new external events.
|
||||
let timer = Timer::new_with_now(reactor, self.clock.clone());
|
||||
let timer_handle = timer.handle();
|
||||
|
||||
// And now put a single-threaded executor on top of the timer. When there are no futures ready
|
||||
// to do something, it'll let the timer or the reactor to generate some new stimuli for the
|
||||
// futures to continue in their life.
|
||||
let executor = CurrentThread::new_with_park(timer);
|
||||
|
||||
Ok(Runtime::new2(
|
||||
reactor_handle,
|
||||
timer_handle,
|
||||
self.clock.clone(),
|
||||
executor,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
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<Item = (), Error = io::Error> + Send {
|
||||
let AsyncSystemRunner { stop, .. } = self;
|
||||
|
||||
// run loop
|
||||
future::lazy(|| {
|
||||
Arbiter::run_system();
|
||||
stop.then(|res| match res {
|
||||
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)),
|
||||
})
|
||||
.then(|result| {
|
||||
Arbiter::stop_system();
|
||||
result
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 { mut rt, stop, .. } = self;
|
||||
|
||||
// run loop
|
||||
let _ = rt.block_on(lazy(move || {
|
||||
Arbiter::run_system();
|
||||
Ok::<_, ()>(())
|
||||
}));
|
||||
let result = 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)),
|
||||
};
|
||||
Arbiter::stop_system();
|
||||
result
|
||||
}
|
||||
|
||||
/// Execute a future and wait for result.
|
||||
pub fn block_on<F, I, E>(&mut self, fut: F) -> Result<I, E>
|
||||
where
|
||||
F: Future<Item = I, Error = E>,
|
||||
{
|
||||
let _ = self.rt.block_on(lazy(move || {
|
||||
Arbiter::run_system();
|
||||
Ok::<_, ()>(())
|
||||
}));
|
||||
let res = self.rt.block_on(fut);
|
||||
let _ = self.rt.block_on(lazy(move || {
|
||||
Arbiter::stop_system();
|
||||
Ok::<_, ()>(())
|
||||
}));
|
||||
res
|
||||
}
|
||||
}
|
@@ -1,205 +1,30 @@
|
||||
//! Tokio-based single-threaded async runtime for the Actix ecosystem.
|
||||
//!
|
||||
//! In most parts of the the Actix ecosystem, it has been chosen to use !Send futures. For this
|
||||
//! reason, a single-threaded runtime is appropriate since it is guaranteed that futures will not
|
||||
//! be moved between threads. This can result in small performance improvements over cases where
|
||||
//! atomics would otherwise be needed.
|
||||
//!
|
||||
//! To achieve similar performance to multi-threaded, work-stealing runtimes, applications
|
||||
//! using `actix-rt` will create multiple, mostly disconnected, single-threaded runtimes.
|
||||
//! This approach has good performance characteristics for workloads where the majority of tasks
|
||||
//! have similar runtime expense.
|
||||
//!
|
||||
//! The disadvantage is that idle threads will not steal work from very busy, stuck or otherwise
|
||||
//! backlogged threads. Tasks that are disproportionately expensive should be offloaded to the
|
||||
//! blocking task thread-pool using [`task::spawn_blocking`].
|
||||
//!
|
||||
//! # Examples
|
||||
//! ```
|
||||
//! use std::sync::mpsc;
|
||||
//! use actix_rt::{Arbiter, System};
|
||||
//!
|
||||
//! let _ = System::new();
|
||||
//!
|
||||
//! let (tx, rx) = mpsc::channel::<u32>();
|
||||
//!
|
||||
//! let arbiter = Arbiter::new();
|
||||
//! arbiter.spawn_fn(move || tx.send(42).unwrap());
|
||||
//!
|
||||
//! let num = rx.recv().unwrap();
|
||||
//! assert_eq!(num, 42);
|
||||
//!
|
||||
//! arbiter.stop();
|
||||
//! arbiter.join().unwrap();
|
||||
//! ```
|
||||
//!
|
||||
//! # `io-uring` Support
|
||||
//! There is experimental support for using io-uring with this crate by enabling the
|
||||
//! `io-uring` feature. For now, it is semver exempt.
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![allow(clippy::type_complexity)]
|
||||
#![warn(missing_docs)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
|
||||
#[cfg(all(not(target_os = "linux"), feature = "io-uring"))]
|
||||
compile_error!("io_uring is a linux only feature.");
|
||||
|
||||
use std::future::Future;
|
||||
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
// Cannot define a main macro when compiled into test harness.
|
||||
// Workaround for https://github.com/rust-lang/rust/issues/62127.
|
||||
#[cfg(all(feature = "macros", not(test)))]
|
||||
pub use actix_macros::main;
|
||||
|
||||
#[cfg(feature = "macros")]
|
||||
pub use actix_macros::test;
|
||||
//! A runtime implementation that runs everything on the current thread.
|
||||
|
||||
mod arbiter;
|
||||
mod builder;
|
||||
mod runtime;
|
||||
mod system;
|
||||
|
||||
pub use self::arbiter::{Arbiter, ArbiterHandle};
|
||||
pub use self::arbiter::Arbiter;
|
||||
pub use self::builder::{Builder, SystemRunner};
|
||||
pub use self::runtime::Runtime;
|
||||
pub use self::system::{System, SystemRunner};
|
||||
pub use self::system::System;
|
||||
|
||||
pub use tokio::pin;
|
||||
#[doc(hidden)]
|
||||
pub use actix_threadpool as blocking;
|
||||
|
||||
pub mod signal {
|
||||
//! Asynchronous signal handling (Tokio re-exports).
|
||||
|
||||
#[cfg(unix)]
|
||||
pub mod unix {
|
||||
//! Unix specific signals (Tokio re-exports).
|
||||
pub use tokio::signal::unix::*;
|
||||
}
|
||||
pub use tokio::signal::ctrl_c;
|
||||
}
|
||||
|
||||
pub mod net {
|
||||
//! TCP/UDP/Unix bindings (mostly Tokio re-exports).
|
||||
|
||||
use std::{
|
||||
future::Future,
|
||||
io,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
pub use tokio::io::Ready;
|
||||
use tokio::io::{AsyncRead, AsyncWrite, Interest};
|
||||
pub use tokio::net::UdpSocket;
|
||||
pub use tokio::net::{TcpListener, TcpSocket, TcpStream};
|
||||
|
||||
#[cfg(unix)]
|
||||
pub use tokio::net::{UnixDatagram, UnixListener, UnixStream};
|
||||
|
||||
/// Extension trait over async read+write types that can also signal readiness.
|
||||
#[doc(hidden)]
|
||||
pub trait ActixStream: AsyncRead + AsyncWrite + Unpin {
|
||||
/// Poll stream and check read readiness of Self.
|
||||
///
|
||||
/// See [tokio::net::TcpStream::poll_read_ready] for detail on intended use.
|
||||
fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>>;
|
||||
|
||||
/// Poll stream and check write readiness of Self.
|
||||
///
|
||||
/// See [tokio::net::TcpStream::poll_write_ready] for detail on intended use.
|
||||
fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>>;
|
||||
}
|
||||
|
||||
impl ActixStream for TcpStream {
|
||||
fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
|
||||
let ready = self.ready(Interest::READABLE);
|
||||
tokio::pin!(ready);
|
||||
ready.poll(cx)
|
||||
}
|
||||
|
||||
fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
|
||||
let ready = self.ready(Interest::WRITABLE);
|
||||
tokio::pin!(ready);
|
||||
ready.poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl ActixStream for UnixStream {
|
||||
fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
|
||||
let ready = self.ready(Interest::READABLE);
|
||||
tokio::pin!(ready);
|
||||
ready.poll(cx)
|
||||
}
|
||||
|
||||
fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
|
||||
let ready = self.ready(Interest::WRITABLE);
|
||||
tokio::pin!(ready);
|
||||
ready.poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Io: ActixStream + ?Sized> ActixStream for Box<Io> {
|
||||
fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
|
||||
(**self).poll_read_ready(cx)
|
||||
}
|
||||
|
||||
fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
|
||||
(**self).poll_write_ready(cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod time {
|
||||
//! Utilities for tracking time (Tokio re-exports).
|
||||
|
||||
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};
|
||||
}
|
||||
|
||||
pub mod task {
|
||||
//! Task management (Tokio re-exports).
|
||||
|
||||
pub use tokio::task::{spawn_blocking, yield_now, JoinError, JoinHandle};
|
||||
}
|
||||
|
||||
/// Spawns a future on the current thread as a new task.
|
||||
///
|
||||
/// If not immediately awaited, the task can be cancelled using [`JoinHandle::abort`].
|
||||
///
|
||||
/// The provided future is spawned as a new task; therefore, panics are caught.
|
||||
/// Spawns a future on the current arbiter.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if Actix system is not running.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use std::time::Duration;
|
||||
/// # actix_rt::Runtime::new().unwrap().block_on(async {
|
||||
/// // task resolves successfully
|
||||
/// assert_eq!(actix_rt::spawn(async { 1 }).await.unwrap(), 1);
|
||||
///
|
||||
/// // task panics
|
||||
/// assert!(actix_rt::spawn(async {
|
||||
/// panic!("panic is caught at task boundary");
|
||||
/// })
|
||||
/// .await
|
||||
/// .unwrap_err()
|
||||
/// .is_panic());
|
||||
///
|
||||
/// // task is cancelled before completion
|
||||
/// let handle = actix_rt::spawn(actix_rt::time::sleep(Duration::from_secs(100)));
|
||||
/// handle.abort();
|
||||
/// assert!(handle.await.unwrap_err().is_cancelled());
|
||||
/// # });
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn spawn<Fut>(f: Fut) -> JoinHandle<Fut::Output>
|
||||
/// This function panics if actix system is not running.
|
||||
pub fn spawn<F>(f: F)
|
||||
where
|
||||
Fut: Future + 'static,
|
||||
Fut::Output: 'static,
|
||||
F: futures::Future<Item = (), Error = ()> + 'static,
|
||||
{
|
||||
tokio::task::spawn_local(f)
|
||||
if !System::is_set() {
|
||||
panic!("System is not running");
|
||||
}
|
||||
|
||||
Arbiter::spawn(f);
|
||||
}
|
||||
|
92
actix-rt/src/mod.rs
Normal file
92
actix-rt/src/mod.rs
Normal file
@@ -0,0 +1,92 @@
|
||||
//! A runtime implementation that runs everything on the current thread.
|
||||
//!
|
||||
//! [`current_thread::Runtime`][rt] is similar to the primary
|
||||
//! [`Runtime`][concurrent-rt] except that it runs all components on the current
|
||||
//! thread instead of using a thread pool. This means that it is able to spawn
|
||||
//! futures that do not implement `Send`.
|
||||
//!
|
||||
//! Same as the default [`Runtime`][concurrent-rt], the
|
||||
//! [`current_thread::Runtime`][rt] includes:
|
||||
//!
|
||||
//! * A [reactor] to drive I/O resources.
|
||||
//! * An [executor] to execute tasks that use these I/O resources.
|
||||
//! * A [timer] for scheduling work to run after a set period of time.
|
||||
//!
|
||||
//! Note that [`current_thread::Runtime`][rt] does not implement `Send` itself
|
||||
//! and cannot be safely moved to other threads.
|
||||
//!
|
||||
//! # Spawning from other threads
|
||||
//!
|
||||
//! While [`current_thread::Runtime`][rt] does not implement `Send` and cannot
|
||||
//! safely be moved to other threads, it provides a `Handle` that can be sent
|
||||
//! to other threads and allows to spawn new tasks from there.
|
||||
//!
|
||||
//! For example:
|
||||
//!
|
||||
//! ```
|
||||
//! # extern crate tokio;
|
||||
//! # extern crate futures;
|
||||
//! use tokio::runtime::current_thread::Runtime;
|
||||
//! use tokio::prelude::*;
|
||||
//! use std::thread;
|
||||
//!
|
||||
//! # fn main() {
|
||||
//! let mut runtime = Runtime::new().unwrap();
|
||||
//! let handle = runtime.handle();
|
||||
//!
|
||||
//! thread::spawn(move || {
|
||||
//! handle.spawn(future::ok(()));
|
||||
//! }).join().unwrap();
|
||||
//!
|
||||
//! # /*
|
||||
//! runtime.run().unwrap();
|
||||
//! # */
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! Creating a new `Runtime` and running a future `f` until its completion and
|
||||
//! returning its result.
|
||||
//!
|
||||
//! ```
|
||||
//! use tokio::runtime::current_thread::Runtime;
|
||||
//! use tokio::prelude::*;
|
||||
//!
|
||||
//! let mut runtime = Runtime::new().unwrap();
|
||||
//!
|
||||
//! // Use the runtime...
|
||||
//! // runtime.block_on(f); // where f is a future
|
||||
//! ```
|
||||
//!
|
||||
//! [rt]: struct.Runtime.html
|
||||
//! [concurrent-rt]: ../struct.Runtime.html
|
||||
//! [chan]: https://docs.rs/futures/0.1/futures/sync/mpsc/fn.channel.html
|
||||
//! [reactor]: ../../reactor/struct.Reactor.html
|
||||
//! [executor]: https://tokio.rs/docs/getting-started/runtime-model/#executors
|
||||
//! [timer]: ../../timer/index.html
|
||||
|
||||
mod builder;
|
||||
mod runtime;
|
||||
|
||||
pub use self::builder::Builder;
|
||||
pub use self::runtime::{Runtime, Handle};
|
||||
pub use tokio_current_thread::spawn;
|
||||
pub use tokio_current_thread::TaskExecutor;
|
||||
|
||||
use futures::Future;
|
||||
|
||||
/// Run the provided future to completion using a runtime running on the current thread.
|
||||
///
|
||||
/// This first creates a new [`Runtime`], and calls [`Runtime::block_on`] with the provided future,
|
||||
/// which blocks the current thread until the provided future completes. It then calls
|
||||
/// [`Runtime::run`] to wait for any other spawned futures to resolve.
|
||||
pub fn block_on_all<F>(future: F) -> Result<F::Item, F::Error>
|
||||
where
|
||||
F: Future,
|
||||
{
|
||||
let mut r = Runtime::new().expect("failed to start runtime on current thread");
|
||||
let v = r.block_on(future)?;
|
||||
r.run().expect("failed to resolve remaining futures");
|
||||
Ok(v)
|
||||
}
|
@@ -1,91 +1,174 @@
|
||||
use std::{future::Future, io};
|
||||
use std::error::Error;
|
||||
use std::{fmt, io};
|
||||
|
||||
use tokio::task::{JoinHandle, LocalSet};
|
||||
use futures::Future;
|
||||
use tokio_current_thread::{self as current_thread, CurrentThread};
|
||||
use tokio_executor;
|
||||
use tokio_reactor::{self, Reactor};
|
||||
use tokio_timer::clock::{self, Clock};
|
||||
use tokio_timer::timer::{self, Timer};
|
||||
|
||||
/// A Tokio-based runtime proxy.
|
||||
use crate::builder::Builder;
|
||||
|
||||
/// Single-threaded runtime provides a way to start reactor
|
||||
/// and executor on the current thread.
|
||||
///
|
||||
/// All spawned futures will be executed on the current thread. Therefore, there is no `Send` bound
|
||||
/// on submitted futures.
|
||||
/// See [module level][mod] documentation for more details.
|
||||
///
|
||||
/// [mod]: index.html
|
||||
#[derive(Debug)]
|
||||
pub struct Runtime {
|
||||
local: LocalSet,
|
||||
rt: tokio::runtime::Runtime,
|
||||
reactor_handle: tokio_reactor::Handle,
|
||||
timer_handle: timer::Handle,
|
||||
clock: Clock,
|
||||
executor: CurrentThread<Timer<Reactor>>,
|
||||
}
|
||||
|
||||
pub(crate) fn default_tokio_runtime() -> io::Result<tokio::runtime::Runtime> {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_io()
|
||||
.enable_time()
|
||||
.build()
|
||||
/// Error returned by the `run` function.
|
||||
#[derive(Debug)]
|
||||
pub struct RunError {
|
||||
inner: current_thread::RunError,
|
||||
}
|
||||
|
||||
impl fmt::Display for RunError {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(fmt, "{}", self.inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for RunError {
|
||||
fn description(&self) -> &str {
|
||||
self.inner.description()
|
||||
}
|
||||
fn cause(&self) -> Option<&Error> {
|
||||
self.inner.source()
|
||||
}
|
||||
}
|
||||
|
||||
impl Runtime {
|
||||
/// Returns a new runtime initialized with default configuration values.
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new() -> io::Result<Self> {
|
||||
let rt = default_tokio_runtime()?;
|
||||
|
||||
Ok(Runtime {
|
||||
rt,
|
||||
local: LocalSet::new(),
|
||||
})
|
||||
/// Returns a new runtime initialized with default configuration values.
|
||||
pub fn new() -> io::Result<Runtime> {
|
||||
Builder::new().build_rt()
|
||||
}
|
||||
|
||||
/// Offload a future onto the single-threaded runtime.
|
||||
pub(super) fn new2(
|
||||
reactor_handle: tokio_reactor::Handle,
|
||||
timer_handle: timer::Handle,
|
||||
clock: Clock,
|
||||
executor: CurrentThread<Timer<Reactor>>,
|
||||
) -> Runtime {
|
||||
Runtime {
|
||||
reactor_handle,
|
||||
timer_handle,
|
||||
clock,
|
||||
executor,
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawn a future onto the single-threaded Tokio runtime.
|
||||
///
|
||||
/// The returned join handle can be used to await the future's result.
|
||||
/// See [module level][mod] documentation for more details.
|
||||
///
|
||||
/// See [crate root][crate] documentation for more details.
|
||||
/// [mod]: index.html
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// let rt = actix_rt::Runtime::new().unwrap();
|
||||
///
|
||||
/// ```rust
|
||||
/// # use futures::{future, Future, Stream};
|
||||
/// use actix_rt::Runtime;
|
||||
///
|
||||
/// # fn dox() {
|
||||
/// // Create the runtime
|
||||
/// let mut rt = Runtime::new().unwrap();
|
||||
///
|
||||
/// // Spawn a future onto the runtime
|
||||
/// let handle = rt.spawn(async {
|
||||
/// rt.spawn(future::lazy(|| {
|
||||
/// println!("running on the runtime");
|
||||
/// 42
|
||||
/// });
|
||||
///
|
||||
/// assert_eq!(rt.block_on(handle).unwrap(), 42);
|
||||
/// Ok(())
|
||||
/// }));
|
||||
/// # }
|
||||
/// # 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) -> JoinHandle<F::Output>
|
||||
///
|
||||
/// 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>(&mut self, future: F) -> &mut Self
|
||||
where
|
||||
F: Future + 'static,
|
||||
F: Future<Item = (), Error = ()> + 'static,
|
||||
{
|
||||
self.local.spawn_local(future)
|
||||
self.executor.spawn(future);
|
||||
self
|
||||
}
|
||||
|
||||
/// Runs the provided future, blocking the current thread until the future completes.
|
||||
/// 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.
|
||||
/// 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
|
||||
/// 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
|
||||
/// The caller is responsible for ensuring that other spawned futures
|
||||
/// complete execution by calling `block_on` or `run`.
|
||||
pub fn block_on<F>(&mut self, f: F) -> Result<F::Item, F::Error>
|
||||
where
|
||||
F: Future,
|
||||
{
|
||||
self.local.block_on(&self.rt, f)
|
||||
self.enter(|executor| {
|
||||
// Run the provided future
|
||||
let ret = executor.block_on(f);
|
||||
ret.map_err(|e| e.into_inner().expect("unexpected execution error"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<tokio::runtime::Runtime> for Runtime {
|
||||
fn from(rt: tokio::runtime::Runtime) -> Self {
|
||||
Self {
|
||||
local: LocalSet::new(),
|
||||
rt,
|
||||
}
|
||||
/// Run the executor to completion, blocking the thread until **all**
|
||||
/// spawned futures have completed.
|
||||
pub fn run(&mut self) -> Result<(), RunError> {
|
||||
self.enter(|executor| executor.run())
|
||||
.map_err(|e| RunError { inner: e })
|
||||
}
|
||||
|
||||
fn enter<F, R>(&mut self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut current_thread::Entered<Timer<Reactor>>) -> R,
|
||||
{
|
||||
let Runtime {
|
||||
ref reactor_handle,
|
||||
ref timer_handle,
|
||||
ref clock,
|
||||
ref mut executor,
|
||||
..
|
||||
} = *self;
|
||||
|
||||
// Binds an executor to this thread
|
||||
let mut enter = tokio_executor::enter().expect("Multiple executors at once");
|
||||
|
||||
// This will set the default handle and timer to use inside the closure
|
||||
// and run the future.
|
||||
tokio_reactor::with_default(&reactor_handle, &mut enter, |enter| {
|
||||
clock::with_default(clock, enter, |enter| {
|
||||
timer::with_default(&timer_handle, enter, |enter| {
|
||||
// The TaskExecutor is a fake executor that looks into the
|
||||
// current single-threaded executor when used. This is a trick,
|
||||
// because we need two mutable references to the executor (one
|
||||
// to run the provided future, another to install as the default
|
||||
// one). We use the fake one here as the default one.
|
||||
let mut default_executor = current_thread::TaskExecutor::current();
|
||||
tokio_executor::with_default(&mut default_executor, enter, |enter| {
|
||||
let mut executor = executor.enter(enter);
|
||||
f(&mut executor)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -1,98 +1,77 @@
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
collections::HashMap,
|
||||
future::Future,
|
||||
io,
|
||||
pin::Pin,
|
||||
sync::atomic::{AtomicUsize, Ordering},
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
use std::io;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use futures_core::ready;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
use futures::sync::mpsc::UnboundedSender;
|
||||
use futures::Future;
|
||||
use tokio_current_thread::Handle;
|
||||
|
||||
use crate::{arbiter::ArbiterHandle, runtime::default_tokio_runtime, Arbiter, Runtime};
|
||||
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);
|
||||
);
|
||||
|
||||
/// A manager for a per-thread distributed async runtime.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct System {
|
||||
id: usize,
|
||||
sys_tx: mpsc::UnboundedSender<SystemCommand>,
|
||||
|
||||
/// Handle to the first [Arbiter] that is created with the System.
|
||||
arbiter_handle: ArbiterHandle,
|
||||
}
|
||||
|
||||
impl System {
|
||||
/// Create a new system.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if underlying Tokio runtime can not be created.
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new() -> SystemRunner {
|
||||
Self::with_tokio_rt(|| {
|
||||
default_tokio_runtime()
|
||||
.expect("Default Actix (Tokio) runtime could not be created.")
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new System using the [Tokio Runtime](tokio-runtime) returned from a closure.
|
||||
///
|
||||
/// [tokio-runtime]: tokio::runtime::Runtime
|
||||
#[doc(hidden)]
|
||||
pub fn with_tokio_rt<F>(runtime_factory: F) -> SystemRunner
|
||||
where
|
||||
F: Fn() -> tokio::runtime::Runtime,
|
||||
{
|
||||
let (stop_tx, stop_rx) = oneshot::channel();
|
||||
let (sys_tx, sys_rx) = mpsc::unbounded_channel();
|
||||
|
||||
let rt = Runtime::from(runtime_factory());
|
||||
let sys_arbiter = rt.block_on(async { Arbiter::in_new_system() });
|
||||
let system = System::construct(sys_tx, sys_arbiter.clone());
|
||||
|
||||
system
|
||||
.tx()
|
||||
.send(SystemCommand::RegisterArbiter(usize::MAX, sys_arbiter))
|
||||
.unwrap();
|
||||
|
||||
// init background system arbiter
|
||||
let sys_ctrl = SystemController::new(sys_rx, stop_tx);
|
||||
rt.spawn(sys_ctrl);
|
||||
|
||||
SystemRunner {
|
||||
rt,
|
||||
stop_rx,
|
||||
system,
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs new system and registers it on the current thread.
|
||||
/// Constructs new system and sets it as current
|
||||
pub(crate) fn construct(
|
||||
sys_tx: mpsc::UnboundedSender<SystemCommand>,
|
||||
arbiter_handle: ArbiterHandle,
|
||||
sys: UnboundedSender<SystemCommand>,
|
||||
arbiter: Arbiter,
|
||||
stop_on_panic: bool,
|
||||
) -> Self {
|
||||
let sys = System {
|
||||
sys_tx,
|
||||
arbiter_handle,
|
||||
sys,
|
||||
arbiter,
|
||||
stop_on_panic,
|
||||
id: SYSTEM_COUNT.fetch_add(1, Ordering::SeqCst),
|
||||
};
|
||||
|
||||
System::set_current(sys.clone());
|
||||
|
||||
sys
|
||||
}
|
||||
|
||||
/// Get current running system.
|
||||
/// Build a new system with a customized tokio runtime.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if no system is registered on the current thread.
|
||||
/// 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()
|
||||
}
|
||||
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
/// Create new system using provided CurrentThread Handle.
|
||||
///
|
||||
/// This method panics if it can not spawn system arbiter
|
||||
pub fn run_in_executor<T: Into<String>>(
|
||||
name: T,
|
||||
executor: Handle,
|
||||
) -> impl Future<Item = (), Error = io::Error> + Send {
|
||||
Self::builder()
|
||||
.name(name)
|
||||
.build_async(executor)
|
||||
.run_nonblocking()
|
||||
}
|
||||
|
||||
/// Get current running system.
|
||||
pub fn current() -> System {
|
||||
CURRENT.with(|cell| match *cell.borrow() {
|
||||
Some(ref sys) => sys.clone(),
|
||||
@@ -100,157 +79,67 @@ impl System {
|
||||
})
|
||||
}
|
||||
|
||||
/// Try to get current running system.
|
||||
///
|
||||
/// Returns `None` if no System has been started.
|
||||
///
|
||||
/// Contrary to `current`, this never panics.
|
||||
pub fn try_current() -> Option<System> {
|
||||
CURRENT.with(|cell| cell.borrow().clone())
|
||||
/// Set current running system.
|
||||
pub(crate) fn is_set() -> bool {
|
||||
CURRENT.with(|cell| cell.borrow().is_some())
|
||||
}
|
||||
|
||||
/// Get handle to a the System's initial [Arbiter].
|
||||
pub fn arbiter(&self) -> &ArbiterHandle {
|
||||
&self.arbiter_handle
|
||||
}
|
||||
|
||||
/// Check if there is a System registered on the current thread.
|
||||
pub fn is_registered() -> bool {
|
||||
CURRENT.with(|sys| sys.borrow().is_some())
|
||||
}
|
||||
|
||||
/// Register given system on current thread.
|
||||
/// Set current running system.
|
||||
#[doc(hidden)]
|
||||
pub fn set_current(sys: System) {
|
||||
CURRENT.with(|cell| {
|
||||
*cell.borrow_mut() = Some(sys);
|
||||
CURRENT.with(|s| {
|
||||
*s.borrow_mut() = Some(sys);
|
||||
})
|
||||
}
|
||||
|
||||
/// Numeric system identifier.
|
||||
///
|
||||
/// Useful when using multiple Systems.
|
||||
/// 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 (with code 0).
|
||||
/// Stop the system
|
||||
pub fn stop(&self) {
|
||||
self.stop_with_code(0)
|
||||
}
|
||||
|
||||
/// Stop the system with a given exit code.
|
||||
/// Stop the system with a particular exit code.
|
||||
pub fn stop_with_code(&self, code: i32) {
|
||||
let _ = self.sys_tx.send(SystemCommand::Exit(code));
|
||||
let _ = self.sys.unbounded_send(SystemCommand::Exit(code));
|
||||
}
|
||||
|
||||
pub(crate) fn tx(&self) -> &mpsc::UnboundedSender<SystemCommand> {
|
||||
&self.sys_tx
|
||||
}
|
||||
}
|
||||
|
||||
/// Runner that keeps a [System]'s event loop alive until stop message is received.
|
||||
#[must_use = "A SystemRunner does nothing unless `run` is called."]
|
||||
#[derive(Debug)]
|
||||
pub struct SystemRunner {
|
||||
rt: Runtime,
|
||||
stop_rx: oneshot::Receiver<i32>,
|
||||
#[allow(dead_code)]
|
||||
system: System,
|
||||
}
|
||||
|
||||
impl SystemRunner {
|
||||
/// Starts event loop and will return once [System] is [stopped](System::stop).
|
||||
pub fn run(self) -> io::Result<()> {
|
||||
let SystemRunner { rt, stop_rx, .. } = self;
|
||||
|
||||
// run loop
|
||||
match rt.block_on(stop_rx) {
|
||||
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)),
|
||||
}
|
||||
pub(crate) fn sys(&self) -> &UnboundedSender<SystemCommand> {
|
||||
&self.sys
|
||||
}
|
||||
|
||||
/// Runs the provided future, blocking the current thread until the future completes.
|
||||
#[inline]
|
||||
pub fn block_on<F: Future>(&self, fut: F) -> F::Output {
|
||||
self.rt.block_on(fut)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum SystemCommand {
|
||||
Exit(i32),
|
||||
RegisterArbiter(usize, ArbiterHandle),
|
||||
DeregisterArbiter(usize),
|
||||
}
|
||||
|
||||
/// There is one `SystemController` per [System]. It runs in the background, keeping track of
|
||||
/// [Arbiter]s and is able to distribute a system-wide stop command.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct SystemController {
|
||||
stop_tx: Option<oneshot::Sender<i32>>,
|
||||
cmd_rx: mpsc::UnboundedReceiver<SystemCommand>,
|
||||
arbiters: HashMap<usize, ArbiterHandle>,
|
||||
}
|
||||
|
||||
impl SystemController {
|
||||
pub(crate) fn new(
|
||||
cmd_rx: mpsc::UnboundedReceiver<SystemCommand>,
|
||||
stop_tx: oneshot::Sender<i32>,
|
||||
) -> Self {
|
||||
SystemController {
|
||||
cmd_rx,
|
||||
stop_tx: Some(stop_tx),
|
||||
arbiters: HashMap::with_capacity(4),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for SystemController {
|
||||
type Output = ();
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
// process all items currently buffered in channel
|
||||
loop {
|
||||
match ready!(Pin::new(&mut self.cmd_rx).poll_recv(cx)) {
|
||||
// channel closed; no more messages can be received
|
||||
None => return Poll::Ready(()),
|
||||
|
||||
// process system command
|
||||
Some(cmd) => match cmd {
|
||||
SystemCommand::Exit(code) => {
|
||||
// stop all arbiters
|
||||
for arb in self.arbiters.values() {
|
||||
arb.stop();
|
||||
}
|
||||
|
||||
// stop event loop
|
||||
// will only fire once
|
||||
if let Some(stop_tx) = self.stop_tx.take() {
|
||||
let _ = stop_tx.send(code);
|
||||
}
|
||||
}
|
||||
|
||||
SystemCommand::RegisterArbiter(id, arb) => {
|
||||
self.arbiters.insert(id, arb);
|
||||
}
|
||||
|
||||
SystemCommand::DeregisterArbiter(id) => {
|
||||
self.arbiters.remove(&id);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
/// 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() + 'static,
|
||||
{
|
||||
Self::builder().run(f)
|
||||
}
|
||||
}
|
||||
|
@@ -1,17 +0,0 @@
|
||||
//! Checks that test macro does not cause problems in the presence of imports named "test" that
|
||||
//! could be either a module with test items or the "test with runtime" macro itself.
|
||||
//!
|
||||
//! Before actix/actix-net#399 was implemented, this macro was running twice. The first run output
|
||||
//! `#[test]` and it got run again and since it was in scope.
|
||||
//!
|
||||
//! Prevented by using the fully-qualified test marker (`#[::core::prelude::v1::test]`).
|
||||
|
||||
#![cfg(feature = "macros")]
|
||||
|
||||
use actix_rt::time as test;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_naming_conflict() {
|
||||
use test as time;
|
||||
time::sleep(std::time::Duration::from_millis(2)).await;
|
||||
}
|
@@ -1,357 +0,0 @@
|
||||
use std::{
|
||||
future::Future,
|
||||
sync::mpsc::channel,
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use actix_rt::{task::JoinError, Arbiter, System};
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
#[test]
|
||||
fn await_for_timer() {
|
||||
let time = Duration::from_secs(1);
|
||||
let instant = Instant::now();
|
||||
System::new().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(1);
|
||||
let instant = Instant::now();
|
||||
System::new().block_on(async move {
|
||||
let arbiter = Arbiter::new();
|
||||
arbiter.spawn(Box::pin(async move {
|
||||
tokio::time::sleep(time).await;
|
||||
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();
|
||||
System::new().block_on(async move {
|
||||
let arbiter = Arbiter::new();
|
||||
arbiter.spawn_fn(move || {
|
||||
actix_rt::spawn(async move {
|
||||
tokio::time::sleep(time).await;
|
||||
Arbiter::current().stop();
|
||||
});
|
||||
});
|
||||
arbiter.join().unwrap();
|
||||
});
|
||||
assert!(
|
||||
instant.elapsed() >= time,
|
||||
"Join on an arbiter that has used actix_rt::spawn should wait for said future"
|
||||
);
|
||||
|
||||
let instant = Instant::now();
|
||||
System::new().block_on(async move {
|
||||
let arbiter = Arbiter::new();
|
||||
arbiter.spawn(Box::pin(async move {
|
||||
tokio::time::sleep(time).await;
|
||||
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 non_static_block_on() {
|
||||
let string = String::from("test_str");
|
||||
let string = string.as_str();
|
||||
|
||||
let sys = System::new();
|
||||
|
||||
sys.block_on(async {
|
||||
actix_rt::time::sleep(Duration::from_millis(1)).await;
|
||||
assert_eq!("test_str", string);
|
||||
});
|
||||
|
||||
let rt = actix_rt::Runtime::new().unwrap();
|
||||
|
||||
rt.block_on(async {
|
||||
actix_rt::time::sleep(Duration::from_millis(1)).await;
|
||||
assert_eq!("test_str", string);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wait_for_spawns() {
|
||||
let rt = actix_rt::Runtime::new().unwrap();
|
||||
|
||||
let handle = rt.spawn(async {
|
||||
println!("running on the runtime");
|
||||
// assertion panic is caught at task boundary
|
||||
assert_eq!(1, 2);
|
||||
});
|
||||
|
||||
assert!(rt.block_on(handle).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arbiter_spawn_fn_runs() {
|
||||
let _ = System::new();
|
||||
|
||||
let (tx, rx) = channel::<u32>();
|
||||
|
||||
let arbiter = Arbiter::new();
|
||||
arbiter.spawn_fn(move || tx.send(42).unwrap());
|
||||
|
||||
let num = rx.recv().unwrap();
|
||||
assert_eq!(num, 42);
|
||||
|
||||
arbiter.stop();
|
||||
arbiter.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arbiter_handle_spawn_fn_runs() {
|
||||
let sys = System::new();
|
||||
|
||||
let (tx, rx) = channel::<u32>();
|
||||
|
||||
let arbiter = Arbiter::new();
|
||||
let handle = arbiter.handle();
|
||||
drop(arbiter);
|
||||
|
||||
handle.spawn_fn(move || {
|
||||
tx.send(42).unwrap();
|
||||
System::current().stop()
|
||||
});
|
||||
|
||||
let num = rx.recv_timeout(Duration::from_secs(2)).unwrap();
|
||||
assert_eq!(num, 42);
|
||||
|
||||
handle.stop();
|
||||
sys.run().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arbiter_drop_no_panic_fn() {
|
||||
let _ = System::new();
|
||||
|
||||
let arbiter = Arbiter::new();
|
||||
arbiter.spawn_fn(|| panic!("test"));
|
||||
|
||||
arbiter.stop();
|
||||
arbiter.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arbiter_drop_no_panic_fut() {
|
||||
let _ = System::new();
|
||||
|
||||
let arbiter = Arbiter::new();
|
||||
arbiter.spawn(async { panic!("test") });
|
||||
|
||||
arbiter.stop();
|
||||
arbiter.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn no_system_current_panic() {
|
||||
System::current();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn no_system_arbiter_new_panic() {
|
||||
Arbiter::new();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn system_arbiter_spawn() {
|
||||
let runner = System::new();
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let sys = System::current();
|
||||
|
||||
thread::spawn(|| {
|
||||
// this thread will have no arbiter in it's thread local so call will panic
|
||||
Arbiter::current();
|
||||
})
|
||||
.join()
|
||||
.unwrap_err();
|
||||
|
||||
let thread = thread::spawn(|| {
|
||||
// this thread will have no arbiter in it's thread local so use the system handle instead
|
||||
System::set_current(sys);
|
||||
let sys = System::current();
|
||||
|
||||
let arb = sys.arbiter();
|
||||
arb.spawn(async move {
|
||||
tx.send(42u32).unwrap();
|
||||
System::current().stop();
|
||||
});
|
||||
});
|
||||
|
||||
assert_eq!(runner.block_on(rx).unwrap(), 42);
|
||||
thread.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn system_stop_stops_arbiters() {
|
||||
let sys = System::new();
|
||||
let arb = Arbiter::new();
|
||||
|
||||
// arbiter should be alive to receive spawn msg
|
||||
assert!(Arbiter::current().spawn_fn(|| {}));
|
||||
assert!(arb.spawn_fn(|| {}));
|
||||
|
||||
System::current().stop();
|
||||
sys.run().unwrap();
|
||||
|
||||
// account for slightly slow thread de-spawns
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
|
||||
// arbiter should be dead and return false
|
||||
assert!(!Arbiter::current().spawn_fn(|| {}));
|
||||
assert!(!arb.spawn_fn(|| {}));
|
||||
|
||||
arb.join().unwrap();
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "io-uring"))]
|
||||
#[test]
|
||||
fn new_system_with_tokio() {
|
||||
let (tx, rx) = channel();
|
||||
|
||||
let res = System::with_tokio_rt(move || {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_io()
|
||||
.enable_time()
|
||||
.thread_keep_alive(Duration::from_millis(1000))
|
||||
.worker_threads(2)
|
||||
.max_blocking_threads(2)
|
||||
.on_thread_start(|| {})
|
||||
.on_thread_stop(|| {})
|
||||
.build()
|
||||
.unwrap()
|
||||
})
|
||||
.block_on(async {
|
||||
actix_rt::time::sleep(Duration::from_millis(1)).await;
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
tx.send(42).unwrap();
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
123usize
|
||||
});
|
||||
|
||||
assert_eq!(res, 123);
|
||||
assert_eq!(rx.recv().unwrap(), 42);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "io-uring"))]
|
||||
#[test]
|
||||
fn new_arbiter_with_tokio() {
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
};
|
||||
|
||||
let _ = System::new();
|
||||
|
||||
let arb = Arbiter::with_tokio_rt(|| {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
let counter = Arc::new(AtomicBool::new(true));
|
||||
|
||||
let counter1 = counter.clone();
|
||||
let did_spawn = arb.spawn(async move {
|
||||
actix_rt::time::sleep(Duration::from_millis(1)).await;
|
||||
counter1.store(false, Ordering::SeqCst);
|
||||
Arbiter::current().stop();
|
||||
});
|
||||
|
||||
assert!(did_spawn);
|
||||
|
||||
arb.join().unwrap();
|
||||
|
||||
assert!(!counter.load(Ordering::SeqCst));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn try_current_no_system() {
|
||||
assert!(System::try_current().is_none())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn try_current_with_system() {
|
||||
System::new().block_on(async { assert!(System::try_current().is_some()) });
|
||||
}
|
||||
|
||||
#[allow(clippy::unit_cmp)]
|
||||
#[test]
|
||||
fn spawn_local() {
|
||||
System::new().block_on(async {
|
||||
// demonstrate that spawn -> R is strictly more capable than spawn -> ()
|
||||
|
||||
assert_eq!(actix_rt::spawn(async {}).await.unwrap(), ());
|
||||
assert_eq!(actix_rt::spawn(async { 1 }).await.unwrap(), 1);
|
||||
assert!(actix_rt::spawn(async { panic!("") }).await.is_err());
|
||||
|
||||
actix_rt::spawn(async { tokio::time::sleep(Duration::from_millis(50)).await })
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
fn g<F: Future<Output = Result<(), JoinError>>>(_f: F) {}
|
||||
g(actix_rt::spawn(async {}));
|
||||
// g(actix_rt::spawn(async { 1 })); // compile err
|
||||
|
||||
fn h<F: Future<Output = Result<R, JoinError>>, R>(_f: F) {}
|
||||
h(actix_rt::spawn(async {}));
|
||||
h(actix_rt::spawn(async { 1 }));
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(all(target_os = "linux", feature = "io-uring"))]
|
||||
#[test]
|
||||
fn tokio_uring_arbiter() {
|
||||
let system = System::new();
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
|
||||
Arbiter::new().spawn(async move {
|
||||
let handle = actix_rt::spawn(async move {
|
||||
let f = tokio_uring::fs::File::create("test.txt").await.unwrap();
|
||||
let buf = b"Hello World!";
|
||||
|
||||
let (res, _) = f.write_at(&buf[..], 0).await;
|
||||
assert!(res.is_ok());
|
||||
|
||||
f.sync_all().await.unwrap();
|
||||
f.close().await.unwrap();
|
||||
|
||||
std::fs::remove_file("test.txt").unwrap();
|
||||
});
|
||||
|
||||
handle.await.unwrap();
|
||||
tx.send(true).unwrap();
|
||||
});
|
||||
|
||||
assert!(rx.recv().unwrap());
|
||||
|
||||
drop(system);
|
||||
}
|
38
actix-server-config/Cargo.toml
Normal file
38
actix-server-config/Cargo.toml
Normal file
@@ -0,0 +1,38 @@
|
||||
[package]
|
||||
name = "actix-server-config"
|
||||
version = "0.1.2"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix server config utils"
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-net.git"
|
||||
license = "MIT/Apache-2.0"
|
||||
edition = "2018"
|
||||
workspace = ".."
|
||||
|
||||
[lib]
|
||||
name = "actix_server_config"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["ssl", "rust-tls", "uds"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
# openssl
|
||||
ssl = ["tokio-openssl"]
|
||||
|
||||
# rustls
|
||||
rust-tls = ["rustls", "tokio-rustls"]
|
||||
|
||||
# unix domain sockets
|
||||
uds = ["tokio-uds"]
|
||||
|
||||
[dependencies]
|
||||
futures = "0.1.25"
|
||||
tokio-io = "0.1.12"
|
||||
tokio-tcp = "0.1"
|
||||
tokio-openssl = { version="0.3.0", optional = true }
|
||||
rustls = { version = "0.15.2", optional = true }
|
||||
tokio-rustls = { version = "0.9.1", optional = true }
|
||||
tokio-uds = { version="0.2.5", optional = true }
|
14
actix-server-config/changes.md
Normal file
14
actix-server-config/changes.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# Changes
|
||||
|
||||
## [0.1.2] - 2019-07-18
|
||||
|
||||
### Added
|
||||
|
||||
* Add unix domnain sockets support
|
||||
|
||||
|
||||
## [0.1.1] - 2019-04-16
|
||||
|
||||
### Added
|
||||
|
||||
* `IoStream` trait and impls for TcpStream, SslStream and TlsStream
|
241
actix-server-config/src/lib.rs
Normal file
241
actix-server-config/src/lib.rs
Normal file
@@ -0,0 +1,241 @@
|
||||
use std::cell::Cell;
|
||||
use std::net::SocketAddr;
|
||||
use std::rc::Rc;
|
||||
use std::{fmt, io, net, time};
|
||||
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
use tokio_tcp::TcpStream;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ServerConfig {
|
||||
addr: SocketAddr,
|
||||
secure: Rc<Cell<bool>>,
|
||||
}
|
||||
|
||||
impl ServerConfig {
|
||||
pub fn new(addr: SocketAddr) -> Self {
|
||||
ServerConfig {
|
||||
addr,
|
||||
secure: Rc::new(Cell::new(false)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the address of the local half of this TCP server socket
|
||||
pub fn local_addr(&self) -> SocketAddr {
|
||||
self.addr
|
||||
}
|
||||
|
||||
/// Returns true if connection is secure (tls enabled)
|
||||
pub fn secure(&self) -> bool {
|
||||
self.secure.as_ref().get()
|
||||
}
|
||||
|
||||
/// Set secure flag
|
||||
pub fn set_secure(&self) {
|
||||
self.secure.as_ref().set(true)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum Protocol {
|
||||
Unknown,
|
||||
Http10,
|
||||
Http11,
|
||||
Http2,
|
||||
Proto1,
|
||||
Proto2,
|
||||
Proto3,
|
||||
Proto4,
|
||||
Proto5,
|
||||
Proto6,
|
||||
}
|
||||
|
||||
pub struct Io<T, P = ()> {
|
||||
io: T,
|
||||
proto: Protocol,
|
||||
params: P,
|
||||
}
|
||||
|
||||
impl<T> Io<T, ()> {
|
||||
pub fn new(io: T) -> Self {
|
||||
Self {
|
||||
io,
|
||||
proto: Protocol::Unknown,
|
||||
params: (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, P> Io<T, P> {
|
||||
/// Reconstruct from a parts.
|
||||
pub fn from_parts(io: T, params: P, proto: Protocol) -> Self {
|
||||
Self { io, params, proto }
|
||||
}
|
||||
|
||||
/// Deconstruct into a parts.
|
||||
pub fn into_parts(self) -> (T, P, Protocol) {
|
||||
(self.io, self.params, self.proto)
|
||||
}
|
||||
|
||||
/// Returns a shared reference to the underlying stream.
|
||||
pub fn get_ref(&self) -> &T {
|
||||
&self.io
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying stream.
|
||||
pub fn get_mut(&mut self) -> &mut T {
|
||||
&mut self.io
|
||||
}
|
||||
|
||||
/// Get selected protocol
|
||||
pub fn protocol(&self) -> Protocol {
|
||||
self.proto
|
||||
}
|
||||
|
||||
/// Return new Io object with new parameter.
|
||||
pub fn set<U>(self, params: U) -> Io<T, U> {
|
||||
Io {
|
||||
params,
|
||||
io: self.io,
|
||||
proto: self.proto,
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps an Io<_, P> to Io<_, U> by applying a function to a contained value.
|
||||
pub fn map<U, F>(self, op: F) -> Io<T, U>
|
||||
where
|
||||
F: FnOnce(P) -> U,
|
||||
{
|
||||
Io {
|
||||
io: self.io,
|
||||
proto: self.proto,
|
||||
params: op(self.params),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, P> std::ops::Deref for Io<T, P> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
&self.io
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, P> std::ops::DerefMut for Io<T, P> {
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
&mut self.io
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug, P> fmt::Debug for Io<T, P> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "Io {{{:?}}}", self.io)
|
||||
}
|
||||
}
|
||||
|
||||
/// Low-level io stream operations
|
||||
pub trait IoStream: AsyncRead + AsyncWrite {
|
||||
/// Returns the socket address of the remote peer of this TCP connection.
|
||||
fn peer_addr(&self) -> Option<SocketAddr> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Sets the value of the TCP_NODELAY option on this socket.
|
||||
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()>;
|
||||
|
||||
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()>;
|
||||
|
||||
fn set_keepalive(&mut self, dur: Option<time::Duration>) -> io::Result<()>;
|
||||
}
|
||||
|
||||
impl IoStream for TcpStream {
|
||||
#[inline]
|
||||
fn peer_addr(&self) -> Option<net::SocketAddr> {
|
||||
TcpStream::peer_addr(self).ok()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
|
||||
TcpStream::set_nodelay(self, nodelay)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
|
||||
TcpStream::set_linger(self, dur)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_keepalive(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
|
||||
TcpStream::set_keepalive(self, dur)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "ssl"))]
|
||||
impl<T: IoStream> IoStream for tokio_openssl::SslStream<T> {
|
||||
#[inline]
|
||||
fn peer_addr(&self) -> Option<net::SocketAddr> {
|
||||
self.get_ref().get_ref().peer_addr()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
|
||||
self.get_mut().get_mut().set_nodelay(nodelay)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
|
||||
self.get_mut().get_mut().set_linger(dur)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_keepalive(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
|
||||
self.get_mut().get_mut().set_keepalive(dur)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "rust-tls"))]
|
||||
impl<T: IoStream> IoStream for tokio_rustls::TlsStream<T, rustls::ServerSession> {
|
||||
#[inline]
|
||||
fn peer_addr(&self) -> Option<net::SocketAddr> {
|
||||
self.get_ref().0.peer_addr()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
|
||||
self.get_mut().0.set_nodelay(nodelay)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
|
||||
self.get_mut().0.set_linger(dur)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_keepalive(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
|
||||
self.get_mut().0.set_keepalive(dur)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "uds"))]
|
||||
impl IoStream for tokio_uds::UnixStream {
|
||||
#[inline]
|
||||
fn peer_addr(&self) -> Option<net::SocketAddr> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_nodelay(&mut self, _: bool) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_linger(&mut self, _: Option<time::Duration>) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_keepalive(&mut self, _: Option<time::Duration>) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
@@ -1,189 +1,131 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
* Minimum supported Rust version (MSRV) is now 1.52.
|
||||
## [0.6.0] - 2019-07-18
|
||||
|
||||
### Added
|
||||
|
||||
## 2.0.0-beta.6 - 2021-10-11
|
||||
* Add experimental (semver-exempt) `io-uring` feature for enabling async file I/O on linux. [#374]
|
||||
* Server no long listens to `SIGHUP` signal. Previously, the received was not used but did block
|
||||
subsequent exit signals from working. [#389]
|
||||
* Remove `config` module. `ServiceConfig`, `ServiceRuntime` public types are removed due to
|
||||
this change. [#349]
|
||||
* Remove `ServerBuilder::configure` [#349]
|
||||
|
||||
[#374]: https://github.com/actix/actix-net/pull/374
|
||||
[#349]: https://github.com/actix/actix-net/pull/349
|
||||
[#389]: https://github.com/actix/actix-net/pull/389
|
||||
|
||||
|
||||
## 2.0.0-beta.5 - 2021-04-20
|
||||
* Server shutdown notifies all workers to exit regardless if shutdown is graceful. This causes all
|
||||
workers to shutdown immediately in force shutdown case. [#333]
|
||||
|
||||
[#333]: https://github.com/actix/actix-net/pull/333
|
||||
|
||||
|
||||
## 2.0.0-beta.4 - 2021-04-01
|
||||
* Prevent panic when `shutdown_timeout` is very large. [f9262db]
|
||||
|
||||
[f9262db]: https://github.com/actix/actix-net/commit/f9262db
|
||||
|
||||
|
||||
## 2.0.0-beta.3 - 2021-02-06
|
||||
* Hidden `ServerBuilder::start` method has been removed. Use `ServerBuilder::run`. [#246]
|
||||
* Add retry for EINTR signal (`io::Interrupted`) in `Accept`'s poll loop. [#264]
|
||||
* Add `ServerBuilder::worker_max_blocking_threads` to customize blocking thread pool size. [#265]
|
||||
* Update `actix-rt` to `2.0.0`. [#273]
|
||||
|
||||
[#246]: https://github.com/actix/actix-net/pull/246
|
||||
[#264]: https://github.com/actix/actix-net/pull/264
|
||||
[#265]: https://github.com/actix/actix-net/pull/265
|
||||
[#273]: https://github.com/actix/actix-net/pull/273
|
||||
|
||||
|
||||
## 2.0.0-beta.2 - 2021-01-03
|
||||
* Merge `actix-testing` to `actix-server` as `test_server` mod. [#242]
|
||||
|
||||
[#242]: https://github.com/actix/actix-net/pull/242
|
||||
|
||||
|
||||
## 2.0.0-beta.1 - 2020-12-28
|
||||
* Added explicit info log message on accept queue pause. [#215]
|
||||
* Prevent double registration of sockets when back-pressure is resolved. [#223]
|
||||
* Update `mio` dependency to `0.7.3`. [#239]
|
||||
* Remove `socket2` dependency. [#239]
|
||||
* `ServerBuilder::backlog` now accepts `u32` instead of `i32`. [#239]
|
||||
* Remove `AcceptNotify` type and pass `WakerQueue` to `Worker` to wake up `Accept`'s `Poll`. [#239]
|
||||
* Convert `mio::net::TcpStream` to `actix_rt::net::TcpStream`(`UnixStream` for uds) using
|
||||
`FromRawFd` and `IntoRawFd`(`FromRawSocket` and `IntoRawSocket` on windows). [#239]
|
||||
* Remove `AsyncRead` and `AsyncWrite` trait bound for `socket::FromStream` trait. [#239]
|
||||
|
||||
[#215]: https://github.com/actix/actix-net/pull/215
|
||||
[#223]: https://github.com/actix/actix-net/pull/223
|
||||
[#239]: https://github.com/actix/actix-net/pull/239
|
||||
|
||||
|
||||
## 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
|
||||
## [0.5.1] - 2019-05-18
|
||||
|
||||
### Changed
|
||||
|
||||
* ServerBuilder::shutdown_timeout() accepts u64
|
||||
|
||||
|
||||
## 0.5.0 - 2019-05-12
|
||||
## [0.5.0] - 2019-05-12
|
||||
|
||||
### Added
|
||||
|
||||
* Add `Debug` impl for `SslError`
|
||||
|
||||
* Derive debug for `Server` and `ServerCommand`
|
||||
|
||||
### Changed
|
||||
|
||||
* Upgrade to actix-service 0.4
|
||||
|
||||
|
||||
## 0.4.3 - 2019-04-16
|
||||
## [0.4.3] - 2019-04-16
|
||||
|
||||
### Added
|
||||
|
||||
* Re-export `IoStream` trait
|
||||
* Depend on `ssl` and `rust-tls` features from actix-server-config
|
||||
|
||||
### Changed
|
||||
|
||||
* Deppend on `ssl` and `rust-tls` features from actix-server-config
|
||||
|
||||
|
||||
## 0.4.2 - 2019-03-30
|
||||
## [0.4.2] - 2019-03-30
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix SIGINT force shutdown
|
||||
|
||||
|
||||
## 0.4.1 - 2019-03-14
|
||||
## [0.4.1] - 2019-03-14
|
||||
|
||||
### Added
|
||||
|
||||
* `SystemRuntime::on_start()` - allow to run future before server service initialization
|
||||
|
||||
|
||||
## 0.4.0 - 2019-03-12
|
||||
## [0.4.0] - 2019-03-12
|
||||
|
||||
### Changed
|
||||
|
||||
* Use `ServerConfig` for service factory
|
||||
|
||||
* Wrap tcp socket to `Io` type
|
||||
|
||||
* Upgrade actix-service
|
||||
|
||||
|
||||
## 0.3.1 - 2019-03-04
|
||||
## [0.3.1] - 2019-03-04
|
||||
|
||||
### Added
|
||||
|
||||
* Add `ServerBuilder::maxconnrate` sets the maximum per-worker number of concurrent connections
|
||||
|
||||
* Add helper ssl error `SslError`
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
* Rename `StreamServiceFactory` to `ServiceFactory`
|
||||
|
||||
* Deprecate `StreamServiceFactory`
|
||||
|
||||
|
||||
## 0.3.0 - 2019-03-02
|
||||
## [0.3.0] - 2019-03-02
|
||||
|
||||
### Changed
|
||||
|
||||
* Use new `NewService` trait
|
||||
|
||||
|
||||
## 0.2.1 - 2019-02-09
|
||||
## [0.2.1] - 2019-02-09
|
||||
|
||||
### Changed
|
||||
|
||||
* Drop service response
|
||||
|
||||
|
||||
## 0.2.0 - 2019-02-01
|
||||
## [0.2.0] - 2019-02-01
|
||||
|
||||
### Changed
|
||||
|
||||
* Migrate to actix-service 0.2
|
||||
|
||||
* Updated rustls dependency
|
||||
|
||||
|
||||
## 0.1.3 - 2018-12-21
|
||||
## [0.1.3] - 2018-12-21
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix max concurrent connections handling
|
||||
|
||||
|
||||
## 0.1.2 - 2018-12-12
|
||||
## [0.1.2] - 2018-12-12
|
||||
|
||||
### Changed
|
||||
|
||||
* rename ServiceConfig::rt() to ServiceConfig::apply()
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix back-pressure for concurrent ssl handshakes
|
||||
|
||||
|
||||
## 0.1.1 - 2018-12-11
|
||||
## [0.1.1] - 2018-12-11
|
||||
|
||||
* Fix signal handling on windows
|
||||
|
||||
|
||||
## 0.1.0 - 2018-12-09
|
||||
## [0.1.0] - 2018-12-09
|
||||
|
||||
* Move server to separate crate
|
||||
|
79
actix-server/Cargo.toml
Executable file → Normal file
79
actix-server/Cargo.toml
Executable file → Normal file
@@ -1,16 +1,20 @@
|
||||
[package]
|
||||
name = "actix-server"
|
||||
version = "2.0.0-beta.6"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"fakeshadow <24548779@qq.com>",
|
||||
]
|
||||
description = "General purpose TCP server built for the Actix ecosystem"
|
||||
version = "0.6.0"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix server - General purpose tcp server"
|
||||
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"
|
||||
license = "MIT/Apache-2.0"
|
||||
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
|
||||
edition = "2018"
|
||||
workspace = ".."
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["ssl", "tls", "rust-tls", "uds"]
|
||||
|
||||
[lib]
|
||||
name = "actix_server"
|
||||
@@ -18,24 +22,55 @@ path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
io-uring = ["actix-rt/io-uring"]
|
||||
|
||||
# tls
|
||||
tls = ["native-tls"]
|
||||
|
||||
# openssl
|
||||
ssl = ["openssl", "tokio-openssl", "actix-server-config/ssl"]
|
||||
|
||||
# rustls
|
||||
rust-tls = ["rustls", "tokio-rustls", "webpki", "webpki-roots", "actix-server-config/rust-tls"]
|
||||
|
||||
# uds
|
||||
uds = ["mio-uds", "tokio-uds", "actix-server-config/uds"]
|
||||
|
||||
[dependencies]
|
||||
actix-rt = { version = "2.0.0", default-features = false }
|
||||
actix-service = "2.0.0"
|
||||
actix-utils = "3.0.0"
|
||||
actix-rt = "0.2.2"
|
||||
actix-service = "0.4.1"
|
||||
actix-server-config = "0.1.2"
|
||||
|
||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||
log = "0.4"
|
||||
mio = { version = "0.7.6", features = ["os-poll", "net"] }
|
||||
num_cpus = "1.13"
|
||||
tokio = { version = "1.5.1", features = ["sync"] }
|
||||
num_cpus = "1.0"
|
||||
|
||||
mio = "0.6.19"
|
||||
net2 = "0.2"
|
||||
futures = "0.1"
|
||||
slab = "0.4"
|
||||
tokio-io = "0.1"
|
||||
tokio-tcp = "0.1"
|
||||
tokio-timer = "0.2.8"
|
||||
tokio-reactor = "0.1"
|
||||
tokio-signal = "0.2"
|
||||
|
||||
# unix domain sockets
|
||||
mio-uds = { version="0.6.7", optional = true }
|
||||
tokio-uds = { version="0.2.5", optional = true }
|
||||
|
||||
# native-tls
|
||||
native-tls = { version="0.2", optional = true }
|
||||
|
||||
# openssl
|
||||
openssl = { version="0.10", optional = true }
|
||||
tokio-openssl = { version="0.3", optional = true }
|
||||
|
||||
# rustls
|
||||
rustls = { version = "0.15.2", optional = true }
|
||||
tokio-rustls = { version = "0.9.1", optional = true }
|
||||
webpki = { version = "0.19", optional = true }
|
||||
webpki-roots = { version = "0.16", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-codec = "0.4.0-beta.1"
|
||||
actix-rt = "2.0.0"
|
||||
|
||||
bytes = "1"
|
||||
env_logger = "0.9"
|
||||
futures-util = { version = "0.3.7", default-features = false, features = ["sink"] }
|
||||
tokio = { version = "1.5.1", features = ["io-util"] }
|
||||
bytes = "0.4"
|
||||
actix-codec = "0.1.2"
|
||||
env_logger = "0.6"
|
||||
|
@@ -1,90 +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::{
|
||||
env, io,
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
|
||||
use actix_rt::net::TcpStream;
|
||||
use actix_server::Server;
|
||||
use actix_service::{fn_service, ServiceFactoryExt as _};
|
||||
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", "info");
|
||||
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);
|
||||
|
||||
fn_service(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,139 +1,120 @@
|
||||
use std::time::Duration;
|
||||
use std::sync::mpsc as sync_mpsc;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{io, thread};
|
||||
|
||||
use actix_rt::{
|
||||
time::{sleep, Instant},
|
||||
System,
|
||||
};
|
||||
use actix_rt::System;
|
||||
use futures::future::{lazy, Future};
|
||||
use log::{error, info};
|
||||
use mio::{Interest, Poll, Token as MioToken};
|
||||
use slab::Slab;
|
||||
use tokio_timer::Delay;
|
||||
|
||||
use crate::server::Server;
|
||||
use crate::socket::MioListener;
|
||||
use crate::waker_queue::{WakerInterest, WakerQueue, WAKER_TOKEN};
|
||||
use crate::worker::{Conn, WorkerHandleAccept};
|
||||
use crate::socket::{SocketAddr, SocketListener, StdListener};
|
||||
use crate::worker::{Conn, WorkerClient};
|
||||
use crate::Token;
|
||||
|
||||
pub(crate) enum Command {
|
||||
Pause,
|
||||
Resume,
|
||||
Stop,
|
||||
Worker(WorkerClient),
|
||||
}
|
||||
|
||||
struct ServerSocketInfo {
|
||||
token: usize,
|
||||
|
||||
lst: MioListener,
|
||||
|
||||
/// Timeout is used to mark the deadline when this socket's listener should be registered again
|
||||
/// after an error.
|
||||
addr: SocketAddr,
|
||||
token: Token,
|
||||
sock: SocketListener,
|
||||
timeout: Option<Instant>,
|
||||
}
|
||||
|
||||
/// Accept loop would live with `ServerBuilder`.
|
||||
///
|
||||
/// It's tasked with construct `Poll` instance and `WakerQueue` which would be distributed to
|
||||
/// `Accept` and `Worker`.
|
||||
///
|
||||
/// It would also listen to `ServerCommand` and push interests to `WakerQueue`.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct AcceptNotify(mio::SetReadiness);
|
||||
|
||||
impl AcceptNotify {
|
||||
pub(crate) fn new(ready: mio::SetReadiness) -> Self {
|
||||
AcceptNotify(ready)
|
||||
}
|
||||
|
||||
pub(crate) fn notify(&self) {
|
||||
let _ = self.0.set_readiness(mio::Ready::readable());
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AcceptNotify {
|
||||
fn default() -> Self {
|
||||
AcceptNotify::new(mio::Registration::new2().1)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct AcceptLoop {
|
||||
cmd_reg: Option<mio::Registration>,
|
||||
cmd_ready: mio::SetReadiness,
|
||||
notify_reg: Option<mio::Registration>,
|
||||
notify_ready: mio::SetReadiness,
|
||||
tx: sync_mpsc::Sender<Command>,
|
||||
rx: Option<sync_mpsc::Receiver<Command>>,
|
||||
srv: Option<Server>,
|
||||
poll: Option<Poll>,
|
||||
waker: WakerQueue,
|
||||
}
|
||||
|
||||
impl AcceptLoop {
|
||||
pub fn new(srv: Server) -> Self {
|
||||
let poll = Poll::new().unwrap_or_else(|e| panic!("Can not create `mio::Poll`: {}", e));
|
||||
let waker = WakerQueue::new(poll.registry())
|
||||
.unwrap_or_else(|e| panic!("Can not create `mio::Waker`: {}", e));
|
||||
pub fn new(srv: Server) -> AcceptLoop {
|
||||
let (tx, rx) = sync_mpsc::channel();
|
||||
let (cmd_reg, cmd_ready) = mio::Registration::new2();
|
||||
let (notify_reg, notify_ready) = mio::Registration::new2();
|
||||
|
||||
Self {
|
||||
AcceptLoop {
|
||||
tx,
|
||||
cmd_ready,
|
||||
cmd_reg: Some(cmd_reg),
|
||||
notify_ready,
|
||||
notify_reg: Some(notify_reg),
|
||||
rx: Some(rx),
|
||||
srv: Some(srv),
|
||||
poll: Some(poll),
|
||||
waker,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn waker_owned(&self) -> WakerQueue {
|
||||
self.waker.clone()
|
||||
pub fn send(&self, msg: Command) {
|
||||
let _ = self.tx.send(msg);
|
||||
let _ = self.cmd_ready.set_readiness(mio::Ready::readable());
|
||||
}
|
||||
|
||||
pub fn wake(&self, i: WakerInterest) {
|
||||
self.waker.wake(i);
|
||||
pub fn get_notify(&self) -> AcceptNotify {
|
||||
AcceptNotify::new(self.notify_ready.clone())
|
||||
}
|
||||
|
||||
pub(crate) fn start(
|
||||
&mut self,
|
||||
socks: Vec<(usize, MioListener)>,
|
||||
handles: Vec<WorkerHandleAccept>,
|
||||
socks: Vec<(Token, StdListener)>,
|
||||
workers: Vec<WorkerClient>,
|
||||
) {
|
||||
let srv = self.srv.take().expect("Can not re-use AcceptInfo");
|
||||
let poll = self.poll.take().unwrap();
|
||||
let waker = self.waker.clone();
|
||||
|
||||
Accept::start(poll, waker, socks, srv, handles);
|
||||
Accept::start(
|
||||
self.rx.take().expect("Can not re-use AcceptInfo"),
|
||||
self.cmd_reg.take().expect("Can not re-use AcceptInfo"),
|
||||
self.notify_reg.take().expect("Can not re-use AcceptInfo"),
|
||||
socks,
|
||||
srv,
|
||||
workers,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// poll instance of the server.
|
||||
struct Accept {
|
||||
poll: Poll,
|
||||
waker: WakerQueue,
|
||||
handles: Vec<WorkerHandleAccept>,
|
||||
poll: mio::Poll,
|
||||
rx: sync_mpsc::Receiver<Command>,
|
||||
sockets: Slab<ServerSocketInfo>,
|
||||
workers: Vec<WorkerClient>,
|
||||
srv: Server,
|
||||
timer: (mio::Registration, mio::SetReadiness),
|
||||
next: usize,
|
||||
avail: Availability,
|
||||
paused: bool,
|
||||
backpressure: bool,
|
||||
}
|
||||
|
||||
/// Array of u128 with every bit as marker for a worker handle's availability.
|
||||
#[derive(Debug, Default)]
|
||||
struct Availability([u128; 4]);
|
||||
|
||||
impl Availability {
|
||||
/// Check if any worker handle is available
|
||||
#[inline(always)]
|
||||
fn available(&self) -> bool {
|
||||
self.0.iter().any(|a| *a != 0)
|
||||
}
|
||||
|
||||
/// Check if worker handle is available by index
|
||||
#[inline(always)]
|
||||
fn get_available(&self, idx: usize) -> bool {
|
||||
let (offset, idx) = Self::offset(idx);
|
||||
|
||||
self.0[offset] & (1 << idx as u128) != 0
|
||||
}
|
||||
|
||||
/// Set worker handle available state by index.
|
||||
fn set_available(&mut self, idx: usize, avail: bool) {
|
||||
let (offset, idx) = Self::offset(idx);
|
||||
|
||||
let off = 1 << idx as u128;
|
||||
if avail {
|
||||
self.0[offset] |= off;
|
||||
} else {
|
||||
self.0[offset] &= !off
|
||||
}
|
||||
}
|
||||
|
||||
/// Set all worker handle to available state.
|
||||
/// This would result in a re-check on all workers' availability.
|
||||
fn set_available_all(&mut self, handles: &[WorkerHandleAccept]) {
|
||||
handles.iter().for_each(|handle| {
|
||||
self.set_available(handle.idx(), true);
|
||||
})
|
||||
}
|
||||
|
||||
/// Get offset and adjusted index of given worker handle index.
|
||||
fn offset(idx: usize) -> (usize, usize) {
|
||||
if idx < 128 {
|
||||
(0, idx)
|
||||
} else if idx < 128 * 2 {
|
||||
(1, idx - 128)
|
||||
} else if idx < 128 * 3 {
|
||||
(2, idx - 128 * 2)
|
||||
} else if idx < 128 * 4 {
|
||||
(3, idx - 128 * 3)
|
||||
} else {
|
||||
panic!("Max WorkerHandle count is 512")
|
||||
}
|
||||
}
|
||||
}
|
||||
const DELTA: usize = 100;
|
||||
const CMD: mio::Token = mio::Token(0);
|
||||
const TIMER: mio::Token = mio::Token(1);
|
||||
const NOTIFY: mio::Token = mio::Token(2);
|
||||
|
||||
/// This function defines errors that are per-connection. Which basically
|
||||
/// means that if we get this error from `accept()` system call it means
|
||||
@@ -149,439 +130,332 @@ fn connection_error(e: &io::Error) -> bool {
|
||||
}
|
||||
|
||||
impl Accept {
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn start(
|
||||
poll: Poll,
|
||||
waker: WakerQueue,
|
||||
socks: Vec<(usize, MioListener)>,
|
||||
rx: sync_mpsc::Receiver<Command>,
|
||||
cmd_reg: mio::Registration,
|
||||
notify_reg: mio::Registration,
|
||||
socks: Vec<(Token, StdListener)>,
|
||||
srv: Server,
|
||||
handles: Vec<WorkerHandleAccept>,
|
||||
workers: Vec<WorkerClient>,
|
||||
) {
|
||||
// Accept runs in its own thread and would want to spawn additional futures to current
|
||||
// actix system.
|
||||
let sys = System::current();
|
||||
thread::Builder::new()
|
||||
|
||||
// start accept thread
|
||||
let _ = thread::Builder::new()
|
||||
.name("actix-server accept loop".to_owned())
|
||||
.spawn(move || {
|
||||
System::set_current(sys);
|
||||
let (mut accept, mut sockets) =
|
||||
Accept::new_with_sockets(poll, waker, socks, handles, srv);
|
||||
let mut accept = Accept::new(rx, socks, workers, srv);
|
||||
|
||||
accept.poll_with(&mut sockets);
|
||||
})
|
||||
.unwrap();
|
||||
// Start listening for incoming commands
|
||||
if let Err(err) = accept.poll.register(
|
||||
&cmd_reg,
|
||||
CMD,
|
||||
mio::Ready::readable(),
|
||||
mio::PollOpt::edge(),
|
||||
) {
|
||||
panic!("Can not register Registration: {}", err);
|
||||
}
|
||||
|
||||
// Start listening for notify updates
|
||||
if let Err(err) = accept.poll.register(
|
||||
¬ify_reg,
|
||||
NOTIFY,
|
||||
mio::Ready::readable(),
|
||||
mio::PollOpt::edge(),
|
||||
) {
|
||||
panic!("Can not register Registration: {}", err);
|
||||
}
|
||||
|
||||
accept.poll();
|
||||
});
|
||||
}
|
||||
|
||||
fn new_with_sockets(
|
||||
poll: Poll,
|
||||
waker: WakerQueue,
|
||||
socks: Vec<(usize, MioListener)>,
|
||||
handles: Vec<WorkerHandleAccept>,
|
||||
fn new(
|
||||
rx: sync_mpsc::Receiver<Command>,
|
||||
socks: Vec<(Token, StdListener)>,
|
||||
workers: Vec<WorkerClient>,
|
||||
srv: Server,
|
||||
) -> (Accept, Vec<ServerSocketInfo>) {
|
||||
let sockets = socks
|
||||
.into_iter()
|
||||
.map(|(token, mut lst)| {
|
||||
// Start listening for incoming connections
|
||||
poll.registry()
|
||||
.register(&mut lst, MioToken(token), Interest::READABLE)
|
||||
.unwrap_or_else(|e| panic!("Can not register io: {}", e));
|
||||
|
||||
ServerSocketInfo {
|
||||
token,
|
||||
lst,
|
||||
timeout: None,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut avail = Availability::default();
|
||||
|
||||
// Assume all handles are avail at construct time.
|
||||
avail.set_available_all(&handles);
|
||||
|
||||
let accept = Accept {
|
||||
poll,
|
||||
waker,
|
||||
handles,
|
||||
srv,
|
||||
next: 0,
|
||||
avail,
|
||||
paused: false,
|
||||
) -> Accept {
|
||||
// Create a poll instance
|
||||
let poll = match mio::Poll::new() {
|
||||
Ok(poll) => poll,
|
||||
Err(err) => panic!("Can not create mio::Poll: {}", err),
|
||||
};
|
||||
|
||||
(accept, sockets)
|
||||
// Start accept
|
||||
let mut sockets = Slab::new();
|
||||
for (hnd_token, lst) in socks.into_iter() {
|
||||
let addr = lst.local_addr();
|
||||
|
||||
let server = lst.into_listener();
|
||||
let entry = sockets.vacant_entry();
|
||||
let token = entry.key();
|
||||
|
||||
// Start listening for incoming connections
|
||||
if let Err(err) = poll.register(
|
||||
&server,
|
||||
mio::Token(token + DELTA),
|
||||
mio::Ready::readable(),
|
||||
mio::PollOpt::edge(),
|
||||
) {
|
||||
panic!("Can not register io: {}", err);
|
||||
}
|
||||
|
||||
entry.insert(ServerSocketInfo {
|
||||
addr,
|
||||
token: hnd_token,
|
||||
sock: server,
|
||||
timeout: None,
|
||||
});
|
||||
}
|
||||
|
||||
// Timer
|
||||
let (tm, tmr) = mio::Registration::new2();
|
||||
if let Err(err) =
|
||||
poll.register(&tm, TIMER, mio::Ready::readable(), mio::PollOpt::edge())
|
||||
{
|
||||
panic!("Can not register Registration: {}", err);
|
||||
}
|
||||
|
||||
Accept {
|
||||
poll,
|
||||
rx,
|
||||
sockets,
|
||||
workers,
|
||||
srv,
|
||||
next: 0,
|
||||
timer: (tm, tmr),
|
||||
backpressure: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_with(&mut self, sockets: &mut [ServerSocketInfo]) {
|
||||
fn poll(&mut self) {
|
||||
// Create storage for events
|
||||
let mut events = mio::Events::with_capacity(128);
|
||||
|
||||
loop {
|
||||
if let Err(e) = self.poll.poll(&mut events, None) {
|
||||
match e.kind() {
|
||||
io::ErrorKind::Interrupted => {}
|
||||
_ => panic!("Poll error: {}", e),
|
||||
}
|
||||
if let Err(err) = self.poll.poll(&mut events, None) {
|
||||
panic!("Poll error: {}", err);
|
||||
}
|
||||
|
||||
for event in events.iter() {
|
||||
let token = event.token();
|
||||
match token {
|
||||
WAKER_TOKEN => {
|
||||
let exit = self.handle_waker(sockets);
|
||||
if exit {
|
||||
info!("Accept is stopped.");
|
||||
CMD => {
|
||||
if !self.process_cmd() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
TIMER => self.process_timer(),
|
||||
NOTIFY => self.backpressure(false),
|
||||
_ => {
|
||||
let token = usize::from(token);
|
||||
self.accept(sockets, token);
|
||||
if token < DELTA {
|
||||
continue;
|
||||
}
|
||||
self.accept(token - DELTA);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_waker(&mut self, sockets: &mut [ServerSocketInfo]) -> bool {
|
||||
// This is a loop because interests for command from previous version was
|
||||
// a loop that would try to drain the command channel. It's yet unknown
|
||||
// if it's necessary/good practice to actively drain the waker queue.
|
||||
loop {
|
||||
// take guard with every iteration so no new interest can be added
|
||||
// until the current task is done.
|
||||
let mut guard = self.waker.guard();
|
||||
match guard.pop_front() {
|
||||
// worker notify it becomes available.
|
||||
Some(WakerInterest::WorkerAvailable(idx)) => {
|
||||
drop(guard);
|
||||
|
||||
self.avail.set_available(idx, true);
|
||||
|
||||
if !self.paused {
|
||||
self.accept_all(sockets);
|
||||
}
|
||||
}
|
||||
// a new worker thread is made and it's handle would be added to Accept
|
||||
Some(WakerInterest::Worker(handle)) => {
|
||||
drop(guard);
|
||||
|
||||
self.avail.set_available(handle.idx(), true);
|
||||
self.handles.push(handle);
|
||||
|
||||
if !self.paused {
|
||||
self.accept_all(sockets);
|
||||
}
|
||||
}
|
||||
// got timer interest and it's time to try register socket(s) again
|
||||
Some(WakerInterest::Timer) => {
|
||||
drop(guard);
|
||||
|
||||
self.process_timer(sockets)
|
||||
}
|
||||
Some(WakerInterest::Pause) => {
|
||||
drop(guard);
|
||||
|
||||
if !self.paused {
|
||||
self.paused = true;
|
||||
|
||||
self.deregister_all(sockets);
|
||||
}
|
||||
}
|
||||
Some(WakerInterest::Resume) => {
|
||||
drop(guard);
|
||||
|
||||
if self.paused {
|
||||
self.paused = false;
|
||||
|
||||
sockets.iter_mut().for_each(|info| {
|
||||
self.register_logged(info);
|
||||
});
|
||||
|
||||
self.accept_all(sockets);
|
||||
}
|
||||
}
|
||||
Some(WakerInterest::Stop) => {
|
||||
if !self.paused {
|
||||
self.deregister_all(sockets);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
// waker queue is drained
|
||||
None => {
|
||||
// Reset the WakerQueue before break so it does not grow infinitely
|
||||
WakerQueue::reset(&mut guard);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_timer(&self, sockets: &mut [ServerSocketInfo]) {
|
||||
fn process_timer(&mut self) {
|
||||
let now = Instant::now();
|
||||
sockets
|
||||
.iter_mut()
|
||||
// Only sockets that had an associated timeout were deregistered.
|
||||
.filter(|info| info.timeout.is_some())
|
||||
.for_each(|info| {
|
||||
let inst = info.timeout.take().unwrap();
|
||||
|
||||
if now < inst {
|
||||
for (token, info) in self.sockets.iter_mut() {
|
||||
if let Some(inst) = info.timeout.take() {
|
||||
if now > inst {
|
||||
if let Err(err) = self.poll.register(
|
||||
&info.sock,
|
||||
mio::Token(token + DELTA),
|
||||
mio::Ready::readable(),
|
||||
mio::PollOpt::edge(),
|
||||
) {
|
||||
error!("Can not register server socket {}", err);
|
||||
} else {
|
||||
info!("Resume accepting connections on {}", info.addr);
|
||||
}
|
||||
} else {
|
||||
info.timeout = Some(inst);
|
||||
} else if !self.paused {
|
||||
self.register_logged(info);
|
||||
}
|
||||
|
||||
// Drop the timeout if server is paused and socket timeout is expired.
|
||||
// When server recovers from pause it will register all sockets without
|
||||
// a timeout value so this socket register will be delayed till then.
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn register(&self, info: &mut ServerSocketInfo) -> io::Result<()> {
|
||||
let token = MioToken(info.token);
|
||||
self.poll
|
||||
.registry()
|
||||
.register(&mut info.lst, token, Interest::READABLE)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn register(&self, info: &mut ServerSocketInfo) -> io::Result<()> {
|
||||
// On windows, calling register without deregister cause an error.
|
||||
// See https://github.com/actix/actix-web/issues/905
|
||||
// Calling reregister seems to fix the issue.
|
||||
let token = MioToken(info.token);
|
||||
self.poll
|
||||
.registry()
|
||||
.register(&mut info.lst, token, Interest::READABLE)
|
||||
.or_else(|_| {
|
||||
self.poll
|
||||
.registry()
|
||||
.reregister(&mut info.lst, token, Interest::READABLE)
|
||||
})
|
||||
}
|
||||
|
||||
fn register_logged(&self, info: &mut ServerSocketInfo) {
|
||||
match self.register(info) {
|
||||
Ok(_) => info!("Resume accepting connections on {}", info.lst.local_addr()),
|
||||
Err(e) => error!("Can not register server socket {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
fn deregister_logged(&self, info: &mut ServerSocketInfo) {
|
||||
match self.poll.registry().deregister(&mut info.lst) {
|
||||
Ok(_) => info!("Paused accepting connections on {}", info.lst.local_addr()),
|
||||
Err(e) => {
|
||||
error!("Can not deregister server socket {}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn deregister_all(&self, sockets: &mut [ServerSocketInfo]) {
|
||||
// This is a best effort implementation with following limitation:
|
||||
//
|
||||
// Every ServerSocketInfo with associate timeout will be skipped and it's timeout
|
||||
// is removed in the process.
|
||||
//
|
||||
// Therefore WakerInterest::Pause followed by WakerInterest::Resume in a very short
|
||||
// gap (less than 500ms) would cause all timing out ServerSocketInfos be reregistered
|
||||
// before expected timing.
|
||||
sockets
|
||||
.iter_mut()
|
||||
// Take all timeout.
|
||||
// This is to prevent Accept::process_timer method re-register a socket afterwards.
|
||||
.map(|info| (info.timeout.take(), info))
|
||||
// Socket info with a timeout is already deregistered so skip them.
|
||||
.filter(|(timeout, _)| timeout.is_none())
|
||||
.for_each(|(_, info)| self.deregister_logged(info));
|
||||
}
|
||||
|
||||
// Send connection to worker and handle error.
|
||||
fn send_connection(&mut self, conn: Conn) -> Result<(), Conn> {
|
||||
let next = self.next();
|
||||
match next.send(conn) {
|
||||
Ok(_) => {
|
||||
// Increment counter of WorkerHandle.
|
||||
// Set worker to unavailable with it hit max (Return false).
|
||||
if !next.inc_counter() {
|
||||
let idx = next.idx();
|
||||
self.avail.set_available(idx, false);
|
||||
}
|
||||
self.set_next();
|
||||
Ok(())
|
||||
}
|
||||
Err(conn) => {
|
||||
// Worker thread is error and could be gone.
|
||||
// Remove worker handle and notify `ServerBuilder`.
|
||||
self.remove_next();
|
||||
|
||||
if self.handles.is_empty() {
|
||||
error!("No workers");
|
||||
// All workers are gone and Conn is nowhere to be sent.
|
||||
// Treat this situation as Ok and drop Conn.
|
||||
return Ok(());
|
||||
} else if self.handles.len() <= self.next {
|
||||
self.next = 0;
|
||||
}
|
||||
|
||||
Err(conn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn accept_one(&mut self, mut conn: Conn) {
|
||||
fn process_cmd(&mut self) -> bool {
|
||||
loop {
|
||||
let next = self.next();
|
||||
let idx = next.idx();
|
||||
match self.rx.try_recv() {
|
||||
Ok(cmd) => match cmd {
|
||||
Command::Pause => {
|
||||
for (_, info) in self.sockets.iter_mut() {
|
||||
if let Err(err) = self.poll.deregister(&info.sock) {
|
||||
error!("Can not deregister server socket {}", err);
|
||||
} else {
|
||||
info!("Paused accepting connections on {}", info.addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
Command::Resume => {
|
||||
for (token, info) in self.sockets.iter() {
|
||||
if let Err(err) = self.poll.register(
|
||||
&info.sock,
|
||||
mio::Token(token + DELTA),
|
||||
mio::Ready::readable(),
|
||||
mio::PollOpt::edge(),
|
||||
) {
|
||||
error!("Can not resume socket accept process: {}", err);
|
||||
} else {
|
||||
info!(
|
||||
"Accepting connections on {} has been resumed",
|
||||
info.addr
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Command::Stop => {
|
||||
for (_, info) in self.sockets.iter() {
|
||||
let _ = self.poll.deregister(&info.sock);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
Command::Worker(worker) => {
|
||||
self.backpressure(false);
|
||||
self.workers.push(worker);
|
||||
}
|
||||
},
|
||||
Err(err) => match err {
|
||||
sync_mpsc::TryRecvError::Empty => break,
|
||||
sync_mpsc::TryRecvError::Disconnected => {
|
||||
for (_, info) in self.sockets.iter() {
|
||||
let _ = self.poll.deregister(&info.sock);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
if self.avail.get_available(idx) {
|
||||
match self.send_connection(conn) {
|
||||
Ok(_) => return,
|
||||
Err(c) => conn = c,
|
||||
fn backpressure(&mut self, on: bool) {
|
||||
if self.backpressure {
|
||||
if !on {
|
||||
self.backpressure = false;
|
||||
for (token, info) in self.sockets.iter() {
|
||||
if let Err(err) = self.poll.register(
|
||||
&info.sock,
|
||||
mio::Token(token + DELTA),
|
||||
mio::Ready::readable(),
|
||||
mio::PollOpt::edge(),
|
||||
) {
|
||||
error!("Can not resume socket accept process: {}", err);
|
||||
} else {
|
||||
info!("Accepting connections on {} has been resumed", info.addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if on {
|
||||
self.backpressure = true;
|
||||
for (_, info) in self.sockets.iter() {
|
||||
let _ = self.poll.deregister(&info.sock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn accept_one(&mut self, mut msg: Conn) {
|
||||
if self.backpressure {
|
||||
while !self.workers.is_empty() {
|
||||
match self.workers[self.next].send(msg) {
|
||||
Ok(_) => (),
|
||||
Err(tmp) => {
|
||||
self.srv.worker_died(self.workers[self.next].idx);
|
||||
msg = tmp;
|
||||
self.workers.swap_remove(self.next);
|
||||
if self.workers.is_empty() {
|
||||
error!("No workers");
|
||||
return;
|
||||
} else if self.workers.len() <= self.next {
|
||||
self.next = 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
self.next = (self.next + 1) % self.workers.len();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
let mut idx = 0;
|
||||
while idx < self.workers.len() {
|
||||
idx += 1;
|
||||
if self.workers[self.next].available() {
|
||||
match self.workers[self.next].send(msg) {
|
||||
Ok(_) => {
|
||||
self.next = (self.next + 1) % self.workers.len();
|
||||
return;
|
||||
}
|
||||
Err(tmp) => {
|
||||
self.srv.worker_died(self.workers[self.next].idx);
|
||||
msg = tmp;
|
||||
self.workers.swap_remove(self.next);
|
||||
if self.workers.is_empty() {
|
||||
error!("No workers");
|
||||
self.backpressure(true);
|
||||
return;
|
||||
} else if self.workers.len() <= self.next {
|
||||
self.next = 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.next = (self.next + 1) % self.workers.len();
|
||||
}
|
||||
// enable backpressure
|
||||
self.backpressure(true);
|
||||
self.accept_one(msg);
|
||||
}
|
||||
}
|
||||
|
||||
fn accept(&mut self, token: usize) {
|
||||
loop {
|
||||
let msg = if let Some(info) = self.sockets.get_mut(token) {
|
||||
match info.sock.accept() {
|
||||
Ok(Some((io, addr))) => Conn {
|
||||
io,
|
||||
token: info.token,
|
||||
peer: Some(addr),
|
||||
},
|
||||
Ok(None) => return,
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => return,
|
||||
Err(ref e) if connection_error(e) => continue,
|
||||
Err(e) => {
|
||||
error!("Error accepting connection: {}", e);
|
||||
if let Err(err) = self.poll.deregister(&info.sock) {
|
||||
error!("Can not deregister server socket {}", err);
|
||||
}
|
||||
|
||||
// sleep after error
|
||||
info.timeout = Some(Instant::now() + Duration::from_millis(500));
|
||||
|
||||
let r = self.timer.1.clone();
|
||||
System::current().arbiter().send(lazy(move || {
|
||||
Delay::new(Instant::now() + Duration::from_millis(510))
|
||||
.map_err(|_| ())
|
||||
.and_then(move |_| {
|
||||
let _ = r.set_readiness(mio::Ready::readable());
|
||||
Ok(())
|
||||
})
|
||||
}));
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.avail.set_available(idx, false);
|
||||
self.set_next();
|
||||
|
||||
if !self.avail.available() {
|
||||
while let Err(c) = self.send_connection(conn) {
|
||||
conn = c;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn accept(&mut self, sockets: &mut [ServerSocketInfo], token: usize) {
|
||||
while self.avail.available() {
|
||||
let info = &mut sockets[token];
|
||||
|
||||
match info.lst.accept() {
|
||||
Ok(io) => {
|
||||
let conn = Conn { io, token };
|
||||
self.accept_one(conn);
|
||||
}
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => return,
|
||||
Err(ref e) if connection_error(e) => continue,
|
||||
Err(e) => {
|
||||
error!("Error accepting connection: {}", e);
|
||||
|
||||
// deregister listener temporary
|
||||
self.deregister_logged(info);
|
||||
|
||||
// sleep after error. write the timeout to socket info as later
|
||||
// the poll would need it mark which socket and when it's
|
||||
// listener should be registered
|
||||
info.timeout = Some(Instant::now() + Duration::from_millis(500));
|
||||
|
||||
// after the sleep a Timer interest is sent to Accept Poll
|
||||
let waker = self.waker.clone();
|
||||
System::current().arbiter().spawn(async move {
|
||||
sleep(Duration::from_millis(510)).await;
|
||||
waker.wake(WakerInterest::Timer);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
self.accept_one(msg);
|
||||
}
|
||||
}
|
||||
|
||||
fn accept_all(&mut self, sockets: &mut [ServerSocketInfo]) {
|
||||
sockets
|
||||
.iter_mut()
|
||||
.map(|info| info.token)
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
.for_each(|idx| self.accept(sockets, idx))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn next(&self) -> &WorkerHandleAccept {
|
||||
&self.handles[self.next]
|
||||
}
|
||||
|
||||
/// Set next worker handle that would accept connection.
|
||||
#[inline(always)]
|
||||
fn set_next(&mut self) {
|
||||
self.next = (self.next + 1) % self.handles.len();
|
||||
}
|
||||
|
||||
/// Remove next worker handle that fail to accept connection.
|
||||
fn remove_next(&mut self) {
|
||||
let handle = self.handles.swap_remove(self.next);
|
||||
let idx = handle.idx();
|
||||
// A message is sent to `ServerBuilder` future to notify it a new worker
|
||||
// should be made.
|
||||
self.srv.worker_faulted(idx);
|
||||
self.avail.set_available(idx, false);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::Availability;
|
||||
|
||||
fn single(aval: &mut Availability, idx: usize) {
|
||||
aval.set_available(idx, true);
|
||||
assert!(aval.available());
|
||||
|
||||
aval.set_available(idx, true);
|
||||
|
||||
aval.set_available(idx, false);
|
||||
assert!(!aval.available());
|
||||
|
||||
aval.set_available(idx, false);
|
||||
assert!(!aval.available());
|
||||
}
|
||||
|
||||
fn multi(aval: &mut Availability, mut idx: Vec<usize>) {
|
||||
idx.iter().for_each(|idx| aval.set_available(*idx, true));
|
||||
|
||||
assert!(aval.available());
|
||||
|
||||
while let Some(idx) = idx.pop() {
|
||||
assert!(aval.available());
|
||||
aval.set_available(idx, false);
|
||||
}
|
||||
|
||||
assert!(!aval.available());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn availability() {
|
||||
let mut aval = Availability::default();
|
||||
|
||||
single(&mut aval, 1);
|
||||
single(&mut aval, 128);
|
||||
single(&mut aval, 256);
|
||||
single(&mut aval, 511);
|
||||
|
||||
let idx = (0..511).filter(|i| i % 3 == 0 && i % 5 == 0).collect();
|
||||
|
||||
multi(&mut aval, idx);
|
||||
|
||||
multi(&mut aval, (0..511).collect())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn overflow() {
|
||||
let mut aval = Availability::default();
|
||||
single(&mut aval, 512);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pin_point() {
|
||||
let mut aval = Availability::default();
|
||||
|
||||
aval.set_available(438, true);
|
||||
|
||||
aval.set_available(479, true);
|
||||
|
||||
assert_eq!(aval.0[3], 1 << (438 - 384) | 1 << (479 - 384));
|
||||
}
|
||||
}
|
||||
|
@@ -1,43 +1,40 @@
|
||||
use std::{
|
||||
future::Future,
|
||||
io, mem,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
time::Duration,
|
||||
};
|
||||
use std::time::Duration;
|
||||
use std::{io, mem, net};
|
||||
|
||||
use actix_rt::{self as rt, net::TcpStream, time::sleep, System};
|
||||
use actix_rt::{spawn, Arbiter, System};
|
||||
use futures::future::{lazy, ok};
|
||||
use futures::stream::futures_unordered;
|
||||
use futures::sync::mpsc::{unbounded, UnboundedReceiver};
|
||||
use futures::{Async, Future, Poll, Stream};
|
||||
use log::{error, info};
|
||||
use tokio::sync::{
|
||||
mpsc::{unbounded_channel, UnboundedReceiver},
|
||||
oneshot,
|
||||
};
|
||||
use net2::TcpBuilder;
|
||||
use num_cpus;
|
||||
use tokio_tcp::TcpStream;
|
||||
use tokio_timer::sleep;
|
||||
|
||||
use crate::accept::AcceptLoop;
|
||||
use crate::join_all;
|
||||
use crate::accept::{AcceptLoop, AcceptNotify, Command};
|
||||
use crate::config::{ConfiguredService, ServiceConfig};
|
||||
use crate::server::{Server, ServerCommand};
|
||||
use crate::service::{InternalServiceFactory, ServiceFactory, StreamNewService};
|
||||
use crate::services::{InternalServiceFactory, ServiceFactory, StreamNewService};
|
||||
use crate::signals::{Signal, Signals};
|
||||
use crate::socket::{MioListener, StdSocketAddr, StdTcpListener, ToSocketAddrs};
|
||||
use crate::socket::{MioTcpListener, MioTcpSocket};
|
||||
use crate::waker_queue::{WakerInterest, WakerQueue};
|
||||
use crate::worker::{ServerWorker, ServerWorkerConfig, WorkerHandleAccept, WorkerHandleServer};
|
||||
use crate::socket::StdListener;
|
||||
use crate::worker::{self, Worker, WorkerAvailability, WorkerClient};
|
||||
use crate::{ssl, Token};
|
||||
|
||||
/// Server builder
|
||||
pub struct ServerBuilder {
|
||||
threads: usize,
|
||||
token: usize,
|
||||
backlog: u32,
|
||||
handles: Vec<(usize, WorkerHandleServer)>,
|
||||
token: Token,
|
||||
backlog: i32,
|
||||
workers: Vec<(usize, WorkerClient)>,
|
||||
services: Vec<Box<dyn InternalServiceFactory>>,
|
||||
sockets: Vec<(usize, String, MioListener)>,
|
||||
sockets: Vec<(Token, StdListener)>,
|
||||
accept: AcceptLoop,
|
||||
exit: bool,
|
||||
shutdown_timeout: Duration,
|
||||
no_signals: bool,
|
||||
cmd: UnboundedReceiver<ServerCommand>,
|
||||
server: Server,
|
||||
notify: Vec<oneshot::Sender<()>>,
|
||||
worker_config: ServerWorkerConfig,
|
||||
}
|
||||
|
||||
impl Default for ServerBuilder {
|
||||
@@ -49,54 +46,34 @@ impl Default for ServerBuilder {
|
||||
impl ServerBuilder {
|
||||
/// Create new Server builder instance
|
||||
pub fn new() -> ServerBuilder {
|
||||
let (tx, rx) = unbounded_channel();
|
||||
let (tx, rx) = unbounded();
|
||||
let server = Server::new(tx);
|
||||
|
||||
ServerBuilder {
|
||||
threads: num_cpus::get(),
|
||||
token: 0,
|
||||
handles: Vec::new(),
|
||||
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,
|
||||
worker_config: ServerWorkerConfig::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set number of workers to start.
|
||||
///
|
||||
/// By default server uses number of available logical cpu as workers
|
||||
/// count. Workers must be greater than 0.
|
||||
/// count.
|
||||
pub fn workers(mut self, num: usize) -> Self {
|
||||
assert_ne!(num, 0, "workers must be greater than 0");
|
||||
self.threads = num;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set max number of threads for each worker's blocking task thread pool.
|
||||
///
|
||||
/// One thread pool is set up **per worker**; not shared across workers.
|
||||
///
|
||||
/// # Examples:
|
||||
/// ```
|
||||
/// # use actix_server::ServerBuilder;
|
||||
/// let builder = ServerBuilder::new()
|
||||
/// .workers(4) // server has 4 worker thread.
|
||||
/// .worker_max_blocking_threads(4); // every worker has 4 max blocking threads.
|
||||
/// ```
|
||||
///
|
||||
/// See [tokio::runtime::Builder::max_blocking_threads] for behavior reference.
|
||||
pub fn worker_max_blocking_threads(mut self, num: usize) -> Self {
|
||||
self.worker_config.max_blocking_threads(num);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the maximum number of pending connections.
|
||||
///
|
||||
/// This refers to the number of clients that can be waiting to be served.
|
||||
@@ -107,7 +84,7 @@ impl ServerBuilder {
|
||||
/// 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: u32) -> Self {
|
||||
pub fn backlog(mut self, num: i32) -> Self {
|
||||
self.backlog = num;
|
||||
self
|
||||
}
|
||||
@@ -118,18 +95,29 @@ impl ServerBuilder {
|
||||
/// reached for each worker.
|
||||
///
|
||||
/// By default max connections is set to a 25k per worker.
|
||||
pub fn maxconn(mut self, num: usize) -> Self {
|
||||
self.worker_config.max_concurrent_connections(num);
|
||||
pub fn maxconn(self, num: usize) -> Self {
|
||||
worker::max_concurrent_connections(num);
|
||||
self
|
||||
}
|
||||
|
||||
/// Stop Actix system.
|
||||
/// Sets the maximum per-worker concurrent connection establish process.
|
||||
///
|
||||
/// All listeners will stop accepting connections when this limit is reached. It
|
||||
/// can be used to limit the global SSL CPU usage.
|
||||
///
|
||||
/// By default max connections is set to a 256.
|
||||
pub fn maxconnrate(self, num: usize) -> Self {
|
||||
ssl::max_concurrent_ssl_connect(num);
|
||||
self
|
||||
}
|
||||
|
||||
/// Stop actix system.
|
||||
pub fn system_exit(mut self) -> Self {
|
||||
self.exit = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Disable signal handling.
|
||||
/// Disable signal handling
|
||||
pub fn disable_signals(mut self) -> Self {
|
||||
self.no_signals = true;
|
||||
self
|
||||
@@ -137,84 +125,89 @@ impl ServerBuilder {
|
||||
|
||||
/// 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.
|
||||
/// 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.worker_config
|
||||
.shutdown_timeout(Duration::from_secs(sec));
|
||||
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, lst.local_addr()?);
|
||||
self.sockets.push((token, 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: ToSocketAddrs,
|
||||
U: net::ToSocketAddrs,
|
||||
{
|
||||
let sockets = bind_addr(addr, self.backlog)?;
|
||||
|
||||
for lst in sockets {
|
||||
let token = self.next_token();
|
||||
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(), MioListener::Tcp(lst)));
|
||||
self.sockets.push((token, StdListener::Tcp(lst)));
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "uds"))]
|
||||
/// Add new unix domain service to the server.
|
||||
#[cfg(unix)]
|
||||
pub fn bind_uds<F, U, N>(self, name: N, addr: U, factory: F) -> io::Result<Self>
|
||||
pub fn bind_uds<F, U, N>(mut self, name: N, addr: U, factory: F) -> io::Result<Self>
|
||||
where
|
||||
F: ServiceFactory<actix_rt::net::UnixStream>,
|
||||
F: ServiceFactory<tokio_uds::UnixStream>,
|
||||
N: AsRef<str>,
|
||||
U: AsRef<std::path::Path>,
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::os::unix::net::UnixListener;
|
||||
|
||||
let lst = crate::socket::StdUnixListener::bind(addr)?;
|
||||
self.listen_uds(name, lst, factory)
|
||||
}
|
||||
// TODO: need to do something with existing paths
|
||||
let _ = std::fs::remove_file(addr.as_ref());
|
||||
|
||||
/// 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.
|
||||
#[cfg(unix)]
|
||||
pub fn listen_uds<F, N: AsRef<str>>(
|
||||
mut self,
|
||||
name: N,
|
||||
lst: crate::socket::StdUnixListener,
|
||||
factory: F,
|
||||
) -> io::Result<Self>
|
||||
where
|
||||
F: ServiceFactory<actix_rt::net::UnixStream>,
|
||||
{
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
lst.set_nonblocking(true)?;
|
||||
let token = self.next_token();
|
||||
let addr = StdSocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
|
||||
let lst = UnixListener::bind(addr)?;
|
||||
|
||||
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,
|
||||
factory.clone(),
|
||||
addr,
|
||||
));
|
||||
self.sockets
|
||||
.push((token, name.as_ref().to_string(), MioListener::from(lst)));
|
||||
self.sockets.push((token, StdListener::Uds(lst)));
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
@@ -222,58 +215,68 @@ impl ServerBuilder {
|
||||
pub fn listen<F, N: AsRef<str>>(
|
||||
mut self,
|
||||
name: N,
|
||||
lst: StdTcpListener,
|
||||
lst: net::TcpListener,
|
||||
factory: F,
|
||||
) -> io::Result<Self>
|
||||
where
|
||||
F: ServiceFactory<TcpStream>,
|
||||
{
|
||||
lst.set_nonblocking(true)?;
|
||||
let addr = lst.local_addr()?;
|
||||
|
||||
let token = self.next_token();
|
||||
let token = self.token.next();
|
||||
self.services.push(StreamNewService::create(
|
||||
name.as_ref().to_string(),
|
||||
token,
|
||||
factory,
|
||||
addr,
|
||||
lst.local_addr()?,
|
||||
));
|
||||
|
||||
self.sockets
|
||||
.push((token, name.as_ref().to_string(), MioListener::from(lst)));
|
||||
|
||||
self.sockets.push((token, StdListener::Tcp(lst)));
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Spawn new thread and start listening for incoming connections.
|
||||
///
|
||||
/// This method spawns new thread and starts new actix system. Other than
|
||||
/// that it is similar to `start()` method. This method blocks.
|
||||
///
|
||||
/// This methods panics if no socket addresses get bound.
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// use actix_web::*;
|
||||
///
|
||||
/// fn main() -> std::io::Result<()> {
|
||||
/// Server::new().
|
||||
/// .service(
|
||||
/// HttpServer::new(|| App::new().service(web::service("/").to(|| HttpResponse::Ok())))
|
||||
/// .bind("127.0.0.1:0")
|
||||
/// .run()
|
||||
/// }
|
||||
/// ```
|
||||
pub fn run(self) -> io::Result<()> {
|
||||
let sys = System::new("http-server");
|
||||
self.start();
|
||||
sys.run()
|
||||
}
|
||||
|
||||
/// Starts processing incoming connections and return server controller.
|
||||
pub fn run(mut self) -> Server {
|
||||
pub fn start(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 handles = (0..self.threads)
|
||||
.map(|idx| {
|
||||
let (handle_accept, handle_server) =
|
||||
self.start_worker(idx, self.accept.waker_owned());
|
||||
self.handles.push((idx, handle_server));
|
||||
|
||||
handle_accept
|
||||
})
|
||||
.collect();
|
||||
let mut workers = Vec::new();
|
||||
for idx in 0..self.threads {
|
||||
let worker = self.start_worker(idx, self.accept.get_notify());
|
||||
workers.push(worker.clone());
|
||||
self.workers.push((idx, worker));
|
||||
}
|
||||
|
||||
// start accept thread
|
||||
for sock in &self.sockets {
|
||||
info!("Starting \"{}\" service on {}", sock.1, sock.2);
|
||||
info!("Starting server on {}", sock.1);
|
||||
}
|
||||
self.accept.start(
|
||||
mem::take(&mut self.sockets)
|
||||
.into_iter()
|
||||
.map(|t| (t.0, t.2))
|
||||
.collect(),
|
||||
handles,
|
||||
);
|
||||
self.accept
|
||||
.start(mem::replace(&mut self.sockets, Vec::new()), workers);
|
||||
|
||||
// handle signals
|
||||
if !self.no_signals {
|
||||
@@ -282,29 +285,36 @@ impl ServerBuilder {
|
||||
|
||||
// start http server actor
|
||||
let server = self.server.clone();
|
||||
rt::spawn(self);
|
||||
spawn(self);
|
||||
server
|
||||
}
|
||||
}
|
||||
|
||||
fn start_worker(
|
||||
&self,
|
||||
idx: usize,
|
||||
waker_queue: WakerQueue,
|
||||
) -> (WorkerHandleAccept, WorkerHandleServer) {
|
||||
let services = self.services.iter().map(|v| v.clone_factory()).collect();
|
||||
fn start_worker(&self, idx: usize, notify: AcceptNotify) -> WorkerClient {
|
||||
let (tx1, rx1) = unbounded();
|
||||
let (tx2, rx2) = unbounded();
|
||||
let timeout = self.shutdown_timeout;
|
||||
let avail = WorkerAvailability::new(notify);
|
||||
let worker = WorkerClient::new(idx, tx1, tx2, avail.clone());
|
||||
let services: Vec<Box<dyn InternalServiceFactory>> =
|
||||
self.services.iter().map(|v| v.clone_factory()).collect();
|
||||
|
||||
ServerWorker::start(idx, services, waker_queue, self.worker_config)
|
||||
Arbiter::new().send(lazy(move || {
|
||||
Worker::start(rx1, rx2, services, avail, timeout);
|
||||
Ok::<_, ()>(())
|
||||
}));
|
||||
|
||||
worker
|
||||
}
|
||||
|
||||
fn handle_cmd(&mut self, item: ServerCommand) {
|
||||
match item {
|
||||
ServerCommand::Pause(tx) => {
|
||||
self.accept.wake(WakerInterest::Pause);
|
||||
self.accept.send(Command::Pause);
|
||||
let _ = tx.send(());
|
||||
}
|
||||
ServerCommand::Resume(tx) => {
|
||||
self.accept.wake(WakerInterest::Resume);
|
||||
self.accept.send(Command::Resume);
|
||||
let _ = tx.send(());
|
||||
}
|
||||
ServerCommand::Signal(sig) => {
|
||||
@@ -312,36 +322,32 @@ impl ServerBuilder {
|
||||
// Handle `SIGINT`, `SIGTERM`, `SIGQUIT` signals and stop actix system
|
||||
match sig {
|
||||
Signal::Int => {
|
||||
info!("SIGINT received, starting forced shutdown");
|
||||
info!("SIGINT received, exiting");
|
||||
self.exit = true;
|
||||
self.handle_cmd(ServerCommand::Stop {
|
||||
graceful: false,
|
||||
completion: None,
|
||||
})
|
||||
}
|
||||
|
||||
Signal::Term => {
|
||||
info!("SIGTERM received, starting graceful shutdown");
|
||||
info!("SIGTERM received, stopping");
|
||||
self.exit = true;
|
||||
self.handle_cmd(ServerCommand::Stop {
|
||||
graceful: true,
|
||||
completion: None,
|
||||
})
|
||||
}
|
||||
|
||||
Signal::Quit => {
|
||||
info!("SIGQUIT received, starting forced shutdown");
|
||||
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,
|
||||
@@ -349,41 +355,48 @@ impl ServerBuilder {
|
||||
let exit = self.exit;
|
||||
|
||||
// stop accept thread
|
||||
self.accept.wake(WakerInterest::Stop);
|
||||
let notify = std::mem::take(&mut self.notify);
|
||||
self.accept.send(Command::Stop);
|
||||
|
||||
// stop workers
|
||||
let stop = self
|
||||
.handles
|
||||
.iter()
|
||||
.map(move |worker| worker.1.stop(graceful))
|
||||
.collect();
|
||||
|
||||
rt::spawn(async move {
|
||||
if graceful {
|
||||
// wait for all workers to shut down
|
||||
let _ = join_all(stop).await;
|
||||
if !self.workers.is_empty() && graceful {
|
||||
spawn(
|
||||
futures_unordered(
|
||||
self.workers
|
||||
.iter()
|
||||
.map(move |worker| worker.1.stop(graceful)),
|
||||
)
|
||||
.collect()
|
||||
.then(move |_| {
|
||||
if let Some(tx) = completion {
|
||||
let _ = tx.send(());
|
||||
}
|
||||
if exit {
|
||||
spawn(sleep(Duration::from_millis(300)).then(|_| {
|
||||
System::current().stop();
|
||||
ok(())
|
||||
}));
|
||||
}
|
||||
ok(())
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
// we need to stop system if server was spawned
|
||||
if self.exit {
|
||||
spawn(sleep(Duration::from_millis(300)).then(|_| {
|
||||
System::current().stop();
|
||||
ok(())
|
||||
}));
|
||||
}
|
||||
|
||||
if let Some(tx) = completion {
|
||||
let _ = tx.send(());
|
||||
}
|
||||
|
||||
for tx in notify {
|
||||
let _ = tx.send(());
|
||||
}
|
||||
|
||||
if exit {
|
||||
sleep(Duration::from_millis(300)).await;
|
||||
System::current().stop();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
ServerCommand::WorkerFaulted(idx) => {
|
||||
ServerCommand::WorkerDied(idx) => {
|
||||
let mut found = false;
|
||||
for i in 0..self.handles.len() {
|
||||
if self.handles[i].0 == idx {
|
||||
self.handles.swap_remove(i);
|
||||
for i in 0..self.workers.len() {
|
||||
if self.workers[i].0 == idx {
|
||||
self.workers.swap_remove(i);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
@@ -392,10 +405,10 @@ impl ServerBuilder {
|
||||
if found {
|
||||
error!("Worker has died {:?}, restarting", idx);
|
||||
|
||||
let mut new_idx = self.handles.len();
|
||||
let mut new_idx = self.workers.len();
|
||||
'found: loop {
|
||||
for i in 0..self.handles.len() {
|
||||
if self.handles[i].0 == new_idx {
|
||||
for i in 0..self.workers.len() {
|
||||
if self.workers[i].0 == new_idx {
|
||||
new_idx += 1;
|
||||
continue 'found;
|
||||
}
|
||||
@@ -403,39 +416,34 @@ impl ServerBuilder {
|
||||
break;
|
||||
}
|
||||
|
||||
let (handle_accept, handle_server) =
|
||||
self.start_worker(new_idx, self.accept.waker_owned());
|
||||
self.handles.push((new_idx, handle_server));
|
||||
self.accept.wake(WakerInterest::Worker(handle_accept));
|
||||
let worker = self.start_worker(new_idx, self.accept.get_notify());
|
||||
self.workers.push((new_idx, worker.clone()));
|
||||
self.accept.send(Command::Worker(worker));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn next_token(&mut self) -> usize {
|
||||
let token = self.token;
|
||||
self.token += 1;
|
||||
token
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for ServerBuilder {
|
||||
type Output = ();
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
loop {
|
||||
match Pin::new(&mut self.cmd).poll_recv(cx) {
|
||||
Poll::Ready(Some(it)) => self.as_mut().get_mut().handle_cmd(it),
|
||||
_ => return Poll::Pending,
|
||||
match self.cmd.poll() {
|
||||
Ok(Async::Ready(None)) | Err(_) => return Ok(Async::Ready(())),
|
||||
Ok(Async::NotReady) => return Ok(Async::NotReady),
|
||||
Ok(Async::Ready(Some(item))) => self.handle_cmd(item),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn bind_addr<S: ToSocketAddrs>(
|
||||
pub(super) fn bind_addr<S: net::ToSocketAddrs>(
|
||||
addr: S,
|
||||
backlog: u32,
|
||||
) -> io::Result<Vec<MioTcpListener>> {
|
||||
backlog: i32,
|
||||
) -> io::Result<Vec<net::TcpListener>> {
|
||||
let mut err = None;
|
||||
let mut succ = false;
|
||||
let mut sockets = Vec::new();
|
||||
@@ -463,13 +471,12 @@ pub(super) fn bind_addr<S: ToSocketAddrs>(
|
||||
}
|
||||
}
|
||||
|
||||
fn create_tcp_listener(addr: StdSocketAddr, backlog: u32) -> io::Result<MioTcpListener> {
|
||||
let socket = match addr {
|
||||
StdSocketAddr::V4(_) => MioTcpSocket::new_v4()?,
|
||||
StdSocketAddr::V6(_) => MioTcpSocket::new_v6()?,
|
||||
fn create_tcp_listener(addr: net::SocketAddr, backlog: i32) -> io::Result<net::TcpListener> {
|
||||
let builder = match addr {
|
||||
net::SocketAddr::V4(_) => TcpBuilder::new_v4()?,
|
||||
net::SocketAddr::V6(_) => TcpBuilder::new_v6()?,
|
||||
};
|
||||
|
||||
socket.set_reuseaddr(true)?;
|
||||
socket.bind(addr)?;
|
||||
socket.listen(backlog)
|
||||
builder.reuse_address(true)?;
|
||||
builder.bind(addr)?;
|
||||
Ok(builder.listen(backlog)?)
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user