mirror of
https://github.com/fafhrd91/actix-net
synced 2025-08-12 15:37:05 +02:00
Compare commits
1 Commits
macros-v0.
...
circleci-p
Author | SHA1 | Date | |
---|---|---|---|
|
6e2db3dc25 |
@@ -14,10 +14,13 @@ ci-check = "hack --workspace --feature-powerset --exclude-features=io-uring chec
|
||||
ci-check-linux = "hack --workspace --feature-powerset check --tests --examples"
|
||||
|
||||
# tests avoiding io-uring feature
|
||||
ci-test = "hack --feature-powerset --exclude-features=io-uring test --lib --tests --no-fail-fast -- --nocapture"
|
||||
|
||||
# tests avoiding io-uring feature on Windows
|
||||
ci-test-win = "hack --feature-powerset --depth 2 --exclude-features=io-uring test --lib --tests --no-fail-fast -- --nocapture"
|
||||
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-linux = "hack --feature-powerset test --lib --tests --no-fail-fast -- --nocapture"
|
||||
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"
|
||||
|
22
.circleci/config.yml
Normal file
22
.circleci/config.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
version: 2.1
|
||||
|
||||
jobs:
|
||||
test-io-uring-features:
|
||||
machine:
|
||||
image: ubuntu-2004:202107-02
|
||||
resource_class: large
|
||||
environment:
|
||||
CI: '1'
|
||||
CARGO_INCREMENTAL: '0'
|
||||
RUST_BACKTRACE: '1'
|
||||
steps:
|
||||
- checkout
|
||||
- run: uname -a
|
||||
- run: apt-get update; apt-get install -y liburing-dev
|
||||
- run: cargo test -p=actix-rt --features=io-uring
|
||||
- run: cargo test -p=actix-server --features=io-uring
|
||||
|
||||
workflows:
|
||||
test:
|
||||
jobs:
|
||||
- test-io-uring-features
|
143
.github/workflows/ci-post-merge.yml
vendored
143
.github/workflows/ci-post-merge.yml
vendored
@@ -1,143 +0,0 @@
|
||||
name: CI (master only)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build_and_test_nightly:
|
||||
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:
|
||||
- nightly
|
||||
|
||||
name: ${{ matrix.target.name }} / ${{ matrix.version }}
|
||||
runs-on: ${{ matrix.target.os }}
|
||||
|
||||
env: {}
|
||||
|
||||
steps:
|
||||
- name: Setup Routing
|
||||
if: matrix.target.os == 'macos-latest'
|
||||
run: sudo ifconfig lo0 alias 127.0.0.3
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install OpenSSL
|
||||
if: matrix.target.os == 'windows-latest'
|
||||
run: choco install openssl -y --forcex64 --no-progress
|
||||
- name: Set OpenSSL dir in env
|
||||
if: matrix.target.os == 'windows-latest'
|
||||
run: |
|
||||
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL-Win64' | Out-File -FilePath $env:GITHUB_ENV -Append
|
||||
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' | Out-File -FilePath $env:GITHUB_ENV -Append
|
||||
|
||||
- name: Install Rust (${{ matrix.version }})
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.version }}
|
||||
|
||||
- uses: taiki-e/install-action@v2
|
||||
with: { tool: cargo-hack }
|
||||
|
||||
- name: check lib
|
||||
if: >
|
||||
matrix.target.os != 'ubuntu-latest'
|
||||
&& matrix.target.triple != 'x86_64-pc-windows-gnu'
|
||||
run: cargo ci-check-lib
|
||||
- name: check lib
|
||||
if: matrix.target.os == 'ubuntu-latest'
|
||||
run: cargo ci-check-lib-linux
|
||||
- name: check lib
|
||||
if: matrix.target.triple == 'x86_64-pc-windows-gnu'
|
||||
run: cargo 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'
|
||||
run: cargo ci-check
|
||||
- name: check all
|
||||
if: matrix.target.os == 'ubuntu-latest'
|
||||
run: cargo ci-check-linux
|
||||
|
||||
- name: tests
|
||||
if: >
|
||||
matrix.target.os != 'ubuntu-latest'
|
||||
&& matrix.target.triple != 'x86_64-pc-windows-gnu'
|
||||
run: cargo ci-test
|
||||
- name: tests
|
||||
if: matrix.target.os == 'ubuntu-latest'
|
||||
run: |
|
||||
sudo bash -c "ulimit -Sl 512 && ulimit -Hl 512 && PATH=$PATH:/usr/share/rust/.cargo/bin && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo ci-test && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo ci-test-linux"
|
||||
|
||||
- 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@v3
|
||||
|
||||
- name: Install Rust (nightly)
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with: { toolchain: nightly }
|
||||
|
||||
- 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@v3
|
||||
with: { files: cobertura.xml }
|
||||
|
||||
minimal-versions:
|
||||
name: minimal versions
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust (nightly)
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with: { toolchain: nightly }
|
||||
|
||||
- uses: taiki-e/install-action@v1
|
||||
with: { tool: cargo-hack, cargo-minimal-versions }
|
||||
|
||||
- name: Check With Minimal Versions
|
||||
run: cargo minimal-versions check
|
||||
|
||||
nextest:
|
||||
name: nextest
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
|
||||
- name: Install cargo-nextest
|
||||
run: cargo install cargo-nextest
|
||||
|
||||
- name: Test with cargo-nextest
|
||||
run: cargo nextest run
|
174
.github/workflows/ci.yml
vendored
174
.github/workflows/ci.yml
vendored
@@ -1,15 +1,10 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request: {}
|
||||
push: { branches: [master] }
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
push:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
@@ -23,96 +18,183 @@ jobs:
|
||||
- { 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.65.0 # MSRV
|
||||
- 1.52.0 # MSRV for -server and -tls
|
||||
- stable
|
||||
- nightly
|
||||
|
||||
name: ${{ matrix.target.name }} / ${{ matrix.version }}
|
||||
runs-on: ${{ matrix.target.os }}
|
||||
|
||||
env: {}
|
||||
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@v3
|
||||
- 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.os == 'windows-latest'
|
||||
run: choco install openssl -y --forcex64 --no-progress
|
||||
- name: Set OpenSSL dir in env
|
||||
if: matrix.target.os == 'windows-latest'
|
||||
run: |
|
||||
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL-Win64' | Out-File -FilePath $env:GITHUB_ENV -Append
|
||||
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' | Out-File -FilePath $env:GITHUB_ENV -Append
|
||||
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 Rust (${{ matrix.version }})
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
- name: Install ${{ matrix.version }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.version }}
|
||||
toolchain: ${{ matrix.version }}-${{ matrix.target.triple }}
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- uses: taiki-e/install-action@v1
|
||||
with: { tool: cargo-hack }
|
||||
# - 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
|
||||
run: cargo generate-lockfile
|
||||
# - name: Generate Cargo.lock
|
||||
# uses: actions-rs/cargo@v1
|
||||
# with: { command: generate-lockfile }
|
||||
# - name: Cache Dependencies
|
||||
# uses: Swatinem/rust-cache@v1.2.0
|
||||
|
||||
- name: workaround MSRV issues
|
||||
if: matrix.version != 'stable'
|
||||
run: |
|
||||
cargo update -p=time --precise=0.3.16 # time is only a dev dep so shouldn't affect msrv
|
||||
- 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'
|
||||
run: cargo ci-check-lib
|
||||
uses: actions-rs/cargo@v1
|
||||
with: { command: ci-check-lib }
|
||||
- name: check lib
|
||||
if: matrix.target.os == 'ubuntu-latest'
|
||||
run: cargo ci-check-lib-linux
|
||||
uses: actions-rs/cargo@v1
|
||||
with: { command: ci-check-lib-linux }
|
||||
- name: check lib
|
||||
if: matrix.target.triple == 'x86_64-pc-windows-gnu'
|
||||
run: cargo ci-check-min
|
||||
|
||||
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'
|
||||
run: cargo ci-check
|
||||
uses: actions-rs/cargo@v1
|
||||
with: { command: ci-check }
|
||||
- name: check all
|
||||
if: matrix.target.os == 'ubuntu-latest'
|
||||
run: cargo ci-check-linux
|
||||
uses: actions-rs/cargo@v1
|
||||
with: { command: ci-check-linux }
|
||||
|
||||
- name: tests
|
||||
if: matrix.target.os == 'macos-latest'
|
||||
run: cargo ci-test
|
||||
- name: tests
|
||||
if: >
|
||||
matrix.target.os == 'windows-latest'
|
||||
matrix.target.os != 'ubuntu-latest'
|
||||
&& matrix.target.triple != 'x86_64-pc-windows-gnu'
|
||||
run: cargo ci-test-win
|
||||
run: |
|
||||
cargo ci-test
|
||||
cargo ci-test-rt
|
||||
cargo ci-test-server
|
||||
- name: tests
|
||||
if: matrix.target.os == 'ubuntu-latest'
|
||||
run: |
|
||||
sudo bash -c "ulimit -Sl 512 && ulimit -Hl 512 && PATH=$PATH:/usr/share/rust/.cargo/bin && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo ci-test && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo ci-test-linux"
|
||||
sudo bash -c "ulimit -Sl 512 && ulimit -Hl 512 && PATH=$PATH:/usr/share/rust/.cargo/bin && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo ci-test && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo ci-test-rt-linux && RUSTUP_TOOLCHAIN=${{ matrix.version }} 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: |
|
||||
sudo bash -c "ulimit -Sl 512 && ulimit -Hl 512 && PATH=$PATH:/usr/share/rust/.cargo/bin && RUSTUP_TOOLCHAIN=1.46 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@v3
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install Rust (nightly)
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with: { toolchain: 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: doc tests io-uring
|
||||
run: |
|
||||
|
51
.github/workflows/clippy-fmt.yml
vendored
51
.github/workflows/clippy-fmt.yml
vendored
@@ -1,43 +1,42 @@
|
||||
name: Lint
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
fmt:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
components: rustfmt
|
||||
|
||||
override: true
|
||||
- name: Rustfmt Check
|
||||
run: cargo fmt --all -- --check
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
clippy:
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with: { components: clippy }
|
||||
|
||||
- uses: giraffate/clippy-action@v1
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
reporter: 'github-pr-check'
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
clippy_flags: --workspace --all-features --tests --examples --bins -- -Dclippy::todo
|
||||
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
|
||||
|
34
.github/workflows/upload-doc.yml
vendored
34
.github/workflows/upload-doc.yml
vendored
@@ -1,35 +1,35 @@
|
||||
name: Upload documentation
|
||||
|
||||
on:
|
||||
push: { branches: [master] }
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
push:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with: { toolchain: nightly }
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly-x86_64-unknown-linux-gnu
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Build Docs
|
||||
run: cargo doc --workspace --all-features --no-deps
|
||||
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@v4
|
||||
uses: JamesIves/github-pages-deploy-action@3.7.1
|
||||
with:
|
||||
folder: target/doc
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BRANCH: gh-pages
|
||||
FOLDER: target/doc
|
||||
|
@@ -1,3 +0,0 @@
|
||||
group_imports = "StdExternalCrate"
|
||||
imports_granularity = "Crate"
|
||||
use_field_init_shorthand = true
|
@@ -8,19 +8,19 @@ In the interest of fostering an open and welcoming environment, we as contributo
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
- Using welcoming and inclusive language
|
||||
- Being respectful of differing viewpoints and experiences
|
||||
- Gracefully accepting constructive criticism
|
||||
- Focusing on what is best for the community
|
||||
- Showing empathy towards other community members
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
- The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
@@ -39,7 +39,7 @@ Instances of abusive, harassing, or otherwise unacceptable behavior may be repor
|
||||
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
|
||||
[@JohnTitor]: https://github.com/JohnTitor
|
||||
|
||||
## Attribution
|
||||
|
||||
|
@@ -12,11 +12,6 @@ members = [
|
||||
"local-channel",
|
||||
"local-waker",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
edition = "2021"
|
||||
rust-version = "1.65"
|
||||
|
||||
[patch.crates-io]
|
||||
actix-codec = { path = "actix-codec" }
|
||||
|
26
README.md
26
README.md
@@ -2,25 +2,29 @@
|
||||
|
||||
> A collection of lower-level libraries for composable network services.
|
||||
|
||||
[](https://github.com/actix/actix-net/actions/workflows/ci.yml)
|
||||

|
||||
[](https://codecov.io/gh/actix/actix-net)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
[](https://deps.rs/repo/github/actix/actix-net)
|
||||
|
||||
## Examples
|
||||
## 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)") |
|
||||
|
||||
See example folders for [`actix-server`](./actix-server/examples) and [`actix-tls`](./actix-tls/examples).
|
||||
## Example
|
||||
See `actix-server/examples` and `actix-tls/examples` for some basic examples.
|
||||
|
||||
## MSRV
|
||||
|
||||
Crates in this repo currently have a Minimum Supported Rust Version (MSRV) of 1.65. As a policy, we permit MSRV increases in non-breaking releases.
|
||||
### MSRV
|
||||
This repo's Minimum Supported Rust Version (MSRV) is 1.46.0.
|
||||
|
||||
## License
|
||||
This project is licensed under either of
|
||||
|
||||
The crates in repo are 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))
|
||||
- MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT))
|
||||
* 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))
|
||||
* MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT))
|
||||
|
||||
at your option.
|
||||
|
||||
|
@@ -1,90 +1,71 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2023-xx-xx
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.65.
|
||||
|
||||
## 0.5.1 - 2022-03-15
|
||||
|
||||
- Logs emitted now use the `tracing` crate with `log` compatibility. [#451]
|
||||
- Minimum supported Rust version (MSRV) is now 1.49.
|
||||
|
||||
[#451]: https://github.com/actix/actix-net/pull/451
|
||||
|
||||
## 0.5.0 - 2022-02-15
|
||||
|
||||
- Updated `tokio-util` dependency to `0.7.0`. [#446]
|
||||
|
||||
[#446]: https://github.com/actix/actix-net/pull/446
|
||||
|
||||
## 0.4.2 - 2021-12-31
|
||||
|
||||
- No significant changes since `0.4.1`.
|
||||
|
||||
## 0.4.1 - 2021-11-05
|
||||
|
||||
- Added `LinesCodec.` [#338]
|
||||
- `Framed::poll_ready` flushes when the buffer is full. [#409]
|
||||
* Added `LinesCodec.` [#338]
|
||||
* `Framed::poll_ready` flushes when the buffer is full. [#409]
|
||||
|
||||
[#338]: https://github.com/actix/actix-net/pull/338
|
||||
[#409]: https://github.com/actix/actix-net/pull/409
|
||||
|
||||
## 0.4.0 - 2021-04-20
|
||||
|
||||
- No significant changes since v0.4.0-beta.1.
|
||||
## 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]
|
||||
* 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 - 2020-08-23
|
||||
* No changes from beta 2.
|
||||
|
||||
|
||||
## 0.3.0-beta.2 - 2020-08-19
|
||||
* Remove unused type parameter from `Framed::replace_codec`.
|
||||
|
||||
- Remove unused type parameter from `Framed::replace_codec`.
|
||||
|
||||
## 0.3.0-beta.1 - 2020-08-19
|
||||
* 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.
|
||||
|
||||
- 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.
|
||||
|
||||
- Use specific futures dependencies.
|
||||
|
||||
## 0.2.0-alpha.4
|
||||
* Fix buffer remaining capacity calculation.
|
||||
|
||||
- Fix buffer remaining capacity calculation.
|
||||
|
||||
## 0.2.0-alpha.3
|
||||
* Use tokio 0.2.
|
||||
* Fix low/high watermark for write/read buffers.
|
||||
|
||||
- Use tokio 0.2.
|
||||
- Fix low/high watermark for write/read buffers.
|
||||
|
||||
## 0.2.0-alpha.2
|
||||
* Migrated to `std::future`.
|
||||
|
||||
- Migrated to `std::future`.
|
||||
|
||||
## 0.1.2 - 2019-03-27
|
||||
* Added `Framed::map_io()` method.
|
||||
|
||||
- Added `Framed::map_io()` method.
|
||||
|
||||
## 0.1.1 - 2019-03-06
|
||||
* Added `FramedParts::with_read_buffer()` method.
|
||||
|
||||
- Added `FramedParts::with_read_buffer()` method.
|
||||
|
||||
## 0.1.0 - 2018-12-09
|
||||
|
||||
- Move codec to separate crate.
|
||||
* Move codec to separate crate.
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-codec"
|
||||
version = "0.5.1"
|
||||
version = "0.4.1"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
@@ -10,22 +10,25 @@ keywords = ["network", "framework", "async", "futures"]
|
||||
repository = "https://github.com/actix/actix-net"
|
||||
categories = ["network-programming", "asynchronous"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
name = "actix_codec"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2"
|
||||
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"
|
||||
memchr = "2.3"
|
||||
pin-project-lite = "0.2"
|
||||
tokio = "1.23.1"
|
||||
tokio-util = { version = "0.7", features = ["codec", "io"] }
|
||||
tracing = { version = "0.1.30", default-features = false, features = ["log"] }
|
||||
tokio = "1.5.1"
|
||||
tokio-util = { version = "0.6", features = ["codec", "io"] }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.4", features = ["html_reports"] }
|
||||
criterion = { version = "0.3", features = ["html_reports"] }
|
||||
tokio-test = "0.4.2"
|
||||
|
||||
[[bench]]
|
||||
|
@@ -1,14 +1,10 @@
|
||||
use std::{
|
||||
fmt, io,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use std::{fmt, io};
|
||||
|
||||
use bitflags::bitflags;
|
||||
use bytes::{Buf, BytesMut};
|
||||
use futures_core::{ready, Stream};
|
||||
use futures_sink::Sink;
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use crate::{AsyncRead, AsyncWrite, Decoder, Encoder};
|
||||
|
||||
@@ -17,15 +13,14 @@ const LW: usize = 1024;
|
||||
/// High-water mark
|
||||
const HW: usize = 8 * 1024;
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
bitflags::bitflags! {
|
||||
struct Flags: u8 {
|
||||
const EOF = 0b0001;
|
||||
const READABLE = 0b0010;
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
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.
|
||||
///
|
||||
@@ -157,7 +152,7 @@ impl<T, U> Framed<T, U> {
|
||||
}
|
||||
|
||||
impl<T, U> Framed<T, U> {
|
||||
/// Serialize item and write to the inner buffer
|
||||
/// 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,
|
||||
@@ -194,18 +189,18 @@ impl<T, U> Framed<T, U> {
|
||||
match this.codec.decode_eof(this.read_buf) {
|
||||
Ok(Some(frame)) => return Poll::Ready(Some(Ok(frame))),
|
||||
Ok(None) => return Poll::Ready(None),
|
||||
Err(err) => return Poll::Ready(Some(Err(err))),
|
||||
Err(e) => return Poll::Ready(Some(Err(e))),
|
||||
}
|
||||
}
|
||||
|
||||
tracing::trace!("attempting to decode a frame");
|
||||
log::trace!("attempting to decode a frame");
|
||||
|
||||
match this.codec.decode(this.read_buf) {
|
||||
Ok(Some(frame)) => {
|
||||
tracing::trace!("frame decoded from buffer");
|
||||
log::trace!("frame decoded from buffer");
|
||||
return Poll::Ready(Some(Ok(frame)));
|
||||
}
|
||||
Err(err) => return Poll::Ready(Some(Err(err))),
|
||||
Err(e) => return Poll::Ready(Some(Err(e))),
|
||||
_ => (), // Need more data
|
||||
}
|
||||
|
||||
@@ -222,7 +217,7 @@ impl<T, U> Framed<T, U> {
|
||||
|
||||
let cnt = match tokio_util::io::poll_read_buf(this.io, cx, this.read_buf) {
|
||||
Poll::Pending => return Poll::Pending,
|
||||
Poll::Ready(Err(err)) => return Poll::Ready(Some(Err(err.into()))),
|
||||
Poll::Ready(Err(e)) => return Poll::Ready(Some(Err(e.into()))),
|
||||
Poll::Ready(Ok(cnt)) => cnt,
|
||||
};
|
||||
|
||||
@@ -234,16 +229,19 @@ impl<T, U> Framed<T, U> {
|
||||
}
|
||||
|
||||
/// Flush write buffer to underlying I/O stream.
|
||||
pub fn flush<I>(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), U::Error>>
|
||||
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();
|
||||
tracing::trace!("flushing framed transport");
|
||||
log::trace!("flushing framed transport");
|
||||
|
||||
while !this.write_buf.is_empty() {
|
||||
tracing::trace!("writing; remaining={}", this.write_buf.len());
|
||||
log::trace!("writing; remaining={}", this.write_buf.len());
|
||||
|
||||
let n = ready!(this.io.as_mut().poll_write(cx, this.write_buf))?;
|
||||
|
||||
@@ -262,12 +260,15 @@ impl<T, U> Framed<T, U> {
|
||||
// Try flushing the underlying IO
|
||||
ready!(this.io.poll_flush(cx))?;
|
||||
|
||||
tracing::trace!("framed transport flushed");
|
||||
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>>
|
||||
pub fn close<I>(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Result<(), U::Error>>
|
||||
where
|
||||
T: AsyncWrite,
|
||||
U: Encoder<I>,
|
||||
|
@@ -1,28 +1,25 @@
|
||||
//! Codec utilities for working with framed protocols.
|
||||
//!
|
||||
//! Contains adapters to go from streams of bytes, [`AsyncRead`] and [`AsyncWrite`], to framed
|
||||
//! streams implementing [`Sink`] and [`Stream`]. Framed streams are also known as `transports`.
|
||||
//! Contains adapters to go from streams of bytes, [`AsyncRead`] and
|
||||
//! [`AsyncWrite`], to framed streams implementing [`Sink`] and [`Stream`].
|
||||
//! Framed streams are also known as `transports`.
|
||||
//!
|
||||
//! [`Sink`]: futures_sink::Sink
|
||||
//! [`Stream`]: futures_core::Stream
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible, missing_docs)]
|
||||
#![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")]
|
||||
|
||||
pub use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
pub use tokio_util::{
|
||||
codec::{Decoder, Encoder},
|
||||
io::poll_read_buf,
|
||||
};
|
||||
|
||||
mod bcodec;
|
||||
mod framed;
|
||||
mod lines;
|
||||
|
||||
pub use self::{
|
||||
bcodec::BytesCodec,
|
||||
framed::{Framed, FramedParts},
|
||||
lines::LinesCodec,
|
||||
};
|
||||
pub use self::bcodec::BytesCodec;
|
||||
pub use self::framed::{Framed, FramedParts};
|
||||
pub use self::lines::LinesCodec;
|
||||
|
||||
pub use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
pub use tokio_util::codec::{Decoder, Encoder};
|
||||
pub use tokio_util::io::poll_read_buf;
|
||||
|
@@ -7,8 +7,8 @@ use super::{Decoder, Encoder};
|
||||
|
||||
/// Lines codec. Reads/writes line delimited strings.
|
||||
///
|
||||
/// Will split input up by LF or CRLF delimiters. Carriage return characters at the end of lines are
|
||||
/// not preserved.
|
||||
/// Will split input up by LF or CRLF delimiters. I.e. carriage return characters at the end of
|
||||
/// lines are not preserved.
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
#[non_exhaustive]
|
||||
pub struct LinesCodec;
|
||||
|
@@ -1,16 +1,12 @@
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
io::{self, Write},
|
||||
pin::Pin,
|
||||
task::{
|
||||
Context,
|
||||
Poll::{self, Pending, Ready},
|
||||
},
|
||||
};
|
||||
|
||||
use actix_codec::*;
|
||||
use bytes::{Buf as _, BufMut as _, BytesMut};
|
||||
use bytes::Buf;
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use futures_sink::Sink;
|
||||
use std::collections::VecDeque;
|
||||
use std::io::{self, Write};
|
||||
use std::pin::Pin;
|
||||
use std::task::Poll::{Pending, Ready};
|
||||
use std::task::{Context, Poll};
|
||||
use tokio_test::{assert_ready, task};
|
||||
|
||||
macro_rules! bilateral {
|
||||
@@ -54,7 +50,7 @@ impl Write for Bilateral {
|
||||
assert_eq!(&data[..], &src[..data.len()]);
|
||||
Ok(data.len())
|
||||
}
|
||||
Some(Err(err)) => Err(err),
|
||||
Some(Err(e)) => Err(e),
|
||||
None => panic!("unexpected write; {:?}", src),
|
||||
}
|
||||
}
|
||||
@@ -71,17 +67,20 @@ impl AsyncWrite for Bilateral {
|
||||
buf: &[u8],
|
||||
) -> Poll<Result<usize, io::Error>> {
|
||||
match Pin::get_mut(self).write(buf) {
|
||||
Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => Pending,
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => Pending,
|
||||
other => Ready(other),
|
||||
}
|
||||
}
|
||||
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
match Pin::get_mut(self).flush() {
|
||||
Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => Pending,
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => Pending,
|
||||
other => Ready(other),
|
||||
}
|
||||
}
|
||||
fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
fn poll_shutdown(
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
) -> Poll<Result<(), io::Error>> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
@@ -100,8 +99,8 @@ impl AsyncRead for Bilateral {
|
||||
buf.put_slice(&data);
|
||||
Ready(Ok(()))
|
||||
}
|
||||
Some(Err(ref err)) if err.kind() == WouldBlock => Pending,
|
||||
Some(Err(err)) => Ready(Err(err)),
|
||||
Some(Err(ref e)) if e.kind() == WouldBlock => Pending,
|
||||
Some(Err(e)) => Ready(Err(e)),
|
||||
None => Ready(Ok(())),
|
||||
}
|
||||
}
|
||||
|
@@ -1,51 +1,46 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
## 0.2.4
|
||||
|
||||
- Update `syn` dependency to `2`.
|
||||
- Minimum supported Rust version (MSRV) is now 1.65.
|
||||
|
||||
## 0.2.3
|
||||
|
||||
- Fix test macro in presence of other imports named "test". [#399]
|
||||
## 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
|
||||
|
||||
- Improve error recovery potential when macro input is invalid. [#391]
|
||||
- Allow custom `System`s on test macro. [#391]
|
||||
## 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
|
||||
|
||||
- Add optional argument `system` to `main` macro which can be used to specify the path to `actix_rt::System` (useful for re-exports). [#363]
|
||||
## 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
|
||||
|
||||
- Update to latest `actix_rt::System::new` signature. [#261]
|
||||
## 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
|
||||
|
||||
- Remove `actix-reexport` feature. [#218]
|
||||
## 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
|
||||
|
||||
- Add `actix-reexport` feature. [#218]
|
||||
## 0.1.3 - 2020-12-03
|
||||
* Add `actix-reexport` feature. [#218]
|
||||
|
||||
[#218]: https://github.com/actix/actix-net/pull/218
|
||||
|
||||
## 0.1.2
|
||||
|
||||
- Forward actix_rt::test arguments to test function [#127]
|
||||
## 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,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-macros"
|
||||
version = "0.2.4"
|
||||
version = "0.2.3"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Ibraheem Ahmed <ibrah1440@gmail.com>",
|
||||
@@ -10,19 +10,18 @@ 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.workspace = true
|
||||
rust-version.workspace = true
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
quote = "1"
|
||||
syn = { version = "2", features = ["full"] }
|
||||
quote = "1.0.3"
|
||||
syn = { version = "^1", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "2"
|
||||
actix-rt = "2.0.0"
|
||||
|
||||
futures-util = { version = "0.3.17", default-features = false }
|
||||
futures-util = { version = "0.3.7", default-features = false }
|
||||
rustversion = "1"
|
||||
trybuild = "1"
|
||||
|
@@ -9,15 +9,11 @@
|
||||
//! See docs for the [`#[test]`](macro@test) macro.
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible)]
|
||||
#![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;
|
||||
use syn::parse::Parser as _;
|
||||
|
||||
type AttributeArgs = syn::punctuated::Punctuated<syn::Meta, syn::Token![,]>;
|
||||
|
||||
/// Marks async entry-point function to be executed by Actix system.
|
||||
///
|
||||
@@ -28,9 +24,9 @@ type AttributeArgs = syn::punctuated::Punctuated<syn::Meta, syn::Token![,]>;
|
||||
/// println!("Hello world");
|
||||
/// }
|
||||
/// ```
|
||||
// #[allow(clippy::needless_doctest_main)]
|
||||
// #[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
#[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,
|
||||
@@ -38,11 +34,7 @@ pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
Err(err) => return input_and_compile_error(item, err),
|
||||
};
|
||||
|
||||
let parser = AttributeArgs::parse_terminated;
|
||||
let args = match parser.parse(args.clone()) {
|
||||
Ok(args) => args,
|
||||
Err(err) => return input_and_compile_error(args, err),
|
||||
};
|
||||
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
|
||||
|
||||
let attrs = &input.attrs;
|
||||
let vis = &input.vis;
|
||||
@@ -62,15 +54,11 @@ pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
|
||||
for arg in &args {
|
||||
match arg {
|
||||
syn::Meta::NameValue(syn::MetaNameValue {
|
||||
syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue {
|
||||
lit: syn::Lit::Str(lit),
|
||||
path,
|
||||
value:
|
||||
syn::Expr::Lit(syn::ExprLit {
|
||||
lit: syn::Lit::Str(lit),
|
||||
..
|
||||
}),
|
||||
..
|
||||
}) => match path
|
||||
})) => match path
|
||||
.get_ident()
|
||||
.map(|i| i.to_string().to_lowercase())
|
||||
.as_deref()
|
||||
@@ -89,7 +77,6 @@ pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
.into();
|
||||
}
|
||||
},
|
||||
|
||||
_ => {
|
||||
return syn::Error::new_spanned(arg, "Unknown attribute specified")
|
||||
.to_compile_error()
|
||||
@@ -126,11 +113,7 @@ pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
Err(err) => return input_and_compile_error(item, err),
|
||||
};
|
||||
|
||||
let parser = AttributeArgs::parse_terminated;
|
||||
let args = match parser.parse(args.clone()) {
|
||||
Ok(args) => args,
|
||||
Err(err) => return input_and_compile_error(args, err),
|
||||
};
|
||||
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
|
||||
|
||||
let attrs = &input.attrs;
|
||||
let vis = &input.vis;
|
||||
@@ -139,7 +122,7 @@ pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let mut has_test_attr = false;
|
||||
|
||||
for attr in attrs {
|
||||
if attr.path().is_ident("test") {
|
||||
if attr.path.is_ident("test") {
|
||||
has_test_attr = true;
|
||||
}
|
||||
}
|
||||
@@ -165,15 +148,11 @@ pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
|
||||
for arg in &args {
|
||||
match arg {
|
||||
syn::Meta::NameValue(syn::MetaNameValue {
|
||||
syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue {
|
||||
lit: syn::Lit::Str(lit),
|
||||
path,
|
||||
value:
|
||||
syn::Expr::Lit(syn::ExprLit {
|
||||
lit: syn::Lit::Str(lit),
|
||||
..
|
||||
}),
|
||||
..
|
||||
}) => match path
|
||||
})) => match path
|
||||
.get_ident()
|
||||
.map(|i| i.to_string().to_lowercase())
|
||||
.as_deref()
|
||||
|
@@ -1,8 +1,7 @@
|
||||
#[rustversion::stable(1.65)] // MSRV
|
||||
#[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");
|
||||
|
@@ -1,11 +1,14 @@
|
||||
error: the async keyword is missing from the function declaration
|
||||
--> tests/trybuild/main-02-only-async.rs:2:1
|
||||
--> $DIR/main-02-only-async.rs:2:1
|
||||
|
|
||||
2 | fn main() {
|
||||
| ^^
|
||||
|
||||
error[E0601]: `main` function not found in crate `$CRATE`
|
||||
--> tests/trybuild/main-02-only-async.rs:4:2
|
||||
--> $DIR/main-02-only-async.rs:1:1
|
||||
|
|
||||
4 | }
|
||||
| ^ consider adding a `main` function to `$DIR/tests/trybuild/main-02-only-async.rs`
|
||||
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,207 +1,174 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2023-xx-xx
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.65.
|
||||
|
||||
## 2.8.0 - 2022-12-21
|
||||
|
||||
- Add `#[track_caller]` attribute to `spawn` functions and methods. [#454]
|
||||
- Update `tokio-uring` dependency to `0.4`. [#473]
|
||||
- Minimum supported Rust version (MSRV) is now 1.59.
|
||||
|
||||
[#454]: https://github.com/actix/actix-net/pull/454
|
||||
[#473]: https://github.com/actix/actix-net/pull/473
|
||||
|
||||
## 2.7.0 - 2022-03-08
|
||||
|
||||
- Update `tokio-uring` dependency to `0.3`. [#448]
|
||||
- Minimum supported Rust version (MSRV) is now 1.49.
|
||||
|
||||
[#448]: https://github.com/actix/actix-net/pull/448
|
||||
|
||||
## 2.6.0 - 2022-01-12
|
||||
|
||||
- Update `tokio-uring` dependency to `0.2`. [#436]
|
||||
|
||||
[#436]: https://github.com/actix/actix-net/pull/436
|
||||
|
||||
## 2.5.1 - 2021-12-31
|
||||
|
||||
- Expose `System::with_tokio_rt` and `Arbiter::with_tokio_rt`. [#430]
|
||||
|
||||
[#430]: https://github.com/actix/actix-net/pull/430
|
||||
|
||||
## 2.5.0 - 2021-11-22
|
||||
|
||||
- Add `System::run_with_code` to allow retrieving the exit code on stop. [#411]
|
||||
|
||||
[#411]: https://github.com/actix/actix-net/pull/411
|
||||
|
||||
## 2.4.0 - 2021-11-05
|
||||
|
||||
- Add `Arbiter::try_current` for situations where thread may or may not have Arbiter context. [#408]
|
||||
- Start io-uring with `System::new` when feature is enabled. [#395]
|
||||
* Add `Arbiter::try_current` for situations where thread may or may not have Arbiter context. [#408]
|
||||
* Start io-uring with `System::new` when feature is enabled. [#395]
|
||||
|
||||
[#395]: https://github.com/actix/actix-net/pull/395
|
||||
[#408]: https://github.com/actix/actix-net/pull/408
|
||||
|
||||
## 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]
|
||||
## 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.
|
||||
## 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]
|
||||
## 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]
|
||||
## 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]
|
||||
## 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]
|
||||
## 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]
|
||||
## 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`.
|
||||
## 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]
|
||||
## 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)
|
||||
## 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]
|
||||
## 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 - 2019-12-11
|
||||
* Update dependencies
|
||||
|
||||
|
||||
## 1.0.0-alpha.3 - 2019-12-07
|
||||
* Migrate to tokio 0.2
|
||||
* Fix compilation on non-unix platforms
|
||||
|
||||
- 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)
|
||||
|
||||
- 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
|
||||
|
||||
- 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.
|
||||
|
||||
- Allow to join arbiter's thread. #60
|
||||
- Fix arbiter's thread panic message.
|
||||
|
||||
## 0.2.5 - 2019-09-02
|
||||
* Add arbiter specific storage
|
||||
|
||||
- Add arbiter specific storage
|
||||
|
||||
## 0.2.4 - 2019-07-17
|
||||
* Avoid a copy of the Future when initializing the Box. #29
|
||||
|
||||
- Avoid a copy of the Future when initializing the Box. #29
|
||||
|
||||
## 0.2.3 - 2019-06-22
|
||||
* Allow to start System using existing CurrentThread Handle #22
|
||||
|
||||
- Allow to start System using existing CurrentThread Handle #22
|
||||
|
||||
## 0.2.2 - 2019-03-28
|
||||
* Moved `blocking` module to `actix-threadpool` crate
|
||||
|
||||
- Moved `blocking` module to `actix-threadpool` crate
|
||||
|
||||
## 0.2.1 - 2019-03-11
|
||||
* 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
|
||||
|
||||
- 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
|
||||
|
||||
## 0.2.0 - 2019-03-06
|
||||
* `run` method returns `io::Result<()>`
|
||||
* Removed `Handle`
|
||||
|
||||
- `run` method returns `io::Result<()>`
|
||||
- Removed `Handle`
|
||||
|
||||
## 0.1.0 - 2018-12-09
|
||||
|
||||
- Initial release
|
||||
* Initial release
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-rt"
|
||||
version = "2.8.0"
|
||||
version = "2.4.0"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
@@ -11,8 +11,11 @@ homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-net.git"
|
||||
categories = ["network-programming", "asynchronous"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
name = "actix_rt"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = ["macros"]
|
||||
@@ -23,12 +26,11 @@ io-uring = ["tokio-uring"]
|
||||
actix-macros = { version = "0.2.3", optional = true }
|
||||
|
||||
futures-core = { version = "0.3", default-features = false }
|
||||
tokio = { version = "1.23.1", features = ["rt", "net", "parking_lot", "signal", "sync", "time"] }
|
||||
tokio = { version = "1.5.1", features = ["rt", "net", "parking_lot", "signal", "sync", "time"] }
|
||||
|
||||
# runtime for `io-uring` feature
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
tokio-uring = { version = "0.4", optional = true }
|
||||
tokio-uring = { version = "0.1", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.23.1", features = ["full"] }
|
||||
hyper = { version = "0.14.10", default-features = false, features = ["server", "tcp", "http1"] }
|
||||
tokio = { version = "1.5.1", features = ["full"] }
|
||||
hyper = { version = "0.14", default-features = false, features = ["server", "tcp", "http1"] }
|
||||
|
@@ -3,11 +3,11 @@
|
||||
> Tokio-based single-threaded async runtime for the Actix ecosystem.
|
||||
|
||||
[](https://crates.io/crates/actix-rt)
|
||||
[](https://docs.rs/actix-rt/2.8.0)
|
||||
[](https://docs.rs/actix-rt/2.4.0)
|
||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
||||

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

|
||||
[](https://discord.gg/WghFtEH6Hb)
|
||||
|
||||
|
@@ -1,9 +1,7 @@
|
||||
use std::{convert::Infallible, net::SocketAddr};
|
||||
|
||||
use hyper::{
|
||||
service::{make_service_fn, service_fn},
|
||||
Body, Request, Response, Server,
|
||||
};
|
||||
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")))
|
||||
@@ -20,10 +18,11 @@ fn main() {
|
||||
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);
|
||||
let server =
|
||||
Server::bind(&SocketAddr::from(([127, 0, 0, 1], 3000))).serve(make_service);
|
||||
|
||||
if let Err(err) = server.await {
|
||||
eprintln!("server error: {}", err);
|
||||
if let Err(e) = server.await {
|
||||
eprintln!("server error: {}", e);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@@ -99,7 +99,8 @@ impl Arbiter {
|
||||
#[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.")
|
||||
crate::runtime::default_tokio_runtime()
|
||||
.expect("Cannot create new Arbiter's Runtime.")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -107,6 +108,7 @@ impl Arbiter {
|
||||
///
|
||||
/// [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,
|
||||
@@ -148,7 +150,9 @@ impl Arbiter {
|
||||
.send(SystemCommand::DeregisterArbiter(arb_id));
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|err| panic!("Cannot spawn Arbiter's thread: {name:?}: {err:?}"));
|
||||
.unwrap_or_else(|err| {
|
||||
panic!("Cannot spawn Arbiter's thread: {:?}. {:?}", &name, err)
|
||||
});
|
||||
|
||||
ready_rx.recv().unwrap();
|
||||
|
||||
@@ -198,7 +202,9 @@ impl Arbiter {
|
||||
.send(SystemCommand::DeregisterArbiter(arb_id));
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|err| panic!("Cannot spawn Arbiter's thread: {name:?}: {err:?}"));
|
||||
.unwrap_or_else(|err| {
|
||||
panic!("Cannot spawn Arbiter's thread: {:?}. {:?}", &name, err)
|
||||
});
|
||||
|
||||
ready_rx.recv().unwrap();
|
||||
|
||||
@@ -255,7 +261,6 @@ impl Arbiter {
|
||||
/// 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.
|
||||
#[track_caller]
|
||||
pub fn spawn<Fut>(&self, future: Fut) -> bool
|
||||
where
|
||||
Fut: Future<Output = ()> + Send + 'static,
|
||||
@@ -271,7 +276,6 @@ impl Arbiter {
|
||||
/// channel in the function.
|
||||
///
|
||||
/// Returns true if function was sent successfully and false if the Arbiter has died.
|
||||
#[track_caller]
|
||||
pub fn spawn_fn<F>(&self, f: F) -> bool
|
||||
where
|
||||
F: FnOnce() + Send + 'static,
|
||||
@@ -298,7 +302,7 @@ impl Future for ArbiterRunner {
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
// process all items currently buffered in channel
|
||||
loop {
|
||||
match ready!(self.rx.poll_recv(cx)) {
|
||||
match ready!(Pin::new(&mut self.rx).poll_recv(cx)) {
|
||||
// channel closed; no more messages can be received
|
||||
None => return Poll::Ready(()),
|
||||
|
||||
|
@@ -36,13 +36,10 @@
|
||||
//! # `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.
|
||||
//!
|
||||
//! Note that there are currently some unimplemented parts of using `actix-rt` with `io-uring`.
|
||||
//! In particular, when running a `System`, only `System::block_on` is supported.
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible, missing_docs)]
|
||||
#![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")]
|
||||
|
||||
@@ -51,10 +48,13 @@ 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;
|
||||
|
||||
@@ -62,14 +62,11 @@ mod arbiter;
|
||||
mod runtime;
|
||||
mod system;
|
||||
|
||||
pub use tokio::pin;
|
||||
use tokio::task::JoinHandle;
|
||||
pub use self::arbiter::{Arbiter, ArbiterHandle};
|
||||
pub use self::runtime::Runtime;
|
||||
pub use self::system::{System, SystemRunner};
|
||||
|
||||
pub use self::{
|
||||
arbiter::{Arbiter, ArbiterHandle},
|
||||
runtime::Runtime,
|
||||
system::{System, SystemRunner},
|
||||
};
|
||||
pub use tokio::pin;
|
||||
|
||||
pub mod signal {
|
||||
//! Asynchronous signal handling (Tokio re-exports).
|
||||
@@ -91,13 +88,13 @@ pub mod net {
|
||||
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};
|
||||
pub use tokio::{
|
||||
io::Ready,
|
||||
net::{TcpListener, TcpSocket, TcpStream, UdpSocket},
|
||||
};
|
||||
|
||||
/// Extension trait over async read+write types that can also signal readiness.
|
||||
#[doc(hidden)]
|
||||
@@ -156,9 +153,10 @@ pub mod net {
|
||||
pub mod time {
|
||||
//! Utilities for tracking time (Tokio re-exports).
|
||||
|
||||
pub use tokio::time::{
|
||||
interval, interval_at, sleep, sleep_until, timeout, Instant, Interval, Sleep, Timeout,
|
||||
};
|
||||
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 {
|
||||
@@ -197,7 +195,6 @@ pub mod task {
|
||||
/// assert!(handle.await.unwrap_err().is_cancelled());
|
||||
/// # });
|
||||
/// ```
|
||||
#[track_caller]
|
||||
#[inline]
|
||||
pub fn spawn<Fut>(f: Fut) -> JoinHandle<Fut::Output>
|
||||
where
|
||||
|
@@ -53,7 +53,6 @@ impl Runtime {
|
||||
/// # 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.
|
||||
#[track_caller]
|
||||
pub fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
|
||||
where
|
||||
F: Future + 'static,
|
||||
@@ -74,7 +73,6 @@ impl Runtime {
|
||||
///
|
||||
/// The caller is responsible for ensuring that other spawned futures complete execution by
|
||||
/// calling `block_on` or `run`.
|
||||
#[track_caller]
|
||||
pub fn block_on<F>(&self, f: F) -> F::Output
|
||||
where
|
||||
F: Future,
|
||||
|
@@ -46,6 +46,7 @@ impl System {
|
||||
/// 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,
|
||||
@@ -66,7 +67,11 @@ impl System {
|
||||
let sys_ctrl = SystemController::new(sys_rx, stop_tx);
|
||||
rt.spawn(sys_ctrl);
|
||||
|
||||
SystemRunner { rt, stop_rx }
|
||||
SystemRunner {
|
||||
rt,
|
||||
stop_rx,
|
||||
system,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +94,7 @@ impl System {
|
||||
where
|
||||
F: Fn() -> tokio::runtime::Runtime,
|
||||
{
|
||||
unimplemented!("System::with_tokio_rt is not implemented for io-uring feature yet")
|
||||
unimplemented!("System::with_tokio_rt is not implemented yet")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,39 +177,39 @@ impl System {
|
||||
|
||||
/// Runner that keeps a [System]'s event loop alive until stop message is received.
|
||||
#[cfg(not(feature = "io-uring"))]
|
||||
#[must_use = "A SystemRunner does nothing unless `run` is called."]
|
||||
#[must_use = "A SystemRunner does nothing unless `run` or `block_on` is called."]
|
||||
#[derive(Debug)]
|
||||
pub struct SystemRunner {
|
||||
rt: crate::runtime::Runtime,
|
||||
stop_rx: oneshot::Receiver<i32>,
|
||||
#[allow(dead_code)]
|
||||
system: System,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "io-uring"))]
|
||||
impl SystemRunner {
|
||||
/// Starts event loop and will return once [System] is [stopped](System::stop).
|
||||
pub fn run(self) -> io::Result<()> {
|
||||
let exit_code = self.run_with_code()?;
|
||||
|
||||
match exit_code {
|
||||
0 => Ok(()),
|
||||
nonzero => Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("Non-zero exit code: {}", nonzero),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs the event loop until [stopped](System::stop_with_code), returning the exit code.
|
||||
pub fn run_with_code(self) -> io::Result<i32> {
|
||||
let SystemRunner { rt, stop_rx, .. } = self;
|
||||
|
||||
// run loop
|
||||
rt.block_on(stop_rx)
|
||||
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
|
||||
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)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs the provided future, blocking the current thread until the future completes.
|
||||
#[track_caller]
|
||||
#[inline]
|
||||
pub fn block_on<F: Future>(&self, fut: F) -> F::Output {
|
||||
self.rt.block_on(fut)
|
||||
@@ -213,7 +218,7 @@ impl SystemRunner {
|
||||
|
||||
/// Runner that keeps a [System]'s event loop alive until stop message is received.
|
||||
#[cfg(feature = "io-uring")]
|
||||
#[must_use = "A SystemRunner does nothing unless `run` is called."]
|
||||
#[must_use = "A SystemRunner does nothing unless `run` or `block_on` is called."]
|
||||
#[derive(Debug)]
|
||||
pub struct SystemRunner;
|
||||
|
||||
@@ -221,12 +226,7 @@ pub struct SystemRunner;
|
||||
impl SystemRunner {
|
||||
/// Starts event loop and will return once [System] is [stopped](System::stop).
|
||||
pub fn run(self) -> io::Result<()> {
|
||||
unimplemented!("SystemRunner::run is not implemented for io-uring feature yet");
|
||||
}
|
||||
|
||||
/// Runs the event loop until [stopped](System::stop_with_code), returning the exit code.
|
||||
pub fn run_with_code(self) -> io::Result<i32> {
|
||||
unimplemented!("SystemRunner::run_with_code is not implemented for io-uring feature yet");
|
||||
unimplemented!("SystemRunner::run is not implemented yet")
|
||||
}
|
||||
|
||||
/// Runs the provided future, blocking the current thread until the future completes.
|
||||
@@ -290,7 +290,7 @@ impl Future for SystemController {
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
// process all items currently buffered in channel
|
||||
loop {
|
||||
match ready!(self.cmd_rx.poll_recv(cx)) {
|
||||
match ready!(Pin::new(&mut self.cmd_rx).poll_recv(cx)) {
|
||||
// channel closed; no more messages can be received
|
||||
None => return Poll::Ready(()),
|
||||
|
||||
|
@@ -4,6 +4,7 @@ use std::{
|
||||
};
|
||||
|
||||
use actix_rt::{task::JoinError, Arbiter, System};
|
||||
|
||||
#[cfg(not(feature = "io-uring"))]
|
||||
use {
|
||||
std::{sync::mpsc::channel, thread},
|
||||
@@ -23,15 +24,6 @@ fn await_for_timer() {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "io-uring"))]
|
||||
#[test]
|
||||
fn run_with_code() {
|
||||
let sys = System::new();
|
||||
System::current().stop_with_code(42);
|
||||
let exit_code = sys.run_with_code().expect("system stop should not error");
|
||||
assert_eq!(exit_code, 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn join_another_arbiter() {
|
||||
let time = Duration::from_secs(1);
|
||||
@@ -107,8 +99,8 @@ fn wait_for_spawns() {
|
||||
|
||||
let handle = rt.spawn(async {
|
||||
println!("running on the runtime");
|
||||
// panic is caught at task boundary
|
||||
panic!("intentional test panic");
|
||||
// assertion panic is caught at task boundary
|
||||
assert_eq!(1, 2);
|
||||
});
|
||||
|
||||
assert!(rt.block_on(handle).is_err());
|
||||
|
@@ -1,260 +1,207 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2023-xx-xx
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
- Add support for MultiPath TCP (MPTCP) with `MpTcp` enum and `ServerBuilder::mptcp()` method.
|
||||
- Minimum supported Rust version (MSRV) is now 1.65.
|
||||
|
||||
## 2.2.0 - 2022-12-21
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.59.
|
||||
- Update `tokio-uring` dependency to `0.4`. [#473]
|
||||
|
||||
[#473]: https://github.com/actix/actix-net/pull/473
|
||||
|
||||
## 2.1.1 - 2022-03-09
|
||||
|
||||
- No significant changes since `2.1.0`.
|
||||
|
||||
## 2.1.0 - 2022-03-08
|
||||
|
||||
- Update `tokio-uring` dependency to `0.3`. [#448]
|
||||
- Logs emitted now use the `tracing` crate with `log` compatibility. [#448]
|
||||
- Wait for accept thread to stop before sending completion signal. [#443]
|
||||
|
||||
[#443]: https://github.com/actix/actix-net/pull/443
|
||||
[#448]: https://github.com/actix/actix-net/pull/448
|
||||
|
||||
## 2.0.0 - 2022-01-19
|
||||
|
||||
- No significant changes since `2.0.0-rc.4`.
|
||||
|
||||
## 2.0.0-rc.4 - 2022-01-12
|
||||
|
||||
- Update `tokio-uring` dependency to `0.2`. [#436]
|
||||
|
||||
[#436]: https://github.com/actix/actix-net/pull/436
|
||||
|
||||
## 2.0.0-rc.3 - 2021-12-31
|
||||
|
||||
- No significant changes since `2.0.0-rc.2`.
|
||||
|
||||
## 2.0.0-rc.2 - 2021-12-27
|
||||
|
||||
- Simplify `TestServer`. [#431]
|
||||
|
||||
[#431]: https://github.com/actix/actix-net/pull/431
|
||||
|
||||
## 2.0.0-rc.1 - 2021-12-05
|
||||
|
||||
- Hide implementation details of `Server`. [#424]
|
||||
- `Server` now runs only after awaiting it. [#425]
|
||||
|
||||
[#424]: https://github.com/actix/actix-net/pull/424
|
||||
[#425]: https://github.com/actix/actix-net/pull/425
|
||||
|
||||
## 2.0.0-beta.9 - 2021-11-15
|
||||
|
||||
- Restore `Arbiter` support lost in `beta.8`. [#417]
|
||||
|
||||
[#417]: https://github.com/actix/actix-net/pull/417
|
||||
|
||||
## 2.0.0-beta.8 - 2021-11-05 _(YANKED)_
|
||||
|
||||
- Fix non-unix signal handler. [#410]
|
||||
## 2.0.0-beta.8 - 2021-11-05
|
||||
* Fix non-unix signal handler. [#410]
|
||||
|
||||
[#410]: https://github.com/actix/actix-net/pull/410
|
||||
|
||||
## 2.0.0-beta.7 - 2021-11-05 _(YANKED)_
|
||||
|
||||
- Server can be started in regular Tokio runtime. [#408]
|
||||
- Expose new `Server` type whose `Future` impl resolves when server stops. [#408]
|
||||
- Rename `Server` to `ServerHandle`. [#407]
|
||||
- Add `Server::handle` to obtain handle to server. [#408]
|
||||
- Rename `ServerBuilder::{maxconn => max_concurrent_connections}`. [#407]
|
||||
- Deprecate crate-level `new` shortcut for server builder. [#408]
|
||||
- Minimum supported Rust version (MSRV) is now 1.52.
|
||||
## 2.0.0-beta.7 - 2021-11-05
|
||||
* Server can be started in regular Tokio runtime. [#408]
|
||||
* Expose new `Server` type whose `Future` impl resolves when server stops. [#408]
|
||||
* Rename `Server` to `ServerHandle`. [#407]
|
||||
* Add `Server::handle` to obtain handle to server. [#408]
|
||||
* Rename `ServerBuilder::{maxconn => max_concurrent_connections}`. [#407]
|
||||
* Deprecate crate-level `new` shortcut for server builder. [#408]
|
||||
* Minimum supported Rust version (MSRV) is now 1.52.
|
||||
|
||||
[#407]: https://github.com/actix/actix-net/pull/407
|
||||
[#408]: https://github.com/actix/actix-net/pull/408
|
||||
|
||||
## 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]
|
||||
## 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]
|
||||
## 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]
|
||||
## 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]
|
||||
## 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]
|
||||
## 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]
|
||||
## 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]
|
||||
## 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]
|
||||
## 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]
|
||||
## 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.1 - 2019-12-29
|
||||
* Rename `.start()` method to `.run()`
|
||||
|
||||
|
||||
## 1.0.0 - 2019-12-11
|
||||
* Use actix-net releases
|
||||
|
||||
- Use actix-net releases
|
||||
|
||||
## 1.0.0-alpha.4 - 2019-12-08
|
||||
* Use actix-service 1.0.0-alpha.4
|
||||
|
||||
- Use actix-service 1.0.0-alpha.4
|
||||
|
||||
## 1.0.0-alpha.3 - 2019-12-07
|
||||
* Migrate to tokio 0.2
|
||||
* Fix compilation on non-unix platforms
|
||||
* Better handling server configuration
|
||||
|
||||
- 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
|
||||
|
||||
- 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`
|
||||
|
||||
- Migrate to `std::future`
|
||||
|
||||
## 0.7.0 - 2019-10-04
|
||||
* Update `rustls` to 0.16
|
||||
* Minimum required Rust version upped to 1.37.0
|
||||
|
||||
- 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`
|
||||
|
||||
- Add UDS listening support to `ServerBuilder`
|
||||
|
||||
## 0.6.0 - 2019-07-18
|
||||
* Support Unix domain sockets #3
|
||||
|
||||
- Support Unix domain sockets #3
|
||||
|
||||
## 0.5.1 - 2019-05-18
|
||||
* ServerBuilder::shutdown_timeout() accepts u64
|
||||
|
||||
- ServerBuilder::shutdown_timeout() accepts u64
|
||||
|
||||
## 0.5.0 - 2019-05-12
|
||||
* Add `Debug` impl for `SslError`
|
||||
* Derive debug for `Server` and `ServerCommand`
|
||||
* Upgrade to actix-service 0.4
|
||||
|
||||
- Add `Debug` impl for `SslError`
|
||||
- Derive debug for `Server` and `ServerCommand`
|
||||
- Upgrade to actix-service 0.4
|
||||
|
||||
## 0.4.3 - 2019-04-16
|
||||
* Re-export `IoStream` trait
|
||||
* Depend on `ssl` and `rust-tls` features from actix-server-config
|
||||
|
||||
- Re-export `IoStream` trait
|
||||
- Depend on `ssl` and `rust-tls` features from actix-server-config
|
||||
|
||||
## 0.4.2 - 2019-03-30
|
||||
* Fix SIGINT force shutdown
|
||||
|
||||
- Fix SIGINT force shutdown
|
||||
|
||||
## 0.4.1 - 2019-03-14
|
||||
* `SystemRuntime::on_start()` - allow to run future before server service initialization
|
||||
|
||||
- `SystemRuntime::on_start()` - allow to run future before server service initialization
|
||||
|
||||
## 0.4.0 - 2019-03-12
|
||||
* Use `ServerConfig` for service factory
|
||||
* Wrap tcp socket to `Io` type
|
||||
* Upgrade actix-service
|
||||
|
||||
- Use `ServerConfig` for service factory
|
||||
- Wrap tcp socket to `Io` type
|
||||
- Upgrade actix-service
|
||||
|
||||
## 0.3.1 - 2019-03-04
|
||||
* Add `ServerBuilder::maxconnrate` sets the maximum per-worker number of concurrent connections
|
||||
* Add helper ssl error `SslError`
|
||||
* Rename `StreamServiceFactory` to `ServiceFactory`
|
||||
* Deprecate `StreamServiceFactory`
|
||||
|
||||
- Add `ServerBuilder::maxconnrate` sets the maximum per-worker number of concurrent connections
|
||||
- Add helper ssl error `SslError`
|
||||
- Rename `StreamServiceFactory` to `ServiceFactory`
|
||||
- Deprecate `StreamServiceFactory`
|
||||
|
||||
## 0.3.0 - 2019-03-02
|
||||
* Use new `NewService` trait
|
||||
|
||||
- Use new `NewService` trait
|
||||
|
||||
## 0.2.1 - 2019-02-09
|
||||
* Drop service response
|
||||
|
||||
- Drop service response
|
||||
|
||||
## 0.2.0 - 2019-02-01
|
||||
* Migrate to actix-service 0.2
|
||||
* Updated rustls dependency
|
||||
|
||||
- Migrate to actix-service 0.2
|
||||
- Updated rustls dependency
|
||||
|
||||
## 0.1.3 - 2018-12-21
|
||||
* Fix max concurrent connections handling
|
||||
|
||||
- Fix max concurrent connections handling
|
||||
|
||||
## 0.1.2 - 2018-12-12
|
||||
* rename ServiceConfig::rt() to ServiceConfig::apply()
|
||||
* Fix back-pressure for concurrent ssl handshakes
|
||||
|
||||
- rename ServiceConfig::rt() to ServiceConfig::apply()
|
||||
- Fix back-pressure for concurrent ssl handshakes
|
||||
|
||||
## 0.1.1 - 2018-12-11
|
||||
* Fix signal handling on windows
|
||||
|
||||
- Fix signal handling on windows
|
||||
|
||||
## 0.1.0 - 2018-12-09
|
||||
|
||||
- Move server to separate crate
|
||||
* Move server to separate crate
|
||||
|
@@ -1,46 +1,45 @@
|
||||
[package]
|
||||
name = "actix-server"
|
||||
version = "2.2.0"
|
||||
version = "2.0.0-beta.8"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
"Ali MJ Al-Nasrawy <alimjalnasrawy@gmail.com>",
|
||||
"fakeshadow <24548779@qq.com>",
|
||||
]
|
||||
description = "General purpose TCP server built for the Actix ecosystem"
|
||||
keywords = ["network", "tcp", "server", "framework", "async"]
|
||||
categories = ["network-programming", "asynchronous"]
|
||||
homepage = "https://actix.rs"
|
||||
keywords = ["network", "framework", "async", "futures"]
|
||||
repository = "https://github.com/actix/actix-net.git"
|
||||
categories = ["network-programming", "asynchronous"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
name = "actix_server"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
io-uring = ["tokio-uring", "actix-rt/io-uring"]
|
||||
io-uring = ["tokio-uring"]
|
||||
|
||||
[dependencies]
|
||||
actix-rt = { version = "2.8", default-features = false }
|
||||
actix-service = "2"
|
||||
actix-utils = "3"
|
||||
actix-rt = { version = "2.4.0", default-features = false }
|
||||
actix-service = "2.0.0"
|
||||
actix-utils = "3.0.0"
|
||||
|
||||
futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] }
|
||||
futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] }
|
||||
mio = { version = "0.8", features = ["os-poll", "net"] }
|
||||
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"
|
||||
socket2 = "0.5"
|
||||
tokio = { version = "1.23.1", features = ["sync"] }
|
||||
tracing = { version = "0.1.30", default-features = false, features = ["log"] }
|
||||
socket2 = "0.4.2"
|
||||
tokio = { version = "1.5.1", features = ["sync"] }
|
||||
|
||||
# runtime for `io-uring` feature
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
tokio-uring = { version = "0.4", optional = true }
|
||||
# runtime for io-uring feature
|
||||
tokio-uring = { version = "0.1", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-codec = "0.5"
|
||||
actix-rt = "2.8"
|
||||
actix-codec = "0.4.0"
|
||||
actix-rt = "2.0.0"
|
||||
|
||||
bytes = "1"
|
||||
env_logger = "0.10"
|
||||
futures-util = { version = "0.3.17", default-features = false, features = ["sink", "async-await-macro"] }
|
||||
tokio = { version = "1.23.1", features = ["io-util", "rt-multi-thread", "macros", "fs"] }
|
||||
env_logger = "0.9"
|
||||
futures-util = { version = "0.3.7", default-features = false, features = ["sink"] }
|
||||
tokio = { version = "1.5.1", features = ["io-util", "rt-multi-thread", "macros"] }
|
||||
|
@@ -1,15 +0,0 @@
|
||||
# actix-server
|
||||
|
||||
> General purpose TCP server built for the Actix ecosystem.
|
||||
|
||||
[](https://crates.io/crates/actix-server)
|
||||
[](https://docs.rs/actix-server/2.2.0)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
||||

|
||||
[](https://deps.rs/crate/actix-server/2.2.0)
|
||||

|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
## Resources
|
||||
- [Library Documentation](https://docs.rs/actix-server)
|
||||
- [Examples](/actix-server/examples)
|
@@ -1,95 +0,0 @@
|
||||
//! Simple file-reader TCP server with framed stream.
|
||||
//!
|
||||
//! Using the following command:
|
||||
//!
|
||||
//! ```sh
|
||||
//! nc 127.0.0.1 8080
|
||||
//! ```
|
||||
//!
|
||||
//! Follow the prompt and enter a file path, relative or absolute.
|
||||
|
||||
use std::io;
|
||||
|
||||
use actix_codec::{Framed, LinesCodec};
|
||||
use actix_rt::net::TcpStream;
|
||||
use actix_server::Server;
|
||||
use actix_service::{fn_service, ServiceFactoryExt as _};
|
||||
use futures_util::{SinkExt as _, StreamExt as _};
|
||||
use tokio::{fs::File, io::AsyncReadExt as _};
|
||||
|
||||
async fn run() -> io::Result<()> {
|
||||
env_logger::init_from_env(env_logger::Env::default().default_filter_or("info"));
|
||||
|
||||
let addr = ("127.0.0.1", 8080);
|
||||
tracing::info!("starting server on port: {}", &addr.0);
|
||||
|
||||
// Bind socket address and start worker(s). By default, the server uses the number of physical
|
||||
// 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("file-reader", addr, move || {
|
||||
fn_service(move |stream: TcpStream| async move {
|
||||
// set up codec to use with I/O resource
|
||||
let mut framed = Framed::new(stream, LinesCodec::default());
|
||||
|
||||
loop {
|
||||
// prompt for file name
|
||||
framed.send("Type file name to return:").await?;
|
||||
|
||||
// wait for next line
|
||||
match framed.next().await {
|
||||
Some(Ok(line)) => {
|
||||
match File::open(&line).await {
|
||||
Ok(mut file) => {
|
||||
tracing::info!("reading file: {}", &line);
|
||||
|
||||
// read file into String buffer
|
||||
let mut buf = String::new();
|
||||
file.read_to_string(&mut buf).await?;
|
||||
|
||||
// send String into framed object
|
||||
framed.send(buf).await?;
|
||||
|
||||
// break out of loop and
|
||||
break;
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!("{}", err);
|
||||
framed
|
||||
.send("File not found or not readable. Try again.")
|
||||
.await?;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// not being able to read a line from the stream is unrecoverable
|
||||
Some(Err(err)) => return Err(err),
|
||||
|
||||
// This EOF won't be hit.
|
||||
None => continue,
|
||||
}
|
||||
}
|
||||
|
||||
// close connection after file has been copied to TCP stream
|
||||
Ok(())
|
||||
})
|
||||
.map_err(|err| tracing::error!("service error: {:?}", err))
|
||||
})?
|
||||
.workers(2)
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> io::Result<()> {
|
||||
run().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// alternatively:
|
||||
// #[actix_rt::main]
|
||||
// async fn main() -> io::Result<()> {
|
||||
// run().await?;
|
||||
// Ok(())
|
||||
// }
|
@@ -22,19 +22,20 @@ 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 as _, AsyncWriteExt as _};
|
||||
|
||||
async fn run() -> io::Result<()> {
|
||||
env_logger::init_from_env(env_logger::Env::default().default_filter_or("info"));
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
|
||||
|
||||
let count = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let addr = ("127.0.0.1", 8080);
|
||||
tracing::info!("starting server on port: {}", &addr.0);
|
||||
info!("starting server on port: {}", &addr.0);
|
||||
|
||||
// Bind socket address and start worker(s). By default, the server uses the number of physical
|
||||
// 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.
|
||||
// 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);
|
||||
@@ -57,14 +58,14 @@ async fn run() -> io::Result<()> {
|
||||
|
||||
// more bytes to process
|
||||
Ok(bytes_read) => {
|
||||
tracing::info!("[{}] read {} bytes", num, 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) => {
|
||||
tracing::error!("stream error: {:?}", err);
|
||||
error!("Stream Error: {:?}", err);
|
||||
return Err(());
|
||||
}
|
||||
}
|
||||
@@ -74,14 +75,14 @@ async fn run() -> io::Result<()> {
|
||||
Ok((buf.freeze(), size))
|
||||
}
|
||||
})
|
||||
.map_err(|err| tracing::error!("service error: {:?}", err))
|
||||
.map_err(|err| error!("Service Error: {:?}", err))
|
||||
.and_then(move |(_, size)| {
|
||||
let num = num2.load(Ordering::SeqCst);
|
||||
tracing::info!("[{}] total bytes read: {}", num, size);
|
||||
info!("[{}] total bytes read: {}", num, size);
|
||||
ok(size)
|
||||
})
|
||||
})?
|
||||
.workers(2)
|
||||
.workers(1)
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
use std::{io, thread, time::Duration};
|
||||
|
||||
use actix_rt::time::Instant;
|
||||
use log::{debug, error, info};
|
||||
use mio::{Interest, Poll, Token as MioToken};
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
use crate::{
|
||||
availability::Availability,
|
||||
@@ -24,7 +24,7 @@ struct ServerSocketInfo {
|
||||
timeout: Option<actix_rt::time::Instant>,
|
||||
}
|
||||
|
||||
/// Poll instance of the server.
|
||||
/// poll instance of the server.
|
||||
pub(crate) struct Accept {
|
||||
poll: Poll,
|
||||
waker_queue: WakerQueue,
|
||||
@@ -41,7 +41,7 @@ impl Accept {
|
||||
pub(crate) fn start(
|
||||
sockets: Vec<(usize, MioListener)>,
|
||||
builder: &ServerBuilder,
|
||||
) -> io::Result<(WakerQueue, Vec<WorkerHandleServer>, thread::JoinHandle<()>)> {
|
||||
) -> io::Result<(WakerQueue, Vec<WorkerHandleServer>)> {
|
||||
let handle_server = ServerHandle::new(builder.cmd_tx.clone());
|
||||
|
||||
// construct poll instance and its waker
|
||||
@@ -73,12 +73,12 @@ impl Accept {
|
||||
handle_server,
|
||||
)?;
|
||||
|
||||
let accept_handle = thread::Builder::new()
|
||||
thread::Builder::new()
|
||||
.name("actix-server acceptor".to_owned())
|
||||
.spawn(move || accept.poll_with(&mut sockets))
|
||||
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
|
||||
|
||||
Ok((waker_queue, handles_server, accept_handle))
|
||||
Ok((waker_queue, handles_server))
|
||||
}
|
||||
|
||||
fn new_with_sockets(
|
||||
@@ -127,10 +127,10 @@ impl Accept {
|
||||
let mut events = mio::Events::with_capacity(256);
|
||||
|
||||
loop {
|
||||
if let Err(err) = self.poll.poll(&mut events, self.timeout) {
|
||||
match err.kind() {
|
||||
if let Err(e) = self.poll.poll(&mut events, self.timeout) {
|
||||
match e.kind() {
|
||||
io::ErrorKind::Interrupted => {}
|
||||
_ => panic!("Poll error: {}", err),
|
||||
_ => panic!("Poll error: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ impl Accept {
|
||||
WAKER_TOKEN => {
|
||||
let exit = self.handle_waker(sockets);
|
||||
if exit {
|
||||
info!("accept thread stopped");
|
||||
info!("Accept thread stopped");
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -161,13 +161,11 @@ impl Accept {
|
||||
// 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 interests can be added until the current
|
||||
// task is done. Take care not to take the guard again inside this loop.
|
||||
// take guard with every iteration so no new interest can be added
|
||||
// until the current task is done.
|
||||
let mut guard = self.waker_queue.guard();
|
||||
|
||||
#[allow(clippy::significant_drop_in_scrutinee)]
|
||||
match guard.pop_front() {
|
||||
// Worker notified it became available.
|
||||
// worker notify it becomes available.
|
||||
Some(WakerInterest::WorkerAvailable(idx)) => {
|
||||
drop(guard);
|
||||
|
||||
@@ -178,7 +176,7 @@ impl Accept {
|
||||
}
|
||||
}
|
||||
|
||||
// A new worker thread has been created so store its handle.
|
||||
// a new worker thread is made and it's handle would be added to Accept
|
||||
Some(WakerInterest::Worker(handle)) => {
|
||||
drop(guard);
|
||||
|
||||
@@ -299,16 +297,16 @@ impl Accept {
|
||||
|
||||
fn register_logged(&self, info: &mut ServerSocketInfo) {
|
||||
match self.register(info) {
|
||||
Ok(_) => debug!("resume accepting connections on {}", info.lst.local_addr()),
|
||||
Err(err) => error!("can not register server socket {}", err),
|
||||
Ok(_) => debug!("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(_) => debug!("paused accepting connections on {}", info.lst.local_addr()),
|
||||
Err(err) => {
|
||||
error!("can not deregister server socket {}", err)
|
||||
Ok(_) => debug!("Paused accepting connections on {}", info.lst.local_addr()),
|
||||
Err(e) => {
|
||||
error!("Can not deregister server socket {}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -352,7 +350,7 @@ impl Accept {
|
||||
self.remove_next();
|
||||
|
||||
if self.handles.is_empty() {
|
||||
error!("no workers");
|
||||
error!("No workers");
|
||||
// All workers are gone and Conn is nowhere to be sent.
|
||||
// Treat this situation as Ok and drop Conn.
|
||||
return Ok(());
|
||||
@@ -398,10 +396,10 @@ impl Accept {
|
||||
let conn = Conn { io, token };
|
||||
self.accept_one(conn);
|
||||
}
|
||||
Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => return,
|
||||
Err(ref err) if connection_error(err) => continue,
|
||||
Err(err) => {
|
||||
error!("error accepting connection: {}", err);
|
||||
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);
|
||||
|
@@ -1,33 +1,20 @@
|
||||
use std::{io, time::Duration};
|
||||
|
||||
use actix_rt::net::TcpStream;
|
||||
use log::{info, trace};
|
||||
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
|
||||
use tracing::{info, trace};
|
||||
|
||||
use crate::{
|
||||
server::ServerCommand,
|
||||
service::{InternalServiceFactory, ServerServiceFactory, StreamNewService},
|
||||
socket::{create_mio_tcp_listener, MioListener, MioTcpListener, StdTcpListener, ToSocketAddrs},
|
||||
service::{InternalServiceFactory, ServiceFactory, StreamNewService},
|
||||
socket::{
|
||||
create_mio_tcp_listener, MioListener, MioTcpListener, StdSocketAddr, StdTcpListener,
|
||||
ToSocketAddrs,
|
||||
},
|
||||
worker::ServerWorkerConfig,
|
||||
Server,
|
||||
};
|
||||
|
||||
/// Multipath TCP (MPTCP) preference.
|
||||
///
|
||||
/// Also see [`ServerBuilder::mptcp()`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum MpTcp {
|
||||
/// MPTCP will not be used when binding sockets.
|
||||
Disabled,
|
||||
|
||||
/// MPTCP will be attempted when binding sockets. If errors occur, regular TCP will be
|
||||
/// attempted, too.
|
||||
TcpFallback,
|
||||
|
||||
/// MPTCP will be used when binding sockets (with no fallback).
|
||||
NoFallback,
|
||||
}
|
||||
|
||||
/// [Server] builder.
|
||||
pub struct ServerBuilder {
|
||||
pub(crate) threads: usize,
|
||||
@@ -35,7 +22,6 @@ pub struct ServerBuilder {
|
||||
pub(crate) backlog: u32,
|
||||
pub(crate) factories: Vec<Box<dyn InternalServiceFactory>>,
|
||||
pub(crate) sockets: Vec<(usize, String, MioListener)>,
|
||||
pub(crate) mptcp: MpTcp,
|
||||
pub(crate) exit: bool,
|
||||
pub(crate) listen_os_signals: bool,
|
||||
pub(crate) cmd_tx: UnboundedSender<ServerCommand>,
|
||||
@@ -55,12 +41,11 @@ impl ServerBuilder {
|
||||
let (cmd_tx, cmd_rx) = unbounded_channel();
|
||||
|
||||
ServerBuilder {
|
||||
threads: num_cpus::get_physical(),
|
||||
threads: num_cpus::get(),
|
||||
token: 0,
|
||||
factories: Vec::new(),
|
||||
sockets: Vec::new(),
|
||||
backlog: 2048,
|
||||
mptcp: MpTcp::Disabled,
|
||||
exit: false,
|
||||
listen_os_signals: true,
|
||||
cmd_tx,
|
||||
@@ -71,11 +56,8 @@ impl ServerBuilder {
|
||||
|
||||
/// Set number of workers to start.
|
||||
///
|
||||
/// `num` must be greater than 0.
|
||||
///
|
||||
/// The default worker count is the number of physical CPU cores available. If your benchmark
|
||||
/// testing indicates that simultaneous multi-threading is beneficial to your app, you can use
|
||||
/// the [`num_cpus`] crate to acquire the _logical_ core count instead.
|
||||
/// By default server uses number of available logical CPU as workers count. Workers must be
|
||||
/// greater than 0.
|
||||
pub fn workers(mut self, num: usize) -> Self {
|
||||
assert_ne!(num, 0, "workers must be greater than 0");
|
||||
self.threads = num;
|
||||
@@ -114,24 +96,6 @@ impl ServerBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets MultiPath TCP (MPTCP) preference on bound sockets.
|
||||
///
|
||||
/// Multipath TCP (MPTCP) builds on top of TCP to improve connection redundancy and performance
|
||||
/// by sharing a network data stream across multiple underlying TCP sessions. See [mptcp.dev]
|
||||
/// for more info about MPTCP itself.
|
||||
///
|
||||
/// MPTCP is available on Linux kernel version 5.6 and higher. In addition, you'll also need to
|
||||
/// ensure the kernel option is enabled using `sysctl net.mptcp.enabled=1`.
|
||||
///
|
||||
/// This method will have no effect if called after a `bind()`.
|
||||
///
|
||||
/// [mptcp.dev]: https://www.mptcp.dev
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn mptcp(mut self, mptcp_enabled: MpTcp) -> Self {
|
||||
self.mptcp = mptcp_enabled;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the maximum per-worker number of concurrent connections.
|
||||
///
|
||||
/// All socket listeners will stop accepting connections when this limit is reached for
|
||||
@@ -149,7 +113,7 @@ impl ServerBuilder {
|
||||
self.max_concurrent_connections(num)
|
||||
}
|
||||
|
||||
/// Stop Actix `System` after server shutdown.
|
||||
/// Stop Actix system.
|
||||
pub fn system_exit(mut self) -> Self {
|
||||
self.exit = true;
|
||||
self
|
||||
@@ -174,13 +138,12 @@ impl ServerBuilder {
|
||||
}
|
||||
|
||||
/// Add new service to the server.
|
||||
pub fn bind<F, U, N>(mut self, name: N, addr: U, factory: F) -> io::Result<Self>
|
||||
pub fn bind<F, U, N: AsRef<str>>(mut self, name: N, addr: U, factory: F) -> io::Result<Self>
|
||||
where
|
||||
F: ServerServiceFactory<TcpStream>,
|
||||
F: ServiceFactory<TcpStream>,
|
||||
U: ToSocketAddrs,
|
||||
N: AsRef<str>,
|
||||
{
|
||||
let sockets = bind_addr(addr, self.backlog, &self.mptcp)?;
|
||||
let sockets = bind_addr(addr, self.backlog)?;
|
||||
|
||||
trace!("binding server to: {:?}", &sockets);
|
||||
|
||||
@@ -207,7 +170,7 @@ impl ServerBuilder {
|
||||
factory: F,
|
||||
) -> io::Result<Self>
|
||||
where
|
||||
F: ServerServiceFactory<TcpStream>,
|
||||
F: ServiceFactory<TcpStream>,
|
||||
{
|
||||
lst.set_nonblocking(true)?;
|
||||
let addr = lst.local_addr()?;
|
||||
@@ -231,7 +194,7 @@ impl ServerBuilder {
|
||||
if self.sockets.is_empty() {
|
||||
panic!("Server should have at least one bound socket");
|
||||
} else {
|
||||
info!("starting {} workers", self.threads);
|
||||
info!("Starting {} workers", self.threads);
|
||||
Server::new(self)
|
||||
}
|
||||
}
|
||||
@@ -248,16 +211,16 @@ impl ServerBuilder {
|
||||
/// Add new unix domain service to the server.
|
||||
pub fn bind_uds<F, U, N>(self, name: N, addr: U, factory: F) -> io::Result<Self>
|
||||
where
|
||||
F: ServerServiceFactory<actix_rt::net::UnixStream>,
|
||||
F: ServiceFactory<actix_rt::net::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(err) = std::fs::remove_file(addr.as_ref()) {
|
||||
if let Err(e) = std::fs::remove_file(addr.as_ref()) {
|
||||
// NotFound is expected and not an issue. Anything else is.
|
||||
if err.kind() != std::io::ErrorKind::NotFound {
|
||||
return Err(err);
|
||||
if e.kind() != std::io::ErrorKind::NotFound {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,12 +238,12 @@ impl ServerBuilder {
|
||||
factory: F,
|
||||
) -> io::Result<Self>
|
||||
where
|
||||
F: ServerServiceFactory<actix_rt::net::UnixStream>,
|
||||
F: ServiceFactory<actix_rt::net::UnixStream>,
|
||||
{
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
lst.set_nonblocking(true)?;
|
||||
let token = self.next_token();
|
||||
let addr = crate::socket::StdSocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
|
||||
let addr = StdSocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
|
||||
self.factories.push(StreamNewService::create(
|
||||
name.as_ref().to_string(),
|
||||
token,
|
||||
@@ -296,25 +259,23 @@ impl ServerBuilder {
|
||||
pub(super) fn bind_addr<S: ToSocketAddrs>(
|
||||
addr: S,
|
||||
backlog: u32,
|
||||
mptcp: &MpTcp,
|
||||
) -> io::Result<Vec<MioTcpListener>> {
|
||||
let mut opt_err = None;
|
||||
let mut err = None;
|
||||
let mut success = false;
|
||||
let mut sockets = Vec::new();
|
||||
|
||||
for addr in addr.to_socket_addrs()? {
|
||||
match create_mio_tcp_listener(addr, backlog, mptcp) {
|
||||
match create_mio_tcp_listener(addr, backlog) {
|
||||
Ok(lst) => {
|
||||
success = true;
|
||||
sockets.push(lst);
|
||||
}
|
||||
Err(err) => opt_err = Some(err),
|
||||
Err(e) => err = Some(e),
|
||||
}
|
||||
}
|
||||
|
||||
if success {
|
||||
Ok(sockets)
|
||||
} else if let Some(err) = opt_err.take() {
|
||||
} else if let Some(err) = err.take() {
|
||||
Err(err)
|
||||
} else {
|
||||
Err(io::Error::new(
|
||||
|
@@ -42,13 +42,10 @@ impl ServerHandle {
|
||||
/// Stop incoming connection processing, stop all workers and exit.
|
||||
pub fn stop(&self, graceful: bool) -> impl Future<Output = ()> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
let _ = self.cmd_tx.send(ServerCommand::Stop {
|
||||
graceful,
|
||||
completion: Some(tx),
|
||||
force_system_stop: false,
|
||||
});
|
||||
|
||||
async {
|
||||
let _ = rx.await;
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@ use std::{
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use futures_core::future::BoxFuture;
|
||||
use futures_core::future::{BoxFuture, LocalBoxFuture};
|
||||
|
||||
// a poor man's join future. joined future is only used when starting/stopping the server.
|
||||
// pin_project and pinned futures are overkill for this task.
|
||||
@@ -61,12 +61,69 @@ impl<T> Future for JoinAll<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn join_all_local<T>(
|
||||
fut: Vec<impl Future<Output = T> + 'static>,
|
||||
) -> JoinAllLocal<T> {
|
||||
let fut = fut
|
||||
.into_iter()
|
||||
.map(|f| JoinLocalFuture::LocalFuture(Box::pin(f)))
|
||||
.collect();
|
||||
|
||||
JoinAllLocal { fut }
|
||||
}
|
||||
|
||||
// a poor man's join future. joined future is only used when starting/stopping the server.
|
||||
// pin_project and pinned futures are overkill for this task.
|
||||
pub(crate) struct JoinAllLocal<T> {
|
||||
fut: Vec<JoinLocalFuture<T>>,
|
||||
}
|
||||
|
||||
enum JoinLocalFuture<T> {
|
||||
LocalFuture(LocalBoxFuture<'static, T>),
|
||||
Result(Option<T>),
|
||||
}
|
||||
|
||||
impl<T> Unpin for JoinAllLocal<T> {}
|
||||
|
||||
impl<T> Future for JoinAllLocal<T> {
|
||||
type Output = Vec<T>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let mut ready = true;
|
||||
|
||||
let this = self.get_mut();
|
||||
for fut in this.fut.iter_mut() {
|
||||
if let JoinLocalFuture::LocalFuture(f) = fut {
|
||||
match f.as_mut().poll(cx) {
|
||||
Poll::Ready(t) => {
|
||||
*fut = JoinLocalFuture::Result(Some(t));
|
||||
}
|
||||
Poll::Pending => ready = false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ready {
|
||||
let mut res = Vec::new();
|
||||
for fut in this.fut.iter_mut() {
|
||||
if let JoinLocalFuture::Result(f) = fut {
|
||||
res.push(f.take().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
Poll::Ready(res)
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use actix_utils::future::ready;
|
||||
|
||||
use super::*;
|
||||
|
||||
use actix_utils::future::ready;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_join_all() {
|
||||
let futs = vec![ready(Ok(1)), ready(Err(3)), ready(Ok(9))];
|
||||
@@ -75,4 +132,13 @@ mod test {
|
||||
assert_eq!(Err(3), res.next().unwrap());
|
||||
assert_eq!(Ok(9), res.next().unwrap());
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_join_all_local() {
|
||||
let futs = vec![ready(Ok(1)), ready(Err(3)), ready(Ok(9))];
|
||||
let mut res = join_all_local(futs).await.into_iter();
|
||||
assert_eq!(Ok(1), res.next().unwrap());
|
||||
assert_eq!(Err(3), res.next().unwrap());
|
||||
assert_eq!(Ok(9), res.next().unwrap());
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,6 @@
|
||||
//! General purpose TCP server.
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
|
||||
@@ -18,15 +17,14 @@ mod test_server;
|
||||
mod waker_queue;
|
||||
mod worker;
|
||||
|
||||
pub use self::builder::ServerBuilder;
|
||||
pub use self::handle::ServerHandle;
|
||||
pub use self::server::Server;
|
||||
pub use self::service::ServiceFactory;
|
||||
pub use self::test_server::TestServer;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use self::socket::FromStream;
|
||||
pub use self::{
|
||||
builder::{MpTcp, ServerBuilder},
|
||||
handle::ServerHandle,
|
||||
server::Server,
|
||||
service::ServerServiceFactory,
|
||||
test_server::TestServer,
|
||||
};
|
||||
|
||||
/// Start server building process
|
||||
#[doc(hidden)]
|
||||
|
@@ -3,22 +3,23 @@ use std::{
|
||||
io, mem,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use actix_rt::{time::sleep, System};
|
||||
use futures_core::{future::BoxFuture, Stream};
|
||||
use futures_util::stream::StreamExt as _;
|
||||
use tokio::sync::{mpsc::UnboundedReceiver, oneshot};
|
||||
use tracing::{error, info};
|
||||
use futures_core::future::BoxFuture;
|
||||
use log::{error, info};
|
||||
use tokio::sync::{
|
||||
mpsc::{UnboundedReceiver, UnboundedSender},
|
||||
oneshot,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
accept::Accept,
|
||||
builder::ServerBuilder,
|
||||
join_all::join_all,
|
||||
service::InternalServiceFactory,
|
||||
signals::{SignalKind, Signals},
|
||||
signals::{Signal, Signals},
|
||||
waker_queue::{WakerInterest, WakerQueue},
|
||||
worker::{ServerWorker, ServerWorkerConfig, WorkerHandleServer},
|
||||
ServerHandle,
|
||||
@@ -26,31 +27,22 @@ use crate::{
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ServerCommand {
|
||||
/// Worker failed to accept connection, indicating a probable panic.
|
||||
///
|
||||
/// Contains index of faulted worker.
|
||||
/// TODO
|
||||
WorkerFaulted(usize),
|
||||
|
||||
/// Pause accepting connections.
|
||||
///
|
||||
/// Contains return channel to notify caller of successful state change.
|
||||
Pause(oneshot::Sender<()>),
|
||||
|
||||
/// Resume accepting connections.
|
||||
///
|
||||
/// Contains return channel to notify caller of successful state change.
|
||||
Resume(oneshot::Sender<()>),
|
||||
|
||||
/// Stop accepting connections and begin shutdown procedure.
|
||||
/// TODO
|
||||
Stop {
|
||||
/// True if shut down should be graceful.
|
||||
graceful: bool,
|
||||
|
||||
/// Return channel to notify caller that shutdown is complete.
|
||||
completion: Option<oneshot::Sender<()>>,
|
||||
|
||||
/// Force System exit when true, overriding `ServerBuilder::system_exit()` if it is false.
|
||||
force_system_stop: bool,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -62,11 +54,11 @@ pub(crate) enum ServerCommand {
|
||||
/// Creates a worker per CPU core (or the number specified in [`ServerBuilder::workers`]) and
|
||||
/// distributes connections with a round-robin strategy.
|
||||
///
|
||||
/// The [Server] must be awaited or polled in order to start running. It will resolve when the
|
||||
/// server has fully shut down.
|
||||
/// The [Server] must be awaited to process stop commands and listen for OS signals. It will resolve
|
||||
/// when the server has fully shut down.
|
||||
///
|
||||
/// # Shutdown Signals
|
||||
/// On UNIX systems, `SIGTERM` will start a graceful shutdown and `SIGQUIT` or `SIGINT` will start a
|
||||
/// On UNIX systems, `SIGQUIT` will start a graceful shutdown and `SIGTERM` or `SIGINT` will start a
|
||||
/// forced shutdown. On Windows, a Ctrl-C signal will start a forced shutdown.
|
||||
///
|
||||
/// A graceful shutdown will wait for all workers to stop first.
|
||||
@@ -121,10 +113,10 @@ pub(crate) enum ServerCommand {
|
||||
/// .await
|
||||
/// }
|
||||
/// ```
|
||||
#[must_use = "Server does nothing unless you `.await` or poll it"]
|
||||
pub struct Server {
|
||||
handle: ServerHandle,
|
||||
fut: BoxFuture<'static, io::Result<()>>,
|
||||
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||
pub enum Server {
|
||||
Server(ServerInner),
|
||||
Error(Option<io::Error>),
|
||||
}
|
||||
|
||||
impl Server {
|
||||
@@ -133,157 +125,176 @@ impl Server {
|
||||
ServerBuilder::default()
|
||||
}
|
||||
|
||||
pub(crate) fn new(builder: ServerBuilder) -> Self {
|
||||
Server {
|
||||
handle: ServerHandle::new(builder.cmd_tx.clone()),
|
||||
fut: Box::pin(ServerInner::run(builder)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a `Server` handle that can be used issue commands and change it's state.
|
||||
///
|
||||
/// See [ServerHandle](ServerHandle) for usage.
|
||||
pub fn handle(&self) -> ServerHandle {
|
||||
self.handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for Server {
|
||||
type Output = io::Result<()>;
|
||||
|
||||
#[inline]
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
Pin::new(&mut Pin::into_inner(self).fut).poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ServerInner {
|
||||
worker_handles: Vec<WorkerHandleServer>,
|
||||
accept_handle: Option<thread::JoinHandle<()>>,
|
||||
worker_config: ServerWorkerConfig,
|
||||
services: Vec<Box<dyn InternalServiceFactory>>,
|
||||
waker_queue: WakerQueue,
|
||||
system_stop: bool,
|
||||
stopping: bool,
|
||||
}
|
||||
|
||||
impl ServerInner {
|
||||
async fn run(builder: ServerBuilder) -> io::Result<()> {
|
||||
let (mut this, mut mux) = Self::run_sync(builder)?;
|
||||
|
||||
while let Some(cmd) = mux.next().await {
|
||||
this.handle_cmd(cmd).await;
|
||||
|
||||
if this.stopping {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_sync(mut builder: ServerBuilder) -> io::Result<(Self, ServerEventMultiplexer)> {
|
||||
pub(crate) fn new(mut builder: ServerBuilder) -> Self {
|
||||
let sockets = mem::take(&mut builder.sockets)
|
||||
.into_iter()
|
||||
.map(|t| (t.0, t.2))
|
||||
.collect();
|
||||
|
||||
// Give log information on what runtime will be used.
|
||||
let is_actix = actix_rt::System::try_current().is_some();
|
||||
let is_tokio = tokio::runtime::Handle::try_current().is_ok();
|
||||
let is_actix = actix_rt::System::try_current().is_some();
|
||||
|
||||
match (is_actix, is_tokio) {
|
||||
(true, _) => info!("Actix runtime found; starting in Actix runtime"),
|
||||
(_, true) => info!("Tokio runtime found; starting in existing Tokio runtime"),
|
||||
(_, false) => panic!("Actix or Tokio runtime not found; halting"),
|
||||
match (is_tokio, is_actix) {
|
||||
(true, false) => info!("Tokio runtime found. Starting in existing Tokio runtime"),
|
||||
(_, true) => info!("Actix runtime found. Starting in Actix runtime"),
|
||||
(_, _) => info!(
|
||||
"Actix/Tokio runtime not found. Starting in newt Tokio current-thread runtime"
|
||||
),
|
||||
}
|
||||
|
||||
for (_, name, lst) in &builder.sockets {
|
||||
info!(
|
||||
r#"starting service: "{}", workers: {}, listening on: {}"#,
|
||||
r#"Starting service: "{}", workers: {}, listening on: {}"#,
|
||||
name,
|
||||
builder.threads,
|
||||
lst.local_addr()
|
||||
);
|
||||
}
|
||||
|
||||
let (waker_queue, worker_handles, accept_handle) = Accept::start(sockets, &builder)?;
|
||||
match Accept::start(sockets, &builder) {
|
||||
Ok((waker_queue, worker_handles)) => {
|
||||
// construct OS signals listener future
|
||||
let signals = (builder.listen_os_signals).then(Signals::new);
|
||||
|
||||
let mux = ServerEventMultiplexer {
|
||||
signal_fut: (builder.listen_os_signals).then(Signals::new),
|
||||
cmd_rx: builder.cmd_rx,
|
||||
};
|
||||
Self::Server(ServerInner {
|
||||
cmd_tx: builder.cmd_tx.clone(),
|
||||
cmd_rx: builder.cmd_rx,
|
||||
signals,
|
||||
waker_queue,
|
||||
worker_handles,
|
||||
worker_config: builder.worker_config,
|
||||
services: builder.factories,
|
||||
exit: builder.exit,
|
||||
stop_task: None,
|
||||
})
|
||||
}
|
||||
|
||||
let server = ServerInner {
|
||||
waker_queue,
|
||||
accept_handle: Some(accept_handle),
|
||||
worker_handles,
|
||||
worker_config: builder.worker_config,
|
||||
services: builder.factories,
|
||||
system_stop: builder.exit,
|
||||
stopping: false,
|
||||
};
|
||||
|
||||
Ok((server, mux))
|
||||
Err(err) => Self::Error(Some(err)),
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_cmd(&mut self, item: ServerCommand) {
|
||||
/// Get a handle for ServerFuture that can be used to change state of actix server.
|
||||
///
|
||||
/// See [ServerHandle](ServerHandle) for usage.
|
||||
pub fn handle(&self) -> ServerHandle {
|
||||
match self {
|
||||
Server::Server(inner) => ServerHandle::new(inner.cmd_tx.clone()),
|
||||
Server::Error(err) => {
|
||||
// TODO: i don't think this is the best way to handle server startup fail
|
||||
panic!(
|
||||
"server handle can not be obtained because server failed to start up: {}",
|
||||
err.as_ref().unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for Server {
|
||||
type Output = io::Result<()>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
match self.as_mut().get_mut() {
|
||||
Server::Error(err) => Poll::Ready(Err(err
|
||||
.take()
|
||||
.expect("Server future cannot be polled after error"))),
|
||||
|
||||
Server::Server(inner) => {
|
||||
// poll Signals
|
||||
if let Some(ref mut signals) = inner.signals {
|
||||
if let Poll::Ready(signal) = Pin::new(signals).poll(cx) {
|
||||
inner.stop_task = inner.handle_signal(signal);
|
||||
// drop signals listener
|
||||
inner.signals = None;
|
||||
}
|
||||
}
|
||||
|
||||
// handle stop tasks and eager drain command channel
|
||||
loop {
|
||||
if let Some(ref mut fut) = inner.stop_task {
|
||||
// only resolve stop task and exit
|
||||
return fut.as_mut().poll(cx).map(|_| Ok(()));
|
||||
}
|
||||
|
||||
match Pin::new(&mut inner.cmd_rx).poll_recv(cx) {
|
||||
Poll::Ready(Some(cmd)) => {
|
||||
// if stop task is required, set it and loop
|
||||
inner.stop_task = inner.handle_cmd(cmd);
|
||||
}
|
||||
_ => return Poll::Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ServerInner {
|
||||
worker_handles: Vec<WorkerHandleServer>,
|
||||
worker_config: ServerWorkerConfig,
|
||||
services: Vec<Box<dyn InternalServiceFactory>>,
|
||||
exit: bool,
|
||||
cmd_tx: UnboundedSender<ServerCommand>,
|
||||
cmd_rx: UnboundedReceiver<ServerCommand>,
|
||||
signals: Option<Signals>,
|
||||
waker_queue: WakerQueue,
|
||||
stop_task: Option<BoxFuture<'static, ()>>,
|
||||
}
|
||||
|
||||
impl ServerInner {
|
||||
fn handle_cmd(&mut self, item: ServerCommand) -> Option<BoxFuture<'static, ()>> {
|
||||
match item {
|
||||
ServerCommand::Pause(tx) => {
|
||||
self.waker_queue.wake(WakerInterest::Pause);
|
||||
let _ = tx.send(());
|
||||
None
|
||||
}
|
||||
|
||||
ServerCommand::Resume(tx) => {
|
||||
self.waker_queue.wake(WakerInterest::Resume);
|
||||
let _ = tx.send(());
|
||||
None
|
||||
}
|
||||
|
||||
ServerCommand::Stop {
|
||||
graceful,
|
||||
completion,
|
||||
force_system_stop,
|
||||
} => {
|
||||
self.stopping = true;
|
||||
let exit = self.exit;
|
||||
|
||||
// Signal accept thread to stop.
|
||||
// Signal is non-blocking; we wait for thread to stop later.
|
||||
// stop accept thread
|
||||
self.waker_queue.wake(WakerInterest::Stop);
|
||||
|
||||
// send stop signal to workers
|
||||
// stop workers
|
||||
let workers_stop = self
|
||||
.worker_handles
|
||||
.iter()
|
||||
.map(|worker| worker.stop(graceful))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if graceful {
|
||||
// wait for all workers to shut down
|
||||
let _ = join_all(workers_stop).await;
|
||||
}
|
||||
Some(Box::pin(async move {
|
||||
if graceful {
|
||||
// wait for all workers to shut down
|
||||
let _ = join_all(workers_stop).await;
|
||||
}
|
||||
|
||||
// wait for accept thread stop
|
||||
self.accept_handle
|
||||
.take()
|
||||
.unwrap()
|
||||
.join()
|
||||
.expect("Accept thread must not panic in any case");
|
||||
if let Some(tx) = completion {
|
||||
let _ = tx.send(());
|
||||
}
|
||||
|
||||
if let Some(tx) = completion {
|
||||
let _ = tx.send(());
|
||||
}
|
||||
|
||||
if self.system_stop || force_system_stop {
|
||||
sleep(Duration::from_millis(300)).await;
|
||||
System::try_current().as_ref().map(System::stop);
|
||||
}
|
||||
if exit {
|
||||
sleep(Duration::from_millis(300)).await;
|
||||
System::try_current().as_ref().map(System::stop);
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
ServerCommand::WorkerFaulted(idx) => {
|
||||
// TODO: maybe just return with warning log if not found ?
|
||||
assert!(self.worker_handles.iter().any(|wrk| wrk.idx == idx));
|
||||
|
||||
error!("worker {} has died; restarting", idx);
|
||||
error!("Worker {} has died; restarting", idx);
|
||||
|
||||
let factories = self
|
||||
.services
|
||||
@@ -309,60 +320,40 @@ impl ServerInner {
|
||||
|
||||
Err(err) => error!("can not restart worker {}: {}", idx, err),
|
||||
};
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn map_signal(signal: SignalKind) -> ServerCommand {
|
||||
fn handle_signal(&mut self, signal: Signal) -> Option<BoxFuture<'static, ()>> {
|
||||
match signal {
|
||||
SignalKind::Int => {
|
||||
Signal::Int => {
|
||||
info!("SIGINT received; starting forced shutdown");
|
||||
ServerCommand::Stop {
|
||||
self.exit = true;
|
||||
self.handle_cmd(ServerCommand::Stop {
|
||||
graceful: false,
|
||||
completion: None,
|
||||
force_system_stop: true,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
SignalKind::Term => {
|
||||
Signal::Term => {
|
||||
info!("SIGTERM received; starting graceful shutdown");
|
||||
ServerCommand::Stop {
|
||||
self.exit = true;
|
||||
self.handle_cmd(ServerCommand::Stop {
|
||||
graceful: true,
|
||||
completion: None,
|
||||
force_system_stop: true,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
SignalKind::Quit => {
|
||||
Signal::Quit => {
|
||||
info!("SIGQUIT received; starting forced shutdown");
|
||||
ServerCommand::Stop {
|
||||
self.exit = true;
|
||||
self.handle_cmd(ServerCommand::Stop {
|
||||
graceful: false,
|
||||
completion: None,
|
||||
force_system_stop: true,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ServerEventMultiplexer {
|
||||
cmd_rx: UnboundedReceiver<ServerCommand>,
|
||||
signal_fut: Option<Signals>,
|
||||
}
|
||||
|
||||
impl Stream for ServerEventMultiplexer {
|
||||
type Item = ServerCommand;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
let this = Pin::into_inner(self);
|
||||
|
||||
if let Some(signal_fut) = &mut this.signal_fut {
|
||||
if let Poll::Ready(signal) = Pin::new(signal_fut).poll(cx) {
|
||||
this.signal_fut = None;
|
||||
return Poll::Ready(Some(ServerInner::map_signal(signal)));
|
||||
}
|
||||
}
|
||||
|
||||
this.cmd_rx.poll_recv(cx)
|
||||
}
|
||||
}
|
||||
|
@@ -1,21 +1,16 @@
|
||||
use std::{
|
||||
marker::PhantomData,
|
||||
net::SocketAddr,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use std::marker::PhantomData;
|
||||
use std::net::SocketAddr;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use actix_service::{Service, ServiceFactory as BaseServiceFactory};
|
||||
use actix_utils::future::{ready, Ready};
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use tracing::error;
|
||||
use log::error;
|
||||
|
||||
use crate::{
|
||||
socket::{FromStream, MioStream},
|
||||
worker::WorkerCounterGuard,
|
||||
};
|
||||
use crate::socket::{FromStream, MioStream};
|
||||
use crate::worker::WorkerCounterGuard;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub trait ServerServiceFactory<Stream: FromStream>: Send + Clone + 'static {
|
||||
pub trait ServiceFactory<Stream: FromStream>: Send + Clone + 'static {
|
||||
type Factory: BaseServiceFactory<Stream, Config = ()>;
|
||||
|
||||
fn create(&self) -> Self::Factory;
|
||||
@@ -77,15 +72,15 @@ where
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => {
|
||||
error!("can not convert to an async TCP stream: {err}");
|
||||
Err(e) => {
|
||||
error!("Can not convert to an async tcp stream: {}", e);
|
||||
Err(())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct StreamNewService<F: ServerServiceFactory<Io>, Io: FromStream> {
|
||||
pub(crate) struct StreamNewService<F: ServiceFactory<Io>, Io: FromStream> {
|
||||
name: String,
|
||||
inner: F,
|
||||
token: usize,
|
||||
@@ -95,7 +90,7 @@ pub(crate) struct StreamNewService<F: ServerServiceFactory<Io>, Io: FromStream>
|
||||
|
||||
impl<F, Io> StreamNewService<F, Io>
|
||||
where
|
||||
F: ServerServiceFactory<Io>,
|
||||
F: ServiceFactory<Io>,
|
||||
Io: FromStream + Send + 'static,
|
||||
{
|
||||
pub(crate) fn create(
|
||||
@@ -116,7 +111,7 @@ where
|
||||
|
||||
impl<F, Io> InternalServiceFactory for StreamNewService<F, Io>
|
||||
where
|
||||
F: ServerServiceFactory<Io>,
|
||||
F: ServiceFactory<Io>,
|
||||
Io: FromStream + Send + 'static,
|
||||
{
|
||||
fn name(&self, _: usize) -> &str {
|
||||
@@ -148,7 +143,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, T, I> ServerServiceFactory<I> for F
|
||||
impl<F, T, I> ServiceFactory<I> for F
|
||||
where
|
||||
F: Fn() -> T + Send + Clone + 'static,
|
||||
T: BaseServiceFactory<I, Config = ()>,
|
||||
|
@@ -5,13 +5,13 @@ use std::{
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use tracing::trace;
|
||||
use log::trace;
|
||||
|
||||
/// Types of process signals.
|
||||
// #[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[allow(dead_code)] // variants are never constructed on non-unix
|
||||
pub(crate) enum SignalKind {
|
||||
pub(crate) enum Signal {
|
||||
/// `SIGINT`
|
||||
Int,
|
||||
|
||||
@@ -22,12 +22,12 @@ pub(crate) enum SignalKind {
|
||||
Quit,
|
||||
}
|
||||
|
||||
impl fmt::Display for SignalKind {
|
||||
impl fmt::Display for Signal {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(match self {
|
||||
SignalKind::Int => "SIGINT",
|
||||
SignalKind::Term => "SIGTERM",
|
||||
SignalKind::Quit => "SIGQUIT",
|
||||
Signal::Int => "SIGINT",
|
||||
Signal::Term => "SIGTERM",
|
||||
Signal::Quit => "SIGQUIT",
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ pub(crate) struct Signals {
|
||||
signals: futures_core::future::BoxFuture<'static, std::io::Result<()>>,
|
||||
|
||||
#[cfg(unix)]
|
||||
signals: Vec<(SignalKind, actix_rt::signal::unix::Signal)>,
|
||||
signals: Vec<(Signal, actix_rt::signal::unix::Signal)>,
|
||||
}
|
||||
|
||||
impl Signals {
|
||||
@@ -58,9 +58,9 @@ impl Signals {
|
||||
use actix_rt::signal::unix;
|
||||
|
||||
let sig_map = [
|
||||
(unix::SignalKind::interrupt(), SignalKind::Int),
|
||||
(unix::SignalKind::terminate(), SignalKind::Term),
|
||||
(unix::SignalKind::quit(), SignalKind::Quit),
|
||||
(unix::SignalKind::interrupt(), Signal::Int),
|
||||
(unix::SignalKind::terminate(), Signal::Term),
|
||||
(unix::SignalKind::quit(), Signal::Quit),
|
||||
];
|
||||
|
||||
let signals = sig_map
|
||||
@@ -69,8 +69,8 @@ impl Signals {
|
||||
unix::signal(*kind)
|
||||
.map(|tokio_sig| (*sig, tokio_sig))
|
||||
.map_err(|e| {
|
||||
tracing::error!(
|
||||
"can not initialize stream handler for {:?} err: {}",
|
||||
log::error!(
|
||||
"Can not initialize stream handler for {:?} err: {}",
|
||||
sig,
|
||||
e
|
||||
)
|
||||
@@ -85,18 +85,19 @@ impl Signals {
|
||||
}
|
||||
|
||||
impl Future for Signals {
|
||||
type Output = SignalKind;
|
||||
type Output = Signal;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
self.signals.as_mut().poll(cx).map(|_| SignalKind::Int)
|
||||
self.signals.as_mut().poll(cx).map(|_| Signal::Int)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
for (sig, fut) in self.signals.iter_mut() {
|
||||
if fut.poll_recv(cx).is_ready() {
|
||||
// TODO: match on if let Some ?
|
||||
if Pin::new(fut).poll_recv(cx).is_ready() {
|
||||
trace!("{} received", sig);
|
||||
return Poll::Ready(*sig);
|
||||
}
|
||||
|
@@ -1,17 +1,18 @@
|
||||
pub(crate) use std::net::{
|
||||
SocketAddr as StdSocketAddr, TcpListener as StdTcpListener, ToSocketAddrs,
|
||||
};
|
||||
|
||||
pub(crate) use mio::net::TcpListener as MioTcpListener;
|
||||
#[cfg(unix)]
|
||||
pub(crate) use {
|
||||
mio::net::UnixListener as MioUnixListener,
|
||||
std::os::unix::net::UnixListener as StdUnixListener,
|
||||
};
|
||||
|
||||
use std::{fmt, io};
|
||||
|
||||
use actix_rt::net::TcpStream;
|
||||
pub(crate) use mio::net::TcpListener as MioTcpListener;
|
||||
use mio::{event::Source, Interest, Registry, Token};
|
||||
#[cfg(unix)]
|
||||
pub(crate) use {
|
||||
mio::net::UnixListener as MioUnixListener, std::os::unix::net::UnixListener as StdUnixListener,
|
||||
};
|
||||
|
||||
use crate::builder::MpTcp;
|
||||
|
||||
pub(crate) enum MioListener {
|
||||
Tcp(MioTcpListener),
|
||||
@@ -106,7 +107,7 @@ impl fmt::Debug for MioListener {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
MioListener::Tcp(ref lst) => write!(f, "{:?}", lst),
|
||||
#[cfg(unix)]
|
||||
#[cfg(all(unix))]
|
||||
MioListener::Uds(ref lst) => write!(f, "{:?}", lst),
|
||||
}
|
||||
}
|
||||
@@ -158,24 +159,24 @@ pub enum MioStream {
|
||||
Uds(mio::net::UnixStream),
|
||||
}
|
||||
|
||||
/// Helper trait for converting a Mio stream into a Tokio stream.
|
||||
/// helper trait for converting mio stream to tokio stream.
|
||||
pub trait FromStream: Sized {
|
||||
fn from_mio(sock: MioStream) -> io::Result<Self>;
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
mod win_impl {
|
||||
use std::os::windows::io::{FromRawSocket, IntoRawSocket};
|
||||
|
||||
use super::*;
|
||||
|
||||
// TODO: This is a workaround and we need an efficient way to convert between Mio and Tokio stream
|
||||
use std::os::windows::io::{FromRawSocket, IntoRawSocket};
|
||||
|
||||
// FIXME: This is a workaround and we need an efficient way to convert between mio and tokio stream
|
||||
impl FromStream for TcpStream {
|
||||
fn from_mio(sock: MioStream) -> io::Result<Self> {
|
||||
match sock {
|
||||
MioStream::Tcp(mio) => {
|
||||
let raw = IntoRawSocket::into_raw_socket(mio);
|
||||
// SAFETY: This is an in-place conversion from Mio stream to Tokio stream.
|
||||
// SAFETY: This is a in place conversion from mio stream to tokio stream.
|
||||
TcpStream::from_std(unsafe { FromRawSocket::from_raw_socket(raw) })
|
||||
}
|
||||
}
|
||||
@@ -185,19 +186,19 @@ mod win_impl {
|
||||
|
||||
#[cfg(unix)]
|
||||
mod unix_impl {
|
||||
use super::*;
|
||||
|
||||
use std::os::unix::io::{FromRawFd, IntoRawFd};
|
||||
|
||||
use actix_rt::net::UnixStream;
|
||||
|
||||
use super::*;
|
||||
|
||||
// HACK: This is a workaround and we need an efficient way to convert between Mio and Tokio stream
|
||||
// FIXME: This is a workaround and we need an efficient way to convert between mio and tokio stream
|
||||
impl FromStream for TcpStream {
|
||||
fn from_mio(sock: MioStream) -> io::Result<Self> {
|
||||
match sock {
|
||||
MioStream::Tcp(mio) => {
|
||||
let raw = IntoRawFd::into_raw_fd(mio);
|
||||
// SAFETY: This is an in-place conversion from Mio stream to Tokio stream.
|
||||
// SAFETY: This is a in place conversion from mio stream to tokio stream.
|
||||
TcpStream::from_std(unsafe { FromRawFd::from_raw_fd(raw) })
|
||||
}
|
||||
MioStream::Uds(_) => {
|
||||
@@ -207,14 +208,14 @@ mod unix_impl {
|
||||
}
|
||||
}
|
||||
|
||||
// HACK: This is a workaround and we need an efficient way to convert between Mio and Tokio stream
|
||||
// FIXME: This is a workaround and we need an efficient way to convert between mio and tokio stream
|
||||
impl FromStream for UnixStream {
|
||||
fn from_mio(sock: MioStream) -> io::Result<Self> {
|
||||
match sock {
|
||||
MioStream::Tcp(_) => panic!("Should not happen, bug in server impl"),
|
||||
MioStream::Uds(mio) => {
|
||||
let raw = IntoRawFd::into_raw_fd(mio);
|
||||
// SAFETY: This is an in-place conversion from Mio stream to Tokio stream.
|
||||
// SAFETY: This is a in place conversion from mio stream to tokio stream.
|
||||
UnixStream::from_std(unsafe { FromRawFd::from_raw_fd(raw) })
|
||||
}
|
||||
}
|
||||
@@ -225,30 +226,10 @@ mod unix_impl {
|
||||
pub(crate) fn create_mio_tcp_listener(
|
||||
addr: StdSocketAddr,
|
||||
backlog: u32,
|
||||
mptcp: &MpTcp,
|
||||
) -> io::Result<MioTcpListener> {
|
||||
use socket2::{Domain, Protocol, Socket, Type};
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let protocol = Protocol::TCP;
|
||||
#[cfg(target_os = "linux")]
|
||||
let protocol = if matches!(mptcp, MpTcp::Disabled) {
|
||||
Protocol::TCP
|
||||
} else {
|
||||
Protocol::MPTCP
|
||||
};
|
||||
|
||||
let socket = match Socket::new(Domain::for_address(addr), Type::STREAM, Some(protocol)) {
|
||||
Ok(sock) => sock,
|
||||
|
||||
Err(err) if matches!(mptcp, MpTcp::TcpFallback) => {
|
||||
tracing::warn!("binding socket as MPTCP failed: {err}");
|
||||
tracing::warn!("falling back to TCP");
|
||||
Socket::new(Domain::for_address(addr), Type::STREAM, Some(Protocol::TCP))?
|
||||
}
|
||||
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
let socket = Socket::new(Domain::for_address(addr), Type::STREAM, Some(Protocol::TCP))?;
|
||||
|
||||
socket.set_reuse_address(true)?;
|
||||
socket.set_nonblocking(true)?;
|
||||
@@ -269,7 +250,7 @@ mod tests {
|
||||
assert_eq!(format!("{}", addr), "127.0.0.1:8080");
|
||||
|
||||
let addr: StdSocketAddr = "127.0.0.1:0".parse().unwrap();
|
||||
let lst = create_mio_tcp_listener(addr, 128, &MpTcp::Disabled).unwrap();
|
||||
let lst = create_mio_tcp_listener(addr, 128).unwrap();
|
||||
let lst = MioListener::Tcp(lst);
|
||||
assert!(format!("{:?}", lst).contains("TcpListener"));
|
||||
assert!(format!("{}", lst).contains("127.0.0.1"));
|
||||
|
@@ -1,8 +1,9 @@
|
||||
use std::{io, net, sync::mpsc, thread};
|
||||
use std::sync::mpsc;
|
||||
use std::{io, net, thread};
|
||||
|
||||
use actix_rt::{net::TcpStream, System};
|
||||
|
||||
use crate::{Server, ServerBuilder, ServerHandle, ServerServiceFactory};
|
||||
use crate::{Server, ServerBuilder, ServerHandle, ServiceFactory};
|
||||
|
||||
/// A testing server.
|
||||
///
|
||||
@@ -16,7 +17,7 @@ use crate::{Server, ServerBuilder, ServerHandle, ServerServiceFactory};
|
||||
///
|
||||
/// #[actix_rt::main]
|
||||
/// async fn main() {
|
||||
/// let srv = TestServer::start(|| fn_service(
|
||||
/// let srv = TestServer::with(|| fn_service(
|
||||
/// |sock| async move {
|
||||
/// println!("New connection: {:?}", sock);
|
||||
/// Ok::<_, ()>(())
|
||||
@@ -28,8 +29,8 @@ use crate::{Server, ServerBuilder, ServerHandle, ServerServiceFactory};
|
||||
/// ```
|
||||
pub struct TestServer;
|
||||
|
||||
/// Test server handle.
|
||||
pub struct TestServerHandle {
|
||||
/// Test server runtime
|
||||
pub struct TestServerRuntime {
|
||||
addr: net::SocketAddr,
|
||||
host: String,
|
||||
port: u16,
|
||||
@@ -38,26 +39,46 @@ pub struct TestServerHandle {
|
||||
}
|
||||
|
||||
impl TestServer {
|
||||
/// Start new `TestServer` using application factory and default server config.
|
||||
pub fn start(factory: impl ServerServiceFactory<TcpStream>) -> TestServerHandle {
|
||||
Self::start_with_builder(Server::build(), factory)
|
||||
}
|
||||
|
||||
/// Start new `TestServer` using application factory and server builder.
|
||||
pub fn start_with_builder(
|
||||
server_builder: ServerBuilder,
|
||||
factory: impl ServerServiceFactory<TcpStream>,
|
||||
) -> TestServerHandle {
|
||||
/// Start new server with server builder.
|
||||
pub fn start<F>(mut factory: F) -> TestServerRuntime
|
||||
where
|
||||
F: FnMut(ServerBuilder) -> ServerBuilder + Send + 'static,
|
||||
{
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
// run server in separate thread
|
||||
let thread_handle = thread::spawn(move || {
|
||||
let lst = net::TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let local_addr = lst.local_addr().unwrap();
|
||||
|
||||
System::new().block_on(async {
|
||||
let server = server_builder
|
||||
.listen("test", lst, factory)
|
||||
let server = factory(Server::build()).workers(1).disable_signals().run();
|
||||
tx.send(server.handle()).unwrap();
|
||||
server.await
|
||||
})
|
||||
});
|
||||
|
||||
let server_handle = rx.recv().unwrap();
|
||||
|
||||
TestServerRuntime {
|
||||
addr: "127.0.0.1:0".parse().unwrap(),
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 0,
|
||||
server_handle,
|
||||
thread_handle: Some(thread_handle),
|
||||
}
|
||||
}
|
||||
|
||||
/// Start new test server with application factory.
|
||||
pub fn with<F: ServiceFactory<TcpStream>>(factory: F) -> TestServerRuntime {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
// run server in separate thread
|
||||
let thread_handle = thread::spawn(move || {
|
||||
let sys = System::new();
|
||||
let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let local_addr = tcp.local_addr().unwrap();
|
||||
|
||||
sys.block_on(async {
|
||||
let server = Server::build()
|
||||
.listen("test", tcp, factory)
|
||||
.unwrap()
|
||||
.workers(1)
|
||||
.disable_signals()
|
||||
@@ -73,7 +94,7 @@ impl TestServer {
|
||||
let host = format!("{}", addr.ip());
|
||||
let port = addr.port();
|
||||
|
||||
TestServerHandle {
|
||||
TestServerRuntime {
|
||||
addr,
|
||||
host,
|
||||
port,
|
||||
@@ -84,22 +105,16 @@ impl TestServer {
|
||||
|
||||
/// Get first available unused local address.
|
||||
pub fn unused_addr() -> net::SocketAddr {
|
||||
use socket2::{Domain, Protocol, Socket, Type};
|
||||
|
||||
let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
|
||||
let domain = Domain::for_address(addr);
|
||||
let socket = Socket::new(domain, Type::STREAM, Some(Protocol::TCP)).unwrap();
|
||||
|
||||
socket.set_reuse_address(true).unwrap();
|
||||
socket.set_nonblocking(true).unwrap();
|
||||
socket.bind(&addr.into()).unwrap();
|
||||
socket.listen(1024).unwrap();
|
||||
|
||||
net::TcpListener::from(socket).local_addr().unwrap()
|
||||
let socket = mio::net::TcpSocket::new_v4().unwrap();
|
||||
socket.bind(addr).unwrap();
|
||||
socket.set_reuseaddr(true).unwrap();
|
||||
let tcp = socket.listen(1024).unwrap();
|
||||
tcp.local_addr().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl TestServerHandle {
|
||||
impl TestServerRuntime {
|
||||
/// Test server host.
|
||||
pub fn host(&self) -> &str {
|
||||
&self.host
|
||||
@@ -117,37 +132,18 @@ impl TestServerHandle {
|
||||
|
||||
/// Stop server.
|
||||
fn stop(&mut self) {
|
||||
drop(self.server_handle.stop(false));
|
||||
let _ = self.server_handle.stop(false);
|
||||
self.thread_handle.take().unwrap().join().unwrap().unwrap();
|
||||
}
|
||||
|
||||
/// Connect to server, returning a Tokio `TcpStream`.
|
||||
pub fn connect(&self) -> io::Result<TcpStream> {
|
||||
pub fn connect(&self) -> std::io::Result<TcpStream> {
|
||||
TcpStream::from_std(net::TcpStream::connect(self.addr)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TestServerHandle {
|
||||
impl Drop for TestServerRuntime {
|
||||
fn drop(&mut self) {
|
||||
self.stop()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_service::fn_service;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn connect_in_tokio_runtime() {
|
||||
let srv = TestServer::start(|| fn_service(|_sock| async move { Ok::<_, ()>(()) }));
|
||||
assert!(srv.connect().is_ok());
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn connect_in_actix_runtime() {
|
||||
let srv = TestServer::start(|| fn_service(|_sock| async move { Ok::<_, ()>(()) }));
|
||||
assert!(srv.connect().is_ok());
|
||||
}
|
||||
}
|
||||
|
@@ -17,13 +17,14 @@ use actix_rt::{
|
||||
Arbiter, ArbiterHandle, System,
|
||||
};
|
||||
use futures_core::{future::LocalBoxFuture, ready};
|
||||
use log::{error, info, trace};
|
||||
use tokio::sync::{
|
||||
mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
|
||||
oneshot,
|
||||
};
|
||||
use tracing::{error, info, trace};
|
||||
|
||||
use crate::{
|
||||
join_all::join_all_local,
|
||||
service::{BoxedServerService, InternalServiceFactory},
|
||||
socket::MioStream,
|
||||
waker_queue::{WakerInterest, WakerQueue},
|
||||
@@ -201,8 +202,8 @@ impl WorkerHandleServer {
|
||||
pub(crate) struct ServerWorker {
|
||||
// UnboundedReceiver<Conn> should always be the first field.
|
||||
// It must be dropped as soon as ServerWorker dropping.
|
||||
conn_rx: UnboundedReceiver<Conn>,
|
||||
stop_rx: UnboundedReceiver<Stop>,
|
||||
rx: UnboundedReceiver<Conn>,
|
||||
rx2: UnboundedReceiver<Stop>,
|
||||
counter: WorkerCounter,
|
||||
services: Box<[WorkerService]>,
|
||||
factories: Box<[Box<dyn InternalServiceFactory>]>,
|
||||
@@ -211,7 +212,7 @@ pub(crate) struct ServerWorker {
|
||||
}
|
||||
|
||||
struct WorkerService {
|
||||
factory_idx: usize,
|
||||
factory: usize,
|
||||
status: WorkerServiceStatus,
|
||||
service: BoxedServerService,
|
||||
}
|
||||
@@ -233,12 +234,6 @@ enum WorkerServiceStatus {
|
||||
Stopped,
|
||||
}
|
||||
|
||||
impl Default for WorkerServiceStatus {
|
||||
fn default() -> Self {
|
||||
Self::Unavailable
|
||||
}
|
||||
}
|
||||
|
||||
/// Config for worker behavior passed down from server builder.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct ServerWorkerConfig {
|
||||
@@ -250,7 +245,7 @@ pub(crate) struct ServerWorkerConfig {
|
||||
impl Default for ServerWorkerConfig {
|
||||
fn default() -> Self {
|
||||
// 512 is the default max blocking thread count of tokio runtime.
|
||||
let max_blocking_threads = std::cmp::max(512 / num_cpus::get_physical(), 1);
|
||||
let max_blocking_threads = std::cmp::max(512 / num_cpus::get(), 1);
|
||||
Self {
|
||||
shutdown_timeout: Duration::from_secs(30),
|
||||
max_blocking_threads,
|
||||
@@ -282,201 +277,116 @@ impl ServerWorker {
|
||||
) -> io::Result<(WorkerHandleAccept, WorkerHandleServer)> {
|
||||
trace!("starting server worker {}", idx);
|
||||
|
||||
let (tx1, conn_rx) = unbounded_channel();
|
||||
let (tx2, stop_rx) = unbounded_channel();
|
||||
let (tx1, rx) = unbounded_channel();
|
||||
let (tx2, rx2) = unbounded_channel();
|
||||
|
||||
let counter = Counter::new(config.max_concurrent_connections);
|
||||
let pair = handle_pair(idx, tx1, tx2, counter.clone());
|
||||
|
||||
let counter_clone = counter.clone();
|
||||
|
||||
// get actix system context if it is set
|
||||
let actix_system = System::try_current();
|
||||
|
||||
// get tokio runtime handle if it is set
|
||||
let tokio_handle = tokio::runtime::Handle::try_current().ok();
|
||||
let sys = System::try_current();
|
||||
|
||||
// service factories initialization channel
|
||||
let (factory_tx, factory_rx) = std::sync::mpsc::sync_channel::<io::Result<()>>(1);
|
||||
|
||||
// outline of following code:
|
||||
//
|
||||
// if system exists
|
||||
// if uring enabled
|
||||
// start arbiter using uring method
|
||||
// else
|
||||
// start arbiter with regular tokio
|
||||
// else
|
||||
// if uring enabled
|
||||
// start uring in spawned thread
|
||||
// else
|
||||
// start regular tokio in spawned thread
|
||||
let (factory_tx, factory_rx) = std::sync::mpsc::sync_channel(1);
|
||||
|
||||
// every worker runs in it's own thread and tokio runtime.
|
||||
// use a custom tokio runtime builder to change the settings of runtime.
|
||||
std::thread::Builder::new()
|
||||
.name(format!("actix-server worker {}", idx))
|
||||
.spawn(move || {
|
||||
// forward existing actix system context
|
||||
if let Some(sys) = sys {
|
||||
System::set_current(sys);
|
||||
}
|
||||
|
||||
match (actix_system, tokio_handle) {
|
||||
(None, None) => {
|
||||
panic!("No runtime detected. Start a Tokio (or Actix) runtime.");
|
||||
}
|
||||
let worker_fut = async move {
|
||||
let fut = factories
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, factory)| {
|
||||
let fut = factory.create();
|
||||
async move { fut.await.map(|(t, s)| (idx, t, s)) }
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// no actix system
|
||||
(None, Some(rt_handle)) => {
|
||||
std::thread::Builder::new()
|
||||
.name(format!("actix-server worker {}", idx))
|
||||
.spawn(move || {
|
||||
let (worker_stopped_tx, worker_stopped_rx) = oneshot::channel();
|
||||
// a second spawn to run !Send future tasks.
|
||||
spawn(async move {
|
||||
let res = join_all_local(fut)
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<_>, _>>();
|
||||
|
||||
// local set for running service init futures and worker services
|
||||
let ls = tokio::task::LocalSet::new();
|
||||
let services = match res {
|
||||
Ok(res) => res
|
||||
.into_iter()
|
||||
.fold(Vec::new(), |mut services, (factory, token, service)| {
|
||||
assert_eq!(token, services.len());
|
||||
services.push(WorkerService {
|
||||
factory,
|
||||
service,
|
||||
status: WorkerServiceStatus::Unavailable,
|
||||
});
|
||||
services
|
||||
})
|
||||
.into_boxed_slice(),
|
||||
|
||||
// init services using existing Tokio runtime (so probably on main thread)
|
||||
let services = rt_handle.block_on(ls.run_until(async {
|
||||
let mut services = Vec::new();
|
||||
|
||||
for (idx, factory) in factories.iter().enumerate() {
|
||||
match factory.create().await {
|
||||
Ok((token, svc)) => services.push((idx, token, svc)),
|
||||
|
||||
Err(err) => {
|
||||
error!("can not start worker: {:?}", err);
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("can not start server service {}", idx),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(services)
|
||||
}));
|
||||
|
||||
let services = match services {
|
||||
Ok(services) => {
|
||||
factory_tx.send(Ok(())).unwrap();
|
||||
services
|
||||
}
|
||||
Err(err) => {
|
||||
factory_tx.send(Err(err)).unwrap();
|
||||
Err(e) => {
|
||||
error!("Can not start worker: {:?}", e);
|
||||
Arbiter::try_current().as_ref().map(ArbiterHandle::stop);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let worker_services = wrap_worker_services(services);
|
||||
factory_tx.send(()).unwrap();
|
||||
|
||||
let worker_fut = async move {
|
||||
// spawn to make sure ServerWorker runs as non boxed future.
|
||||
spawn(async move {
|
||||
ServerWorker {
|
||||
conn_rx,
|
||||
stop_rx,
|
||||
services: worker_services.into_boxed_slice(),
|
||||
counter: WorkerCounter::new(idx, waker_queue, counter),
|
||||
factories: factories.into_boxed_slice(),
|
||||
state: WorkerState::default(),
|
||||
shutdown_timeout: config.shutdown_timeout,
|
||||
}
|
||||
.await;
|
||||
|
||||
// wake up outermost task waiting for shutdown
|
||||
worker_stopped_tx.send(()).unwrap();
|
||||
});
|
||||
|
||||
worker_stopped_rx.await.unwrap();
|
||||
};
|
||||
|
||||
#[cfg(all(target_os = "linux", feature = "io-uring"))]
|
||||
{
|
||||
// TODO: pass max blocking thread config when tokio-uring enable configuration
|
||||
// on building runtime.
|
||||
let _ = config.max_blocking_threads;
|
||||
tokio_uring::start(worker_fut);
|
||||
}
|
||||
|
||||
#[cfg(not(all(target_os = "linux", feature = "io-uring")))]
|
||||
{
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.max_blocking_threads(config.max_blocking_threads)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
rt.block_on(ls.run_until(worker_fut));
|
||||
}
|
||||
})
|
||||
.expect("cannot spawn server worker thread");
|
||||
}
|
||||
|
||||
// with actix system
|
||||
(Some(_sys), _) => {
|
||||
#[cfg(all(target_os = "linux", feature = "io-uring"))]
|
||||
let arbiter = {
|
||||
// TODO: pass max blocking thread config when tokio-uring enable configuration
|
||||
// on building runtime.
|
||||
let _ = config.max_blocking_threads;
|
||||
Arbiter::new()
|
||||
};
|
||||
|
||||
#[cfg(not(all(target_os = "linux", feature = "io-uring")))]
|
||||
let arbiter = {
|
||||
Arbiter::with_tokio_rt(move || {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.max_blocking_threads(config.max_blocking_threads)
|
||||
.build()
|
||||
.unwrap()
|
||||
})
|
||||
};
|
||||
|
||||
arbiter.spawn(async move {
|
||||
// spawn_local to run !Send future tasks.
|
||||
spawn(async move {
|
||||
let mut services = Vec::new();
|
||||
|
||||
for (idx, factory) in factories.iter().enumerate() {
|
||||
match factory.create().await {
|
||||
Ok((token, svc)) => services.push((idx, token, svc)),
|
||||
|
||||
Err(err) => {
|
||||
error!("can not start worker: {:?}", err);
|
||||
Arbiter::current().stop();
|
||||
factory_tx
|
||||
.send(Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("can not start server service {}", idx),
|
||||
)))
|
||||
.unwrap();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
factory_tx.send(Ok(())).unwrap();
|
||||
|
||||
let worker_services = wrap_worker_services(services);
|
||||
|
||||
// spawn to make sure ServerWorker runs as non boxed future.
|
||||
// a third spawn to make sure ServerWorker runs as non boxed future.
|
||||
spawn(ServerWorker {
|
||||
conn_rx,
|
||||
stop_rx,
|
||||
services: worker_services.into_boxed_slice(),
|
||||
counter: WorkerCounter::new(idx, waker_queue, counter),
|
||||
rx,
|
||||
rx2,
|
||||
services,
|
||||
counter: WorkerCounter::new(idx, waker_queue, counter_clone),
|
||||
factories: factories.into_boxed_slice(),
|
||||
state: Default::default(),
|
||||
shutdown_timeout: config.shutdown_timeout,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
})
|
||||
.await
|
||||
.expect("task 3 panic");
|
||||
})
|
||||
.await
|
||||
.expect("task 2 panic");
|
||||
};
|
||||
|
||||
#[cfg(all(target_os = "linux", feature = "io-uring"))]
|
||||
{
|
||||
// TODO: pass max blocking thread config when tokio-uring enable configuration
|
||||
// on building runtime.
|
||||
let _ = config.max_blocking_threads;
|
||||
tokio_uring::start(worker_fut)
|
||||
}
|
||||
|
||||
#[cfg(not(all(target_os = "linux", feature = "io-uring")))]
|
||||
{
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.max_blocking_threads(config.max_blocking_threads)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
rt.block_on(tokio::task::LocalSet::new().run_until(worker_fut))
|
||||
}
|
||||
})
|
||||
.expect("worker thread error/panic");
|
||||
|
||||
// wait for service factories initialization
|
||||
factory_rx.recv().unwrap()?;
|
||||
factory_rx.recv().unwrap();
|
||||
|
||||
Ok(pair)
|
||||
Ok(handle_pair(idx, tx1, tx2, counter))
|
||||
}
|
||||
|
||||
fn restart_service(&mut self, idx: usize, factory_id: usize) {
|
||||
let factory = &self.factories[factory_id];
|
||||
trace!("service {:?} failed, restarting", factory.name(idx));
|
||||
trace!("Service {:?} failed, restarting", factory.name(idx));
|
||||
self.services[idx].status = WorkerServiceStatus::Restarting;
|
||||
self.state = WorkerState::Restarting(Restart {
|
||||
factory_id,
|
||||
@@ -508,8 +418,8 @@ impl ServerWorker {
|
||||
Poll::Ready(Ok(_)) => {
|
||||
if srv.status == WorkerServiceStatus::Unavailable {
|
||||
trace!(
|
||||
"service {:?} is available",
|
||||
self.factories[srv.factory_idx].name(idx)
|
||||
"Service {:?} is available",
|
||||
self.factories[srv.factory].name(idx)
|
||||
);
|
||||
srv.status = WorkerServiceStatus::Available;
|
||||
}
|
||||
@@ -519,19 +429,19 @@ impl ServerWorker {
|
||||
|
||||
if srv.status == WorkerServiceStatus::Available {
|
||||
trace!(
|
||||
"service {:?} is unavailable",
|
||||
self.factories[srv.factory_idx].name(idx)
|
||||
"Service {:?} is unavailable",
|
||||
self.factories[srv.factory].name(idx)
|
||||
);
|
||||
srv.status = WorkerServiceStatus::Unavailable;
|
||||
}
|
||||
}
|
||||
Poll::Ready(Err(_)) => {
|
||||
error!(
|
||||
"service {:?} readiness check returned error, restarting",
|
||||
self.factories[srv.factory_idx].name(idx)
|
||||
"Service {:?} readiness check returned error, restarting",
|
||||
self.factories[srv.factory].name(idx)
|
||||
);
|
||||
srv.status = WorkerServiceStatus::Failed;
|
||||
return Err((idx, srv.factory_idx));
|
||||
return Err((idx, srv.factory));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -574,6 +484,7 @@ impl Default for WorkerState {
|
||||
|
||||
impl Drop for ServerWorker {
|
||||
fn drop(&mut self) {
|
||||
trace!("stopping ServerWorker Arbiter");
|
||||
Arbiter::try_current().as_ref().map(ArbiterHandle::stop);
|
||||
}
|
||||
}
|
||||
@@ -585,14 +496,15 @@ impl Future for ServerWorker {
|
||||
let this = self.as_mut().get_mut();
|
||||
|
||||
// `StopWorker` message handler
|
||||
if let Poll::Ready(Some(Stop { graceful, tx })) = this.stop_rx.poll_recv(cx) {
|
||||
if let Poll::Ready(Some(Stop { graceful, tx })) = Pin::new(&mut this.rx2).poll_recv(cx)
|
||||
{
|
||||
let num = this.counter.total();
|
||||
if num == 0 {
|
||||
info!("shutting down idle worker");
|
||||
info!("Shutting down idle worker");
|
||||
let _ = tx.send(true);
|
||||
return Poll::Ready(());
|
||||
} else if graceful {
|
||||
info!("graceful worker shutdown; finishing {} connections", num);
|
||||
info!("Graceful worker shutdown; finishing {} connections", num);
|
||||
this.shutdown(false);
|
||||
|
||||
this.state = WorkerState::Shutdown(Shutdown {
|
||||
@@ -601,7 +513,7 @@ impl Future for ServerWorker {
|
||||
tx,
|
||||
});
|
||||
} else {
|
||||
info!("force shutdown worker, closing {} connections", num);
|
||||
info!("Force shutdown worker, closing {} connections", num);
|
||||
this.shutdown(true);
|
||||
|
||||
let _ = tx.send(false);
|
||||
@@ -625,8 +537,8 @@ impl Future for ServerWorker {
|
||||
let factory_id = restart.factory_id;
|
||||
let token = restart.token;
|
||||
|
||||
let (token_new, service) =
|
||||
ready!(restart.fut.as_mut().poll(cx)).unwrap_or_else(|_| {
|
||||
let (token_new, service) = ready!(restart.fut.as_mut().poll(cx))
|
||||
.unwrap_or_else(|_| {
|
||||
panic!(
|
||||
"Can not restart {:?} service",
|
||||
this.factories[factory_id].name(token)
|
||||
@@ -636,7 +548,7 @@ impl Future for ServerWorker {
|
||||
assert_eq!(token, token_new);
|
||||
|
||||
trace!(
|
||||
"service {:?} has been restarted",
|
||||
"Service {:?} has been restarted",
|
||||
this.factories[factory_id].name(token)
|
||||
);
|
||||
|
||||
@@ -647,7 +559,7 @@ impl Future for ServerWorker {
|
||||
}
|
||||
WorkerState::Shutdown(ref mut shutdown) => {
|
||||
// drop all pending connections in rx channel.
|
||||
while let Poll::Ready(Some(conn)) = this.conn_rx.poll_recv(cx) {
|
||||
while let Poll::Ready(Some(conn)) = Pin::new(&mut this.rx).poll_recv(cx) {
|
||||
// WorkerCounterGuard is needed as Accept thread has incremented counter.
|
||||
// It's guard's job to decrement the counter together with drop of Conn.
|
||||
let guard = this.counter.guard();
|
||||
@@ -683,7 +595,7 @@ impl Future for ServerWorker {
|
||||
match this.check_readiness(cx) {
|
||||
Ok(true) => {}
|
||||
Ok(false) => {
|
||||
trace!("worker is unavailable");
|
||||
trace!("Worker is unavailable");
|
||||
this.state = WorkerState::Unavailable;
|
||||
return self.poll(cx);
|
||||
}
|
||||
@@ -694,13 +606,10 @@ impl Future for ServerWorker {
|
||||
}
|
||||
|
||||
// handle incoming io stream
|
||||
match ready!(this.conn_rx.poll_recv(cx)) {
|
||||
match ready!(Pin::new(&mut this.rx).poll_recv(cx)) {
|
||||
Some(msg) => {
|
||||
let guard = this.counter.guard();
|
||||
let _ = this.services[msg.token]
|
||||
.service
|
||||
.call((guard, msg.io))
|
||||
.into_inner();
|
||||
let _ = this.services[msg.token].service.call((guard, msg.io));
|
||||
}
|
||||
None => return Poll::Ready(()),
|
||||
};
|
||||
@@ -708,17 +617,3 @@ impl Future for ServerWorker {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap_worker_services(services: Vec<(usize, usize, BoxedServerService)>) -> Vec<WorkerService> {
|
||||
services
|
||||
.into_iter()
|
||||
.fold(Vec::new(), |mut services, (idx, token, service)| {
|
||||
assert_eq!(token, services.len());
|
||||
services.push(WorkerService {
|
||||
factory_idx: idx,
|
||||
service,
|
||||
status: WorkerServiceStatus::Unavailable,
|
||||
});
|
||||
services
|
||||
})
|
||||
}
|
||||
|
@@ -1,21 +1,21 @@
|
||||
#![allow(clippy::let_underscore_future)]
|
||||
|
||||
use std::{
|
||||
net,
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
mpsc, Arc,
|
||||
},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::{mpsc, Arc};
|
||||
use std::{net, thread, time::Duration};
|
||||
|
||||
use actix_rt::{net::TcpStream, time::sleep};
|
||||
use actix_server::{Server, TestServer};
|
||||
use actix_server::Server;
|
||||
use actix_service::fn_service;
|
||||
use socket2::{Domain, Protocol, Socket, Type};
|
||||
|
||||
fn unused_addr() -> net::SocketAddr {
|
||||
TestServer::unused_addr()
|
||||
let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
|
||||
let socket =
|
||||
Socket::new(Domain::for_address(addr), Type::STREAM, Some(Protocol::TCP)).unwrap();
|
||||
socket.set_reuse_address(true).unwrap();
|
||||
socket.set_nonblocking(true).unwrap();
|
||||
socket.bind(&addr.into()).unwrap();
|
||||
socket.listen(32).unwrap();
|
||||
net::TcpListener::from(socket).local_addr().unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -28,24 +28,23 @@ fn test_bind() {
|
||||
let srv = Server::build()
|
||||
.workers(1)
|
||||
.disable_signals()
|
||||
.shutdown_timeout(3600)
|
||||
.bind("test", addr, move || {
|
||||
fn_service(|_| async { Ok::<_, ()>(()) })
|
||||
})?
|
||||
.run();
|
||||
|
||||
tx.send(srv.handle()).unwrap();
|
||||
let _ = tx.send((srv.handle(), actix_rt::System::current()));
|
||||
|
||||
srv.await
|
||||
})
|
||||
});
|
||||
|
||||
let srv = rx.recv().unwrap();
|
||||
let (srv, sys) = rx.recv().unwrap();
|
||||
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
|
||||
net::TcpStream::connect(addr).unwrap();
|
||||
assert!(net::TcpStream::connect(addr).is_ok());
|
||||
|
||||
let _ = srv.stop(true);
|
||||
sys.stop();
|
||||
h.join().unwrap().unwrap();
|
||||
}
|
||||
|
||||
@@ -53,66 +52,31 @@ fn test_bind() {
|
||||
fn test_listen() {
|
||||
let addr = unused_addr();
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let lst = net::TcpListener::bind(addr).unwrap();
|
||||
|
||||
let h = thread::spawn(move || {
|
||||
let lst = net::TcpListener::bind(addr)?;
|
||||
actix_rt::System::new().block_on(async {
|
||||
let srv = Server::build()
|
||||
.workers(1)
|
||||
.disable_signals()
|
||||
.shutdown_timeout(3600)
|
||||
.workers(1)
|
||||
.listen("test", lst, move || {
|
||||
fn_service(|_| async { Ok::<_, ()>(()) })
|
||||
})?
|
||||
.run();
|
||||
|
||||
tx.send(srv.handle()).unwrap();
|
||||
srv.await
|
||||
})
|
||||
});
|
||||
|
||||
let srv = rx.recv().unwrap();
|
||||
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
|
||||
net::TcpStream::connect(addr).unwrap();
|
||||
|
||||
let _ = srv.stop(true);
|
||||
h.join().unwrap().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_tokio_runtime() {
|
||||
let addr = unused_addr();
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
let h = thread::spawn(move || {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
rt.block_on(async {
|
||||
let srv = Server::build()
|
||||
.workers(1)
|
||||
.disable_signals()
|
||||
.bind("test", addr, move || {
|
||||
fn_service(|_| async { Ok::<_, ()>(()) })
|
||||
})?
|
||||
.run();
|
||||
|
||||
tx.send(srv.handle()).unwrap();
|
||||
let _ = tx.send((srv.handle(), actix_rt::System::current()));
|
||||
|
||||
srv.await
|
||||
})
|
||||
});
|
||||
|
||||
let srv = rx.recv().unwrap();
|
||||
let (srv, sys) = rx.recv().unwrap();
|
||||
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
assert!(net::TcpStream::connect(addr).is_ok());
|
||||
|
||||
let _ = srv.stop(true);
|
||||
sys.stop();
|
||||
h.join().unwrap().unwrap();
|
||||
}
|
||||
|
||||
@@ -188,9 +152,9 @@ fn test_start() {
|
||||
#[actix_rt::test]
|
||||
async fn test_max_concurrent_connections() {
|
||||
// Note:
|
||||
// A TCP listener would accept connects based on it's backlog setting.
|
||||
// A tcp listener would accept connects based on it's backlog setting.
|
||||
//
|
||||
// The limit test on the other hand is only for concurrent TCP stream limiting a work
|
||||
// The limit test on the other hand is only for concurrent tcp stream limiting a work
|
||||
// thread accept.
|
||||
|
||||
use tokio::io::AsyncWriteExt;
|
||||
@@ -256,7 +220,6 @@ async fn test_max_concurrent_connections() {
|
||||
h.join().unwrap().unwrap();
|
||||
}
|
||||
|
||||
// TODO: race-y failures detected due to integer underflow when calling Counter::total
|
||||
#[actix_rt::test]
|
||||
async fn test_service_restart() {
|
||||
use std::task::{Context, Poll};
|
||||
@@ -320,12 +283,12 @@ async fn test_service_restart() {
|
||||
.workers(1)
|
||||
.run();
|
||||
|
||||
let _ = tx.send(srv.handle());
|
||||
let _ = tx.send((srv.handle(), actix_rt::System::current()));
|
||||
srv.await
|
||||
})
|
||||
});
|
||||
|
||||
let srv = rx.recv().unwrap();
|
||||
let (srv, sys) = rx.recv().unwrap();
|
||||
|
||||
for _ in 0..5 {
|
||||
TcpStream::connect(addr1)
|
||||
@@ -348,6 +311,7 @@ async fn test_service_restart() {
|
||||
assert!(num2_clone.load(Ordering::SeqCst) > 5);
|
||||
|
||||
let _ = srv.stop(false);
|
||||
sys.stop();
|
||||
h.join().unwrap().unwrap();
|
||||
}
|
||||
|
||||
@@ -424,13 +388,13 @@ async fn worker_restart() {
|
||||
.workers(2)
|
||||
.run();
|
||||
|
||||
let _ = tx.send(srv.handle());
|
||||
let _ = tx.send((srv.handle(), actix_rt::System::current()));
|
||||
|
||||
srv.await
|
||||
})
|
||||
});
|
||||
|
||||
let srv = rx.recv().unwrap();
|
||||
let (srv, sys) = rx.recv().unwrap();
|
||||
|
||||
sleep(Duration::from_secs(3)).await;
|
||||
|
||||
@@ -488,50 +452,6 @@ async fn worker_restart() {
|
||||
stream.shutdown().await.unwrap();
|
||||
|
||||
let _ = srv.stop(false);
|
||||
sys.stop();
|
||||
h.join().unwrap().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_runtime_on_init() {
|
||||
use std::{thread::sleep, time::Duration};
|
||||
|
||||
let addr = unused_addr();
|
||||
let counter = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let mut srv = Server::build()
|
||||
.workers(2)
|
||||
.disable_signals()
|
||||
.bind("test", addr, {
|
||||
let counter = counter.clone();
|
||||
move || {
|
||||
counter.fetch_add(1, Ordering::SeqCst);
|
||||
fn_service(|_| async { Ok::<_, ()>(()) })
|
||||
}
|
||||
})
|
||||
.unwrap()
|
||||
.run();
|
||||
|
||||
fn is_send<T: Send>(_: &T) {}
|
||||
is_send(&srv);
|
||||
is_send(&srv.handle());
|
||||
|
||||
sleep(Duration::from_millis(1_000));
|
||||
assert_eq!(counter.load(Ordering::SeqCst), 0);
|
||||
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
rt.block_on(async move {
|
||||
let _ = futures_util::poll!(&mut srv);
|
||||
|
||||
// available after the first poll
|
||||
sleep(Duration::from_millis(500));
|
||||
assert_eq!(counter.load(Ordering::SeqCst), 2);
|
||||
|
||||
let _ = srv.handle().stop(true);
|
||||
srv.await
|
||||
})
|
||||
.unwrap();
|
||||
}
|
@@ -1,73 +0,0 @@
|
||||
use std::net;
|
||||
|
||||
use actix_rt::net::TcpStream;
|
||||
use actix_server::{Server, TestServer};
|
||||
use actix_service::fn_service;
|
||||
use bytes::BytesMut;
|
||||
use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};
|
||||
|
||||
macro_rules! await_timeout_ms {
|
||||
($fut:expr, $limit:expr) => {
|
||||
::actix_rt::time::timeout(::std::time::Duration::from_millis($limit), $fut)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
};
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn testing_server_echo() {
|
||||
let srv = TestServer::start(|| {
|
||||
fn_service(move |mut stream: TcpStream| async move {
|
||||
let mut size = 0;
|
||||
let mut buf = BytesMut::new();
|
||||
|
||||
match stream.read_buf(&mut buf).await {
|
||||
Ok(0) => return Err(()),
|
||||
|
||||
Ok(bytes_read) => {
|
||||
stream.write_all(&buf[size..]).await.unwrap();
|
||||
size += bytes_read;
|
||||
}
|
||||
|
||||
Err(_) => return Err(()),
|
||||
}
|
||||
|
||||
Ok((buf.freeze(), size))
|
||||
})
|
||||
});
|
||||
|
||||
let mut conn = srv.connect().unwrap();
|
||||
|
||||
await_timeout_ms!(conn.write_all(b"test"), 200);
|
||||
|
||||
let mut buf = Vec::new();
|
||||
await_timeout_ms!(conn.read_to_end(&mut buf), 200);
|
||||
|
||||
assert_eq!(&buf, b"test".as_ref());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn new_with_builder() {
|
||||
let alt_addr = TestServer::unused_addr();
|
||||
|
||||
let srv = TestServer::start_with_builder(
|
||||
Server::build()
|
||||
.bind("alt", alt_addr, || {
|
||||
fn_service(|_| async { Ok::<_, ()>(()) })
|
||||
})
|
||||
.unwrap(),
|
||||
|| {
|
||||
fn_service(|mut sock: TcpStream| async move {
|
||||
let mut buf = [0u8; 16];
|
||||
sock.read_exact(&mut buf).await
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
// connect to test server
|
||||
srv.connect().unwrap();
|
||||
|
||||
// connect to alt service defined in custom ServerBuilder
|
||||
TcpStream::from_std(net::TcpStream::connect(alt_addr).unwrap()).unwrap();
|
||||
}
|
@@ -1,191 +1,284 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2023-xx-xx
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.65.
|
||||
|
||||
## 2.0.2 - 2021-12-18
|
||||
|
||||
- Service types can now be `Send` and `'static` regardless of request, response, and config types, etc. [#397]
|
||||
|
||||
[#397]: https://github.com/actix/actix-net/pull/397
|
||||
|
||||
## 2.0.1 - 2021-10-11
|
||||
* Documentation fix.
|
||||
|
||||
- Documentation fix. [#388]
|
||||
|
||||
[#388]: https://github.com/actix/actix-net/pull/388
|
||||
|
||||
## 2.0.0 - 2021-04-16
|
||||
|
||||
- Removed pipeline and related structs/functions. [#335]
|
||||
* Removed pipeline and related structs/functions. [#335]
|
||||
|
||||
[#335]: https://github.com/actix/actix-net/pull/335
|
||||
|
||||
## 2.0.0-beta.5 - 2021-03-15
|
||||
|
||||
- Add default `Service` trait impl for `Rc<S: Service>` and `&S: Service`. [#288]
|
||||
- Add `boxed::rc_service` function for constructing `boxed::RcService` type [#290]
|
||||
## 2.0.0-beta.5 - 2021-03-15
|
||||
* Add default `Service` trait impl for `Rc<S: Service>` and `&S: Service`. [#288]
|
||||
* Add `boxed::rc_service` function for constructing `boxed::RcService` type [#290]
|
||||
|
||||
[#288]: https://github.com/actix/actix-net/pull/288
|
||||
[#290]: https://github.com/actix/actix-net/pull/290
|
||||
|
||||
## 2.0.0-beta.4 - 2021-02-04
|
||||
|
||||
- `Service::poll_ready` and `Service::call` receive `&self`. [#247]
|
||||
- `apply_fn` and `apply_fn_factory` now receive `Fn(Req, &Service)` function type. [#247]
|
||||
- `apply_cfg` and `apply_cfg_factory` now receive `Fn(Req, &Service)` function type. [#247]
|
||||
- `fn_service` and friends now receive `Fn(Req)` function type. [#247]
|
||||
## 2.0.0-beta.4 - 2021-02-04
|
||||
* `Service::poll_ready` and `Service::call` receive `&self`. [#247]
|
||||
* `apply_fn` and `apply_fn_factory` now receive `Fn(Req, &Service)` function type. [#247]
|
||||
* `apply_cfg` and `apply_cfg_factory` now receive `Fn(Req, &Service)` function type. [#247]
|
||||
* `fn_service` and friends now receive `Fn(Req)` function type. [#247]
|
||||
|
||||
[#247]: https://github.com/actix/actix-net/pull/247
|
||||
|
||||
## 2.0.0-beta.3 - 2021-01-09
|
||||
|
||||
- The `forward_ready!` macro converts errors. [#246]
|
||||
## 2.0.0-beta.3 - 2021-01-09
|
||||
* The `forward_ready!` macro converts errors. [#246]
|
||||
|
||||
[#246]: https://github.com/actix/actix-net/pull/246
|
||||
|
||||
## 2.0.0-beta.2 - 2021-01-03
|
||||
|
||||
- Remove redundant type parameter from `map_config`.
|
||||
## 2.0.0-beta.2 - 2021-01-03
|
||||
* Remove redundant type parameter from `map_config`.
|
||||
|
||||
|
||||
## 2.0.0-beta.1 - 2020-12-28
|
||||
|
||||
- `Service`, other traits, and many type signatures now take the the request type as a type parameter instead of an associated type. [#232]
|
||||
- Add `always_ready!` and `forward_ready!` macros. [#233]
|
||||
- Crate is now `no_std`. [#233]
|
||||
- Migrate pin projections to `pin-project-lite`. [#233]
|
||||
- Remove `AndThenApplyFn` and Pipeline `and_then_apply_fn`. Use the `.and_then(apply_fn(...))` construction. [#233]
|
||||
- Move non-vital methods to `ServiceExt` and `ServiceFactoryExt` extension traits. [#235]
|
||||
* `Service`, other traits, and many type signatures now take the the request type as a type
|
||||
parameter instead of an associated type. [#232]
|
||||
* Add `always_ready!` and `forward_ready!` macros. [#233]
|
||||
* Crate is now `no_std`. [#233]
|
||||
* Migrate pin projections to `pin-project-lite`. [#233]
|
||||
* Remove `AndThenApplyFn` and Pipeline `and_then_apply_fn`. Use the
|
||||
`.and_then(apply_fn(...))` construction. [#233]
|
||||
* Move non-vital methods to `ServiceExt` and `ServiceFactoryExt` extension traits. [#235]
|
||||
|
||||
[#232]: https://github.com/actix/actix-net/pull/232
|
||||
[#233]: https://github.com/actix/actix-net/pull/233
|
||||
[#235]: https://github.com/actix/actix-net/pull/235
|
||||
|
||||
|
||||
## 1.0.6 - 2020-08-09
|
||||
|
||||
- Removed unsound custom Cell implementation that allowed obtaining several mutable references to the same data, which is undefined behavior in Rust and could lead to violations of memory safety. External code could obtain several mutable references to the same data through service combinators. Attempts to acquire several mutable references to the same data will instead result in a panic.
|
||||
### Fixed
|
||||
|
||||
## 1.0.5 - 2020-01-16
|
||||
* Removed unsound custom Cell implementation that allowed obtaining several mutable references to
|
||||
the same data, which is undefined behavior in Rust and could lead to violations of memory safety. External code could obtain several mutable references to the same data through
|
||||
service combinators. Attempts to acquire several mutable references to the same data will instead
|
||||
result in a panic.
|
||||
|
||||
- Fixed unsoundness in .and_then()/.then() service combinators.
|
||||
## [1.0.5] - 2020-01-16
|
||||
|
||||
## 1.0.4 - 2020-01-15
|
||||
### Fixed
|
||||
|
||||
- Revert 1.0.3 change
|
||||
* Fixed unsoundness in .and_then()/.then() service combinators
|
||||
|
||||
## 1.0.3 - 2020-01-15
|
||||
## [1.0.4] - 2020-01-15
|
||||
|
||||
- Fixed unsoundness in `AndThenService` impl.
|
||||
### Fixed
|
||||
|
||||
## 1.0.2 - 2020-01-08
|
||||
* Revert 1.0.3 change
|
||||
|
||||
- Add `into_service` helper function.
|
||||
## [1.0.3] - 2020-01-15
|
||||
|
||||
## 1.0.1 - 2019-12-22
|
||||
### Fixed
|
||||
|
||||
- `map_config()` and `unit_config()` now accept `IntoServiceFactory` type.
|
||||
* Fixed unsoundness in `AndThenService` impl
|
||||
|
||||
## 1.0.0 - 2019-12-11
|
||||
## [1.0.2] - 2020-01-08
|
||||
|
||||
- Add Clone impl for Apply service
|
||||
### Added
|
||||
|
||||
## 1.0.0-alpha.4 - 2019-12-08
|
||||
* Add `into_service` helper function
|
||||
|
||||
- Renamed `service_fn` to `fn_service`
|
||||
- Renamed `factory_fn` to `fn_factory`
|
||||
- Renamed `factory_fn_cfg` to `fn_factory_with_config`
|
||||
|
||||
## 1.0.0-alpha.3 - 2019-12-06
|
||||
## [1.0.1] - 2019-12-22
|
||||
|
||||
- Add missing Clone impls
|
||||
- Restore `Transform::map_init_err()` combinator
|
||||
- Restore `Service/Factory::apply_fn()` in form of `Pipeline/Factory::and_then_apply_fn()`
|
||||
- Optimize service combinators and futures memory layout
|
||||
### Changed
|
||||
|
||||
## 1.0.0-alpha.2 - 2019-12-02
|
||||
* `map_config()` and `unit_config()` accepts `IntoServiceFactory` type
|
||||
|
||||
- Use owned config value for service factory
|
||||
- Renamed BoxedNewService/BoxedService to BoxServiceFactory/BoxService
|
||||
|
||||
## 1.0.0-alpha.1 - 2019-11-25
|
||||
## [1.0.0] - 2019-12-11
|
||||
|
||||
- Migrated to `std::future`
|
||||
- `NewService` renamed to `ServiceFactory`
|
||||
- Added `pipeline` and `pipeline_factory` function
|
||||
### Added
|
||||
|
||||
## 0.4.2 - 2019-08-27
|
||||
* Add Clone impl for Apply service
|
||||
|
||||
- Check service readiness for `new_apply_cfg` combinator
|
||||
|
||||
## 0.4.1 - 2019-06-06
|
||||
## [1.0.0-alpha.4] - 2019-12-08
|
||||
|
||||
- Add `new_apply_cfg` function
|
||||
### Changed
|
||||
|
||||
## 0.4.0 - 2019-05-12
|
||||
* Renamed `service_fn` to `fn_service`
|
||||
|
||||
- Add `NewService::map_config` and `NewService::unit_config` combinators.
|
||||
- Use associated type for `NewService` config.
|
||||
- Change `apply_cfg` function.
|
||||
- Renamed helper functions.
|
||||
* Renamed `factory_fn` to `fn_factory`
|
||||
|
||||
## 0.3.6 - 2019-04-07
|
||||
* Renamed `factory_fn_cfg` to `fn_factory_with_config`
|
||||
|
||||
- Poll boxed service call result immediately
|
||||
|
||||
## 0.3.5 - 2019-03-29
|
||||
## [1.0.0-alpha.3] - 2019-12-06
|
||||
|
||||
- Add `impl<S: Service> Service for Rc<RefCell<S>>`.
|
||||
### Changed
|
||||
|
||||
## 0.3.4 - 2019-03-12
|
||||
* Add missing Clone impls
|
||||
|
||||
- Add `Transform::from_err()` combinator
|
||||
- Add `apply_fn` helper
|
||||
- Add `apply_fn_factory` helper
|
||||
- Add `apply_transform` helper
|
||||
- Add `apply_cfg` helper
|
||||
* Restore `Transform::map_init_err()` combinator
|
||||
|
||||
## 0.3.3 - 2019-03-09
|
||||
* Restore `Service/Factory::apply_fn()` in form of `Pipeline/Factory::and_then_apply_fn()`
|
||||
|
||||
- Add `ApplyTransform` new service for transform and new service.
|
||||
- Add `NewService::apply_cfg()` combinator, allows to use nested `NewService` with different config parameter.
|
||||
- Revert IntoFuture change
|
||||
* Optimize service combinators and futures memory layout
|
||||
|
||||
## 0.3.2 - 2019-03-04
|
||||
|
||||
- Change `NewService::Future` and `Transform::Future` to the `IntoFuture` trait.
|
||||
- Export `AndThenTransform` type
|
||||
## [1.0.0-alpha.2] - 2019-12-02
|
||||
|
||||
## 0.3.1 - 2019-03-04
|
||||
### Changed
|
||||
|
||||
- Simplify Transform trait
|
||||
* Use owned config value for service factory
|
||||
|
||||
## 0.3.0 - 2019-03-02
|
||||
* Renamed BoxedNewService/BoxedService to BoxServiceFactory/BoxService
|
||||
|
||||
- Added boxed NewService and Service.
|
||||
- Added `Config` parameter to `NewService` trait.
|
||||
- Added `Config` parameter to `NewTransform` trait.
|
||||
|
||||
## 0.2.2 - 2019-02-19
|
||||
## [1.0.0-alpha.1] - 2019-11-25
|
||||
|
||||
- Added `NewService` impl for `Rc<S> where S: NewService`
|
||||
- Added `NewService` impl for `Arc<S> where S: NewService`
|
||||
### Changed
|
||||
|
||||
## 0.2.1 - 2019-02-03
|
||||
* Migraded to `std::future`
|
||||
|
||||
- Generalize `.apply` combinator with Transform trait
|
||||
* `NewService` renamed to `ServiceFactory`
|
||||
|
||||
## 0.2.0 - 2019-02-01
|
||||
* Added `pipeline` and `pipeline_factory` function
|
||||
|
||||
|
||||
## [0.4.2] - 2019-08-27
|
||||
|
||||
### Fixed
|
||||
|
||||
* Check service readiness for `new_apply_cfg` combinator
|
||||
|
||||
|
||||
## [0.4.1] - 2019-06-06
|
||||
|
||||
### Added
|
||||
|
||||
* Add `new_apply_cfg` function
|
||||
|
||||
## [0.4.0] - 2019-05-12
|
||||
|
||||
### Changed
|
||||
|
||||
* Use associated type for `NewService` config
|
||||
|
||||
* Change `apply_cfg` function
|
||||
|
||||
* Renamed helper functions
|
||||
|
||||
### Added
|
||||
|
||||
* Add `NewService::map_config` and `NewService::unit_config` combinators
|
||||
|
||||
|
||||
## [0.3.6] - 2019-04-07
|
||||
|
||||
### Changed
|
||||
|
||||
* Poll boxed service call result immediately
|
||||
|
||||
|
||||
## [0.3.5] - 2019-03-29
|
||||
|
||||
### Added
|
||||
|
||||
* Add `impl<S: Service> Service for Rc<RefCell<S>>`
|
||||
|
||||
|
||||
## [0.3.4] - 2019-03-12
|
||||
|
||||
### Added
|
||||
|
||||
* Add `Transform::from_err()` combinator
|
||||
|
||||
* Add `apply_fn` helper
|
||||
|
||||
* Add `apply_fn_factory` helper
|
||||
|
||||
* Add `apply_transform` helper
|
||||
|
||||
* Add `apply_cfg` helper
|
||||
|
||||
|
||||
## [0.3.3] - 2019-03-09
|
||||
|
||||
### Added
|
||||
|
||||
* Add `ApplyTransform` new service for transform and new service.
|
||||
|
||||
* Add `NewService::apply_cfg()` combinator, allows to use
|
||||
nested `NewService` with different config parameter.
|
||||
|
||||
### Changed
|
||||
|
||||
* Revert IntoFuture change
|
||||
|
||||
|
||||
## [0.3.2] - 2019-03-04
|
||||
|
||||
### Changed
|
||||
|
||||
* Change `NewService::Future` and `Transform::Future` to the `IntoFuture` trait.
|
||||
|
||||
* Export `AndThenTransform` type
|
||||
|
||||
|
||||
## [0.3.1] - 2019-03-04
|
||||
|
||||
### Changed
|
||||
|
||||
* Simplify Transform trait
|
||||
|
||||
|
||||
## [0.3.0] - 2019-03-02
|
||||
|
||||
## Added
|
||||
|
||||
* Added boxed NewService and Service.
|
||||
|
||||
## Changed
|
||||
|
||||
* Added `Config` parameter to `NewService` trait.
|
||||
|
||||
* Added `Config` parameter to `NewTransform` trait.
|
||||
|
||||
|
||||
## [0.2.2] - 2019-02-19
|
||||
|
||||
### Added
|
||||
|
||||
* Added `NewService` impl for `Rc<S> where S: NewService`
|
||||
|
||||
* Added `NewService` impl for `Arc<S> where S: NewService`
|
||||
|
||||
|
||||
## [0.2.1] - 2019-02-03
|
||||
|
||||
### Changed
|
||||
|
||||
* Generalize `.apply` combinator with Transform trait
|
||||
|
||||
|
||||
## [0.2.0] - 2019-02-01
|
||||
|
||||
### Changed
|
||||
|
||||
* Use associated type instead of generic for Service definition.
|
||||
|
||||
* Before:
|
||||
|
||||
- Use associated type instead of generic for Service definition.
|
||||
- Before:
|
||||
```rust
|
||||
impl Service<Request> for Client {
|
||||
type Response = Response;
|
||||
// ...
|
||||
}
|
||||
```
|
||||
- After:
|
||||
* After:
|
||||
|
||||
```rust
|
||||
impl Service for Client {
|
||||
type Request = Request;
|
||||
@@ -194,31 +287,51 @@
|
||||
}
|
||||
```
|
||||
|
||||
## 0.1.6 - 2019-01-24
|
||||
|
||||
- Use `FnMut` instead of `Fn` for .apply() and .map() combinators and `FnService` type
|
||||
- Change `.apply()` error semantic, new service's error is `From<Self::Error>`
|
||||
## [0.1.6] - 2019-01-24
|
||||
|
||||
## 0.1.5 - 2019-01-13
|
||||
### Changed
|
||||
|
||||
- Make `Out::Error` convertible from `T::Error` for apply combinator
|
||||
* Use `FnMut` instead of `Fn` for .apply() and .map() combinators and `FnService` type
|
||||
|
||||
## 0.1.4 - 2019-01-11
|
||||
* Change `.apply()` error semantic, new service's error is `From<Self::Error>`
|
||||
|
||||
- Use `FnMut` instead of `Fn` for `FnService`
|
||||
|
||||
## 0.1.3 - 2018-12-12
|
||||
## [0.1.5] - 2019-01-13
|
||||
|
||||
- Split service combinators to separate trait
|
||||
### Changed
|
||||
|
||||
## 0.1.2 - 2018-12-12
|
||||
* Make `Out::Error` convertable from `T::Error` for apply combinator
|
||||
|
||||
- Release future early for `.and_then()` and `.then()` combinators
|
||||
|
||||
## 0.1.1 - 2018-12-09
|
||||
## [0.1.4] - 2019-01-11
|
||||
|
||||
- Added Service impl for `Box<S: Service>`
|
||||
### Changed
|
||||
|
||||
## 0.1.0 - 2018-12-09
|
||||
* Use `FnMut` instead of `Fn` for `FnService`
|
||||
|
||||
- Initial import
|
||||
|
||||
## [0.1.3] - 2018-12-12
|
||||
|
||||
### Changed
|
||||
|
||||
* Split service combinators to separate trait
|
||||
|
||||
|
||||
## [0.1.2] - 2018-12-12
|
||||
|
||||
### Fixed
|
||||
|
||||
* Release future early for `.and_then()` and `.then()` combinators
|
||||
|
||||
|
||||
## [0.1.1] - 2018-12-09
|
||||
|
||||
### Added
|
||||
|
||||
* Added Service impl for Box<S: Service>
|
||||
|
||||
|
||||
## [0.1.0] - 2018-12-09
|
||||
|
||||
* Initial import
|
||||
|
@@ -1,24 +1,28 @@
|
||||
[package]
|
||||
name = "actix-service"
|
||||
version = "2.0.2"
|
||||
version = "2.0.1"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
"fakeshadow <24548779@qq.com>",
|
||||
]
|
||||
description = "Service trait and combinators for representing asynchronous request/response operations."
|
||||
keywords = ["network", "framework", "async", "futures", "service"]
|
||||
categories = ["network-programming", "asynchronous", "no-std"]
|
||||
repository = "https://github.com/actix/actix-net"
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
name = "actix_service"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
futures-core = { version = "0.3.17", default-features = false }
|
||||
futures-core = { version = "0.3.7", default-features = false }
|
||||
paste = "1"
|
||||
pin-project-lite = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "2"
|
||||
actix-utils = "3"
|
||||
futures-util = { version = "0.3.17", default-features = false }
|
||||
actix-rt = "2.0.0"
|
||||
actix-utils = "3.0.0"
|
||||
futures-util = { version = "0.3.7", default-features = false }
|
||||
|
@@ -3,10 +3,10 @@
|
||||
> Service trait and combinators for representing asynchronous request/response operations.
|
||||
|
||||
[](https://crates.io/crates/actix-service)
|
||||
[](https://docs.rs/actix-service/2.0.2)
|
||||
[](https://docs.rs/actix-service/2.0.1)
|
||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
||||

|
||||
[](https://deps.rs/crate/actix-service/2.0.2)
|
||||
[](https://deps.rs/crate/actix-service/2.0.1)
|
||||

|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
|
@@ -121,7 +121,12 @@ pub struct AndThenServiceFactory<A, B, Req>
|
||||
where
|
||||
A: ServiceFactory<Req>,
|
||||
A::Config: Clone,
|
||||
B: ServiceFactory<A::Response, Config = A::Config, Error = A::Error, InitError = A::InitError>,
|
||||
B: ServiceFactory<
|
||||
A::Response,
|
||||
Config = A::Config,
|
||||
Error = A::Error,
|
||||
InitError = A::InitError,
|
||||
>,
|
||||
{
|
||||
inner: Rc<(A, B)>,
|
||||
_phantom: PhantomData<Req>,
|
||||
@@ -131,7 +136,12 @@ impl<A, B, Req> AndThenServiceFactory<A, B, Req>
|
||||
where
|
||||
A: ServiceFactory<Req>,
|
||||
A::Config: Clone,
|
||||
B: ServiceFactory<A::Response, Config = A::Config, Error = A::Error, InitError = A::InitError>,
|
||||
B: ServiceFactory<
|
||||
A::Response,
|
||||
Config = A::Config,
|
||||
Error = A::Error,
|
||||
InitError = A::InitError,
|
||||
>,
|
||||
{
|
||||
/// Create new `AndThenFactory` combinator
|
||||
pub(crate) fn new(a: A, b: B) -> Self {
|
||||
@@ -146,7 +156,12 @@ impl<A, B, Req> ServiceFactory<Req> for AndThenServiceFactory<A, B, Req>
|
||||
where
|
||||
A: ServiceFactory<Req>,
|
||||
A::Config: Clone,
|
||||
B: ServiceFactory<A::Response, Config = A::Config, Error = A::Error, InitError = A::InitError>,
|
||||
B: ServiceFactory<
|
||||
A::Response,
|
||||
Config = A::Config,
|
||||
Error = A::Error,
|
||||
InitError = A::InitError,
|
||||
>,
|
||||
{
|
||||
type Response = B::Response;
|
||||
type Error = A::Error;
|
||||
@@ -169,7 +184,12 @@ impl<A, B, Req> Clone for AndThenServiceFactory<A, B, Req>
|
||||
where
|
||||
A: ServiceFactory<Req>,
|
||||
A::Config: Clone,
|
||||
B: ServiceFactory<A::Response, Config = A::Config, Error = A::Error, InitError = A::InitError>,
|
||||
B: ServiceFactory<
|
||||
A::Response,
|
||||
Config = A::Config,
|
||||
Error = A::Error,
|
||||
InitError = A::InitError,
|
||||
>,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
@@ -314,8 +334,9 @@ mod tests {
|
||||
async fn test_new_service() {
|
||||
let cnt = Rc::new(Cell::new(0));
|
||||
let cnt2 = cnt.clone();
|
||||
let new_srv = pipeline_factory(fn_factory(move || ready(Ok::<_, ()>(Srv1(cnt2.clone())))))
|
||||
.and_then(move || ready(Ok(Srv2(cnt.clone()))));
|
||||
let new_srv =
|
||||
pipeline_factory(fn_factory(move || ready(Ok::<_, ()>(Srv1(cnt2.clone())))))
|
||||
.and_then(move || ready(Ok(Srv2(cnt.clone()))));
|
||||
|
||||
let srv = new_srv.new_service(()).await.unwrap();
|
||||
let res = srv.call("srv1").await;
|
||||
|
@@ -51,7 +51,7 @@ where
|
||||
{
|
||||
service: S,
|
||||
wrap_fn: F,
|
||||
_phantom: PhantomData<fn(Req) -> (In, Res, Err)>,
|
||||
_phantom: PhantomData<(Req, In, Res, Err)>,
|
||||
}
|
||||
|
||||
impl<S, F, Fut, Req, In, Res, Err> Apply<S, F, Req, In, Res, Err>
|
||||
@@ -106,7 +106,7 @@ where
|
||||
pub struct ApplyFactory<SF, F, Req, In, Res, Err> {
|
||||
factory: SF,
|
||||
wrap_fn: F,
|
||||
_phantom: PhantomData<fn(Req) -> (In, Res, Err)>,
|
||||
_phantom: PhantomData<(Req, In, Res, Err)>,
|
||||
}
|
||||
|
||||
impl<SF, F, Fut, Req, In, Res, Err> ApplyFactory<SF, F, Req, In, Res, Err>
|
||||
@@ -140,7 +140,8 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<SF, F, Fut, Req, In, Res, Err> ServiceFactory<Req> for ApplyFactory<SF, F, Req, In, Res, Err>
|
||||
impl<SF, F, Fut, Req, In, Res, Err> ServiceFactory<Req>
|
||||
for ApplyFactory<SF, F, Req, In, Res, Err>
|
||||
where
|
||||
SF: ServiceFactory<In, Error = Err>,
|
||||
F: Fn(Req, &SF::Service) -> Fut + Clone,
|
||||
@@ -170,7 +171,7 @@ pin_project! {
|
||||
#[pin]
|
||||
fut: SF::Future,
|
||||
wrap_fn: Option<F>,
|
||||
_phantom: PhantomData<fn(Req) -> Res>,
|
||||
_phantom: PhantomData<(Req, Res)>,
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -198,7 +198,8 @@ pin_project! {
|
||||
}
|
||||
}
|
||||
|
||||
impl<SF, Req, F, Cfg, Fut, S> Future for ApplyConfigServiceFactoryResponse<SF, Req, F, Cfg, Fut, S>
|
||||
impl<SF, Req, F, Cfg, Fut, S> Future
|
||||
for ApplyConfigServiceFactoryResponse<SF, Req, F, Cfg, Fut, S>
|
||||
where
|
||||
SF: ServiceFactory<Req, Config = ()>,
|
||||
SF::InitError: From<SF::Error>,
|
||||
|
@@ -91,7 +91,8 @@ type Inner<C, Req, Res, Err, InitErr> = Box<
|
||||
>,
|
||||
>;
|
||||
|
||||
impl<C, Req, Res, Err, InitErr> ServiceFactory<Req> for BoxServiceFactory<C, Req, Res, Err, InitErr>
|
||||
impl<C, Req, Res, Err, InitErr> ServiceFactory<Req>
|
||||
for BoxServiceFactory<C, Req, Res, Err, InitErr>
|
||||
where
|
||||
Req: 'static,
|
||||
Res: 'static,
|
||||
|
@@ -3,7 +3,9 @@ use core::{future::Future, marker::PhantomData};
|
||||
use crate::{ok, IntoService, IntoServiceFactory, Ready, Service, ServiceFactory};
|
||||
|
||||
/// Create `ServiceFactory` for function that can act as a `Service`
|
||||
pub fn fn_service<F, Fut, Req, Res, Err, Cfg>(f: F) -> FnServiceFactory<F, Fut, Req, Res, Err, Cfg>
|
||||
pub fn fn_service<F, Fut, Req, Res, Err, Cfg>(
|
||||
f: F,
|
||||
) -> FnServiceFactory<F, Fut, Req, Res, Err, Cfg>
|
||||
where
|
||||
F: Fn(Req) -> Fut + Clone,
|
||||
Fut: Future<Output = Result<Res, Err>>,
|
||||
@@ -46,7 +48,9 @@ where
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub fn fn_factory<F, Cfg, Srv, Req, Fut, Err>(f: F) -> FnServiceNoConfig<F, Cfg, Srv, Req, Fut, Err>
|
||||
pub fn fn_factory<F, Cfg, Srv, Req, Fut, Err>(
|
||||
f: F,
|
||||
) -> FnServiceNoConfig<F, Cfg, Srv, Req, Fut, Err>
|
||||
where
|
||||
F: Fn() -> Fut,
|
||||
Fut: Future<Output = Result<Srv, Err>>,
|
||||
@@ -101,7 +105,7 @@ where
|
||||
Fut: Future<Output = Result<Res, Err>>,
|
||||
{
|
||||
f: F,
|
||||
_t: PhantomData<fn(Req)>,
|
||||
_t: PhantomData<Req>,
|
||||
}
|
||||
|
||||
impl<F, Fut, Req, Res, Err> FnService<F, Fut, Req, Res, Err>
|
||||
@@ -156,7 +160,7 @@ where
|
||||
Fut: Future<Output = Result<Res, Err>>,
|
||||
{
|
||||
f: F,
|
||||
_t: PhantomData<fn(Req, Cfg)>,
|
||||
_t: PhantomData<(Req, Cfg)>,
|
||||
}
|
||||
|
||||
impl<F, Fut, Req, Res, Err, Cfg> FnServiceFactory<F, Fut, Req, Res, Err, Cfg>
|
||||
@@ -233,7 +237,7 @@ where
|
||||
Srv: Service<Req>,
|
||||
{
|
||||
f: F,
|
||||
_t: PhantomData<fn(Cfg, Req) -> (Fut, Srv, Err)>,
|
||||
_t: PhantomData<(Fut, Cfg, Req, Srv, Err)>,
|
||||
}
|
||||
|
||||
impl<F, Fut, Cfg, Srv, Req, Err> FnServiceConfig<F, Fut, Cfg, Srv, Req, Err>
|
||||
@@ -261,7 +265,8 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, Fut, Cfg, Srv, Req, Err> ServiceFactory<Req> for FnServiceConfig<F, Fut, Cfg, Srv, Req, Err>
|
||||
impl<F, Fut, Cfg, Srv, Req, Err> ServiceFactory<Req>
|
||||
for FnServiceConfig<F, Fut, Cfg, Srv, Req, Err>
|
||||
where
|
||||
F: Fn(Cfg) -> Fut,
|
||||
Fut: Future<Output = Result<Srv, Err>>,
|
||||
@@ -288,7 +293,7 @@ where
|
||||
Fut: Future<Output = Result<Srv, Err>>,
|
||||
{
|
||||
f: F,
|
||||
_t: PhantomData<fn(Cfg, Req)>,
|
||||
_t: PhantomData<(Cfg, Req)>,
|
||||
}
|
||||
|
||||
impl<F, Cfg, Srv, Req, Fut, Err> FnServiceNoConfig<F, Cfg, Srv, Req, Fut, Err>
|
||||
@@ -386,40 +391,4 @@ mod tests {
|
||||
assert!(res.is_ok());
|
||||
assert_eq!(res.unwrap(), ("srv", 1));
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_auto_impl_send() {
|
||||
use alloc::rc::Rc;
|
||||
|
||||
use crate::{map_config, ServiceExt, ServiceFactoryExt};
|
||||
|
||||
let srv_1 = fn_service(|_: Rc<u8>| ok::<_, Rc<u8>>(Rc::new(0u8)));
|
||||
|
||||
let fac_1 = fn_factory_with_config(|_: Rc<u8>| {
|
||||
ok::<_, Rc<u8>>(fn_service(|_: Rc<u8>| ok::<_, Rc<u8>>(Rc::new(0u8))))
|
||||
});
|
||||
|
||||
let fac_2 =
|
||||
fn_factory(|| ok::<_, Rc<u8>>(fn_service(|_: Rc<u8>| ok::<_, Rc<u8>>(Rc::new(0u8)))));
|
||||
|
||||
fn is_send<T: Send + Sync + Clone>(_: &T) {}
|
||||
|
||||
is_send(&fac_1);
|
||||
is_send(&map_config(fac_1.clone(), |_: Rc<u8>| Rc::new(0u8)));
|
||||
is_send(&fac_1.clone().map_err(|_| Rc::new(0u8)));
|
||||
is_send(&fac_1.clone().map(|_| Rc::new(0u8)));
|
||||
is_send(&fac_1.clone().map_init_err(|_| Rc::new(0u8)));
|
||||
// `and_then` is always !Send
|
||||
// is_send(&fac_1.clone().and_then(fac_1.clone()));
|
||||
is_send(&fac_1.new_service(Rc::new(0u8)).await.unwrap());
|
||||
|
||||
is_send(&fac_2);
|
||||
is_send(&fac_2.new_service(Rc::new(0u8)).await.unwrap());
|
||||
|
||||
is_send(&srv_1);
|
||||
is_send(&ServiceExt::map(srv_1.clone(), |_| Rc::new(0u8)));
|
||||
is_send(&ServiceExt::map_err(srv_1.clone(), |_| Rc::new(0u8)));
|
||||
// `and_then` is always !Send
|
||||
// is_send(&ServiceExt::and_then(srv_1.clone(), srv_1.clone()));
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
//! See [`Service`] docs for information on this crate's foundational trait.
|
||||
|
||||
#![no_std]
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible, missing_docs)]
|
||||
#![deny(rust_2018_idioms, nonstandard_style, future_incompatible)]
|
||||
#![warn(missing_docs)]
|
||||
#![allow(clippy::type_complexity)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
@@ -33,16 +33,15 @@ mod then;
|
||||
mod transform;
|
||||
mod transform_err;
|
||||
|
||||
pub use self::apply::{apply_fn, apply_fn_factory};
|
||||
pub use self::apply_cfg::{apply_cfg, apply_cfg_factory};
|
||||
pub use self::ext::{ServiceExt, ServiceFactoryExt, TransformExt};
|
||||
pub use self::fn_service::{fn_factory, fn_factory_with_config, fn_service};
|
||||
pub use self::map_config::{map_config, unit_config};
|
||||
pub use self::transform::{apply, ApplyTransform, Transform};
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use self::ready::{err, ok, ready, Ready};
|
||||
pub use self::{
|
||||
apply::{apply_fn, apply_fn_factory},
|
||||
apply_cfg::{apply_cfg, apply_cfg_factory},
|
||||
ext::{ServiceExt, ServiceFactoryExt, TransformExt},
|
||||
fn_service::{fn_factory, fn_factory_with_config, fn_service},
|
||||
map_config::{map_config, unit_config},
|
||||
transform::{apply, ApplyTransform, Transform},
|
||||
};
|
||||
|
||||
/// An asynchronous operation from `Request` to a `Response`.
|
||||
///
|
||||
|
@@ -1,7 +1,7 @@
|
||||
/// An implementation of [`poll_ready`]() that always signals readiness.
|
||||
///
|
||||
/// This should only be used for basic leaf services that have no concept of un-readiness.
|
||||
/// For wrapper or other service types, use [`forward_ready!`] for simple cases or write a bespoke
|
||||
/// For wrapper or other serivice types, use [`forward_ready!`] for simple cases or write a bespoke
|
||||
/// `poll_ready` implementation.
|
||||
///
|
||||
/// [`poll_ready`]: crate::Service::poll_ready
|
||||
@@ -25,8 +25,6 @@
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`forward_ready!`]: crate::forward_ready
|
||||
#[macro_export]
|
||||
macro_rules! always_ready {
|
||||
() => {
|
||||
|
@@ -15,7 +15,7 @@ use super::{Service, ServiceFactory};
|
||||
pub struct Map<A, F, Req, Res> {
|
||||
service: A,
|
||||
f: F,
|
||||
_t: PhantomData<fn(Req) -> Res>,
|
||||
_t: PhantomData<(Req, Res)>,
|
||||
}
|
||||
|
||||
impl<A, F, Req, Res> Map<A, F, Req, Res> {
|
||||
@@ -97,7 +97,7 @@ where
|
||||
|
||||
match this.fut.poll(cx) {
|
||||
Poll::Ready(Ok(resp)) => Poll::Ready(Ok((this.f)(resp))),
|
||||
Poll::Ready(Err(err)) => Poll::Ready(Err(err)),
|
||||
Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
|
||||
Poll::Pending => Poll::Pending,
|
||||
}
|
||||
}
|
||||
@@ -107,7 +107,7 @@ where
|
||||
pub struct MapServiceFactory<A, F, Req, Res> {
|
||||
a: A,
|
||||
f: F,
|
||||
r: PhantomData<fn(Req) -> Res>,
|
||||
r: PhantomData<(Res, Req)>,
|
||||
}
|
||||
|
||||
impl<A, F, Req, Res> MapServiceFactory<A, F, Req, Res> {
|
||||
|
@@ -28,7 +28,7 @@ where
|
||||
pub struct MapConfig<SF, Req, F, Cfg> {
|
||||
factory: SF,
|
||||
cfg_mapper: F,
|
||||
e: PhantomData<fn(Cfg, Req)>,
|
||||
e: PhantomData<(Cfg, Req)>,
|
||||
}
|
||||
|
||||
impl<SF, Req, F, Cfg> MapConfig<SF, Req, F, Cfg> {
|
||||
@@ -82,7 +82,7 @@ where
|
||||
/// `unit_config()` config combinator
|
||||
pub struct UnitConfig<SF, Cfg, Req> {
|
||||
factory: SF,
|
||||
_phantom: PhantomData<fn(Cfg, Req)>,
|
||||
_phantom: PhantomData<(Cfg, Req)>,
|
||||
}
|
||||
|
||||
impl<SF, Cfg, Req> UnitConfig<SF, Cfg, Req>
|
||||
|
@@ -15,7 +15,7 @@ use super::{Service, ServiceFactory};
|
||||
pub struct MapErr<S, Req, F, E> {
|
||||
service: S,
|
||||
mapper: F,
|
||||
_t: PhantomData<fn(Req) -> E>,
|
||||
_t: PhantomData<(E, Req)>,
|
||||
}
|
||||
|
||||
impl<S, Req, F, E> MapErr<S, Req, F, E> {
|
||||
@@ -111,7 +111,7 @@ where
|
||||
{
|
||||
a: SF,
|
||||
f: F,
|
||||
e: PhantomData<fn(Req) -> E>,
|
||||
e: PhantomData<(E, Req)>,
|
||||
}
|
||||
|
||||
impl<SF, Req, F, E> MapErrServiceFactory<SF, Req, F, E>
|
||||
@@ -206,7 +206,8 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
err, ok, IntoServiceFactory, Ready, Service, ServiceExt, ServiceFactory, ServiceFactoryExt,
|
||||
err, ok, IntoServiceFactory, Ready, Service, ServiceExt, ServiceFactory,
|
||||
ServiceFactoryExt,
|
||||
};
|
||||
|
||||
struct Srv;
|
||||
|
@@ -13,7 +13,7 @@ use super::ServiceFactory;
|
||||
pub struct MapInitErr<A, F, Req, Err> {
|
||||
a: A,
|
||||
f: F,
|
||||
e: PhantomData<fn(Req) -> Err>,
|
||||
e: PhantomData<(Req, Err)>,
|
||||
}
|
||||
|
||||
impl<A, F, Req, Err> MapInitErr<A, F, Req, Err>
|
||||
|
@@ -6,14 +6,12 @@ use core::{
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
and_then::{AndThenService, AndThenServiceFactory},
|
||||
map::{Map, MapServiceFactory},
|
||||
map_err::{MapErr, MapErrServiceFactory},
|
||||
map_init_err::MapInitErr,
|
||||
then::{ThenService, ThenServiceFactory},
|
||||
IntoService, IntoServiceFactory, Service, ServiceFactory,
|
||||
};
|
||||
use crate::and_then::{AndThenService, AndThenServiceFactory};
|
||||
use crate::map::{Map, MapServiceFactory};
|
||||
use crate::map_err::{MapErr, MapErrServiceFactory};
|
||||
use crate::map_init_err::MapInitErr;
|
||||
use crate::then::{ThenService, ThenServiceFactory};
|
||||
use crate::{IntoService, IntoServiceFactory, Service, ServiceFactory};
|
||||
|
||||
/// Construct new pipeline with one service in pipeline chain.
|
||||
pub(crate) fn pipeline<I, S, Req>(service: I) -> Pipeline<S, Req>
|
||||
@@ -42,7 +40,7 @@ where
|
||||
/// Pipeline service - pipeline allows to compose multiple service into one service.
|
||||
pub(crate) struct Pipeline<S, Req> {
|
||||
service: S,
|
||||
_phantom: PhantomData<fn(Req)>,
|
||||
_phantom: PhantomData<Req>,
|
||||
}
|
||||
|
||||
impl<S, Req> Pipeline<S, Req>
|
||||
@@ -164,7 +162,7 @@ impl<S: Service<Req>, Req> Service<Req> for Pipeline<S, Req> {
|
||||
/// Pipeline factory
|
||||
pub(crate) struct PipelineFactory<SF, Req> {
|
||||
factory: SF,
|
||||
_phantom: PhantomData<fn(Req)>,
|
||||
_phantom: PhantomData<Req>,
|
||||
}
|
||||
|
||||
impl<SF, Req> PipelineFactory<SF, Req>
|
||||
@@ -254,7 +252,10 @@ where
|
||||
}
|
||||
|
||||
/// Map this service's error to a different error, returning a new service.
|
||||
pub fn map_err<F, E>(self, f: F) -> PipelineFactory<MapErrServiceFactory<SF, Req, F, E>, Req>
|
||||
pub fn map_err<F, E>(
|
||||
self,
|
||||
f: F,
|
||||
) -> PipelineFactory<MapErrServiceFactory<SF, Req, F, E>, Req>
|
||||
where
|
||||
Self: Sized,
|
||||
F: Fn(SF::Error) -> E + Clone,
|
||||
|
@@ -14,7 +14,7 @@ use super::Transform;
|
||||
pub struct TransformMapInitErr<T, S, Req, F, E> {
|
||||
transform: T,
|
||||
mapper: F,
|
||||
_phantom: PhantomData<fn(Req) -> (S, E)>,
|
||||
_phantom: PhantomData<(S, Req, E)>,
|
||||
}
|
||||
|
||||
impl<T, S, F, E, Req> TransformMapInitErr<T, S, Req, F, E> {
|
||||
|
@@ -1,188 +1,106 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2023-xx-xx
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.65.
|
||||
|
||||
## 3.0.4 - 2022-03-15
|
||||
|
||||
- Logs emitted now use the `tracing` crate with `log` compatibility. [#451]
|
||||
|
||||
[#451]: https://github.com/actix/actix-net/pull/451
|
||||
|
||||
## 3.0.3 - 2022-02-15
|
||||
|
||||
- No significant changes since `3.0.2`.
|
||||
|
||||
## 3.0.2 - 2022-01-28
|
||||
|
||||
- Expose `connect::Connection::new`. [#439]
|
||||
|
||||
[#439]: https://github.com/actix/actix-net/pull/439
|
||||
|
||||
## 3.0.1 - 2022-01-11
|
||||
|
||||
- No significant changes since `3.0.0`.
|
||||
|
||||
## 3.0.0 - 2021-12-26
|
||||
|
||||
- No significant changes since `3.0.0-rc.2`.
|
||||
|
||||
## 3.0.0-rc.2 - 2021-12-10
|
||||
|
||||
- Re-export `openssl::SslConnectorBuilder` in `connect::openssl::reexports`. [#429]
|
||||
|
||||
[#429]: https://github.com/actix/actix-net/pull/429
|
||||
|
||||
## 3.0.0-rc.1 - 2021-11-29
|
||||
|
||||
### Added
|
||||
|
||||
- Derive `Debug` for `connect::Connection`. [#422]
|
||||
- Implement `Display` for `accept::TlsError`. [#422]
|
||||
- Implement `Error` for `accept::TlsError` where both types also implement `Error`. [#422]
|
||||
- Implement `Default` for `connect::Resolver`. [#422]
|
||||
- Implement `Error` for `connect::ConnectError`. [#422]
|
||||
- Implement `Default` for `connect::tcp::{TcpConnector, TcpConnectorService}`. [#423]
|
||||
- Implement `Default` for `connect::ConnectorService`. [#423]
|
||||
|
||||
### Changed
|
||||
|
||||
- The crate's default features flags no longer include `uri`. [#422]
|
||||
- Useful re-exports from underlying TLS crates are exposed in a `reexports` modules in all acceptors and connectors.
|
||||
- Convert `connect::ResolverService` from enum to struct. [#422]
|
||||
- Make `ConnectAddrsIter` private. [#422]
|
||||
- Mark `tcp::{TcpConnector, TcpConnectorService}` structs `#[non_exhaustive]`. [#423]
|
||||
- Rename `accept::native_tls::{NativeTlsAcceptorService => AcceptorService}`. [#422]
|
||||
- Rename `connect::{Address => Host}` trait. [#422]
|
||||
- Rename method `connect::Connection::{host => hostname}`. [#422]
|
||||
- Rename struct `connect::{Connect => ConnectInfo}`. [#422]
|
||||
- Rename struct `connect::{ConnectService => ConnectorService}`. [#422]
|
||||
- Rename struct `connect::{ConnectServiceFactory => Connector}`. [#422]
|
||||
- Rename TLS acceptor service future types and hide from docs. [#422]
|
||||
- Unbox some service futures types. [#422]
|
||||
- Inline modules in `connect::tls` to `connect` module. [#422]
|
||||
|
||||
### Removed
|
||||
|
||||
- Remove `connect::{new_connector, new_connector_factory, default_connector, default_connector_factory}` methods. [#422]
|
||||
- Remove `connect::native_tls::Connector::service` method. [#422]
|
||||
- Remove redundant `connect::Connection::from_parts` method. [#422]
|
||||
|
||||
[#422]: https://github.com/actix/actix-net/pull/422
|
||||
[#423]: https://github.com/actix/actix-net/pull/423
|
||||
|
||||
## 3.0.0-beta.9 - 2021-11-22
|
||||
|
||||
- Add configurable timeout for accepting TLS connection. [#393]
|
||||
- Added `TlsError::Timeout` variant. [#393]
|
||||
- All TLS acceptor services now use `TlsError` for their error types. [#393]
|
||||
- Added `TlsError::into_service_error`. [#420]
|
||||
|
||||
[#393]: https://github.com/actix/actix-net/pull/393
|
||||
[#420]: https://github.com/actix/actix-net/pull/420
|
||||
|
||||
## 3.0.0-beta.8 - 2021-11-15
|
||||
|
||||
- Add `Connect::request` for getting a reference to the connection request. [#415]
|
||||
|
||||
[#415]: https://github.com/actix/actix-net/pull/415
|
||||
|
||||
## 3.0.0-beta.7 - 2021-10-20
|
||||
|
||||
- Add `webpki_roots_cert_store()` to get rustls compatible webpki roots cert store. [#401]
|
||||
- Alias `connect::ssl` to `connect::tls`. [#401]
|
||||
* Add `webpki_roots_cert_store()` to get rustls compatible webpki roots cert store. [#401]
|
||||
* Alias `connect::ssl` to `connect::tls`. [#401]
|
||||
|
||||
[#401]: https://github.com/actix/actix-net/pull/401
|
||||
|
||||
## 3.0.0-beta.6 - 2021-10-19
|
||||
|
||||
- Update `tokio-rustls` to `0.23` which uses `rustls` `0.20`. [#396]
|
||||
- Removed a re-export of `Session` from `rustls` as it no longer exist. [#396]
|
||||
- Minimum supported Rust version (MSRV) is now 1.52.
|
||||
## 3.0.0-beta.6 - 2021-10-19
|
||||
* Update `tokio-rustls` to `0.23` which uses `rustls` `0.20`. [#396]
|
||||
* Removed a re-export of `Session` from `rustls` as it no longer exist. [#396]
|
||||
* Minimum supported Rust version (MSRV) is now 1.52.
|
||||
|
||||
[#396]: https://github.com/actix/actix-net/pull/396
|
||||
|
||||
## 3.0.0-beta.5 - 2021-03-29
|
||||
|
||||
- Changed `connect::ssl::rustls::RustlsConnectorService` to return error when `DNSNameRef` generation failed instead of panic. [#296]
|
||||
- Remove `connect::ssl::openssl::OpensslConnectServiceFactory`. [#297]
|
||||
- Remove `connect::ssl::openssl::OpensslConnectService`. [#297]
|
||||
- Add `connect::ssl::native_tls` module for native tls support. [#295]
|
||||
- Rename `accept::{nativetls => native_tls}`. [#295]
|
||||
- Remove `connect::TcpConnectService` type. Service caller expecting a `TcpStream` should use `connect::ConnectService` instead and call `Connection<T, TcpStream>::into_parts`. [#299]
|
||||
## 3.0.0-beta.5 - 2021-03-29
|
||||
* Changed `connect::ssl::rustls::RustlsConnectorService` to return error when `DNSNameRef`
|
||||
generation failed instead of panic. [#296]
|
||||
* Remove `connect::ssl::openssl::OpensslConnectServiceFactory`. [#297]
|
||||
* Remove `connect::ssl::openssl::OpensslConnectService`. [#297]
|
||||
* Add `connect::ssl::native_tls` module for native tls support. [#295]
|
||||
* Rename `accept::{nativetls => native_tls}`. [#295]
|
||||
* Remove `connect::TcpConnectService` type. service caller expect a `TcpStream` should use
|
||||
`connect::ConnectService` instead and call `Connection<T, TcpStream>::into_parts`. [#299]
|
||||
|
||||
[#295]: https://github.com/actix/actix-net/pull/295
|
||||
[#296]: https://github.com/actix/actix-net/pull/296
|
||||
[#297]: https://github.com/actix/actix-net/pull/297
|
||||
[#299]: https://github.com/actix/actix-net/pull/299
|
||||
|
||||
## 3.0.0-beta.4 - 2021-02-24
|
||||
|
||||
- Rename `accept::openssl::{SslStream => TlsStream}`.
|
||||
- Add `connect::Connect::set_local_addr` to attach local `IpAddr`. [#282]
|
||||
- `connector::TcpConnector` service will try to bind to local_addr of `IpAddr` when given. [#282]
|
||||
## 3.0.0-beta.4 - 2021-02-24
|
||||
* Rename `accept::openssl::{SslStream => TlsStream}`.
|
||||
* Add `connect::Connect::set_local_addr` to attach local `IpAddr`. [#282]
|
||||
* `connector::TcpConnector` service will try to bind to local_addr of `IpAddr` when given. [#282]
|
||||
|
||||
[#282]: https://github.com/actix/actix-net/pull/282
|
||||
|
||||
## 3.0.0-beta.3 - 2021-02-06
|
||||
|
||||
- Remove `trust-dns-proto` and `trust-dns-resolver`. [#248]
|
||||
- Use `std::net::ToSocketAddrs` as simple and basic default resolver. [#248]
|
||||
- Add `Resolve` trait for custom DNS resolvers. [#248]
|
||||
- Add `Resolver::new_custom` function to construct custom resolvers. [#248]
|
||||
- Export `webpki_roots::TLS_SERVER_ROOTS` in `actix_tls::connect` mod and remove the export from `actix_tls::accept` [#248]
|
||||
- Remove `ConnectTakeAddrsIter`. `Connect::take_addrs` now returns `ConnectAddrsIter<'static>` as owned iterator. [#248]
|
||||
- Rename `Address::{host => hostname}` to more accurately describe which URL segment is returned.
|
||||
- Update `actix-rt` to `2.0.0`. [#273]
|
||||
## 3.0.0-beta.3 - 2021-02-06
|
||||
* Remove `trust-dns-proto` and `trust-dns-resolver`. [#248]
|
||||
* Use `std::net::ToSocketAddrs` as simple and basic default resolver. [#248]
|
||||
* Add `Resolve` trait for custom DNS resolvers. [#248]
|
||||
* Add `Resolver::new_custom` function to construct custom resolvers. [#248]
|
||||
* Export `webpki_roots::TLS_SERVER_ROOTS` in `actix_tls::connect` mod and remove
|
||||
the export from `actix_tls::accept` [#248]
|
||||
* Remove `ConnectTakeAddrsIter`. `Connect::take_addrs` now returns `ConnectAddrsIter<'static>`
|
||||
as owned iterator. [#248]
|
||||
* Rename `Address::{host => hostname}` to more accurately describe which URL segment is returned.
|
||||
* Update `actix-rt` to `2.0.0`. [#273]
|
||||
|
||||
[#248]: https://github.com/actix/actix-net/pull/248
|
||||
[#273]: https://github.com/actix/actix-net/pull/273
|
||||
|
||||
## 3.0.0-beta.2 - 2022-xx-xx
|
||||
|
||||
- Depend on stable trust-dns packages. [#204]
|
||||
## 3.0.0-beta.2 - 2021-xx-xx
|
||||
* Depend on stable trust-dns packages. [#204]
|
||||
|
||||
[#204]: https://github.com/actix/actix-net/pull/204
|
||||
|
||||
## 3.0.0-beta.1 - 2020-12-29
|
||||
|
||||
- Move acceptors under `accept` module. [#238]
|
||||
- Merge `actix-connect` crate under `connect` module. [#238]
|
||||
- Add feature flags to enable acceptors and/or connectors individually. [#238]
|
||||
## 3.0.0-beta.1 - 2020-12-29
|
||||
* Move acceptors under `accept` module. [#238]
|
||||
* Merge `actix-connect` crate under `connect` module. [#238]
|
||||
* Add feature flags to enable acceptors and/or connectors individually. [#238]
|
||||
|
||||
[#238]: https://github.com/actix/actix-net/pull/238
|
||||
|
||||
## 2.0.0 - 2020-09-03
|
||||
|
||||
- `nativetls::NativeTlsAcceptor` is renamed to `nativetls::Acceptor`.
|
||||
- Where possible, "SSL" terminology is replaced with "TLS".
|
||||
- `SslError` is renamed to `TlsError`.
|
||||
- `TlsError::Ssl` enum variant is renamed to `TlsError::Tls`.
|
||||
- `max_concurrent_ssl_connect` is renamed to `max_concurrent_tls_connect`.
|
||||
## 2.0.0 - 2020-09-03
|
||||
* `nativetls::NativeTlsAcceptor` is renamed to `nativetls::Acceptor`.
|
||||
* Where possible, "SSL" terminology is replaced with "TLS".
|
||||
* `SslError` is renamed to `TlsError`.
|
||||
* `TlsError::Ssl` enum variant is renamed to `TlsError::Tls`.
|
||||
* `max_concurrent_ssl_connect` is renamed to `max_concurrent_tls_connect`.
|
||||
|
||||
|
||||
## 2.0.0-alpha.2 - 2020-08-17
|
||||
* Update `rustls` dependency to 0.18
|
||||
* Update `tokio-rustls` dependency to 0.14
|
||||
* Update `webpki-roots` dependency to 0.20
|
||||
|
||||
- Update `rustls` dependency to 0.18
|
||||
- Update `tokio-rustls` dependency to 0.14
|
||||
- Update `webpki-roots` dependency to 0.20
|
||||
|
||||
## [2.0.0-alpha.1] - 2020-03-03
|
||||
* Update `rustls` dependency to 0.17
|
||||
* Update `tokio-rustls` dependency to 0.13
|
||||
* Update `webpki-roots` dependency to 0.19
|
||||
|
||||
- Update `rustls` dependency to 0.17
|
||||
- Update `tokio-rustls` dependency to 0.13
|
||||
- Update `webpki-roots` dependency to 0.19
|
||||
|
||||
## [1.0.0] - 2019-12-11
|
||||
* 1.0.0 release
|
||||
|
||||
- 1.0.0 release
|
||||
|
||||
## [1.0.0-alpha.3] - 2019-12-07
|
||||
* Migrate to tokio 0.2
|
||||
* Enable rustls acceptor service
|
||||
* Enable native-tls acceptor service
|
||||
|
||||
- Migrate to tokio 0.2
|
||||
- Enable rustls acceptor service
|
||||
- Enable native-tls acceptor service
|
||||
|
||||
## [1.0.0-alpha.1] - 2019-12-02
|
||||
|
||||
- Split openssl acceptor from actix-server package
|
||||
* Split openssl acceptor from actix-server package
|
||||
|
@@ -1,24 +1,23 @@
|
||||
[package]
|
||||
name = "actix-tls"
|
||||
version = "3.0.4"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
]
|
||||
version = "3.0.0-beta.7"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "TLS acceptor and connector services for Actix ecosystem"
|
||||
keywords = ["network", "tls", "ssl", "async", "transport"]
|
||||
repository = "https://github.com/actix/actix-net.git"
|
||||
categories = ["network-programming", "asynchronous", "cryptography"]
|
||||
categories = ["network-programming", "asynchronous"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
edition = "2018"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
features = ["openssl", "rustls", "native-tls", "accept", "connect", "uri"]
|
||||
|
||||
[lib]
|
||||
name = "actix_tls"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = ["accept", "connect"]
|
||||
default = ["accept", "connect", "uri"]
|
||||
|
||||
# enable acceptor services
|
||||
accept = []
|
||||
@@ -39,22 +38,19 @@ native-tls = ["tokio-native-tls"]
|
||||
uri = ["http"]
|
||||
|
||||
[dependencies]
|
||||
actix-rt = { version = "2.2", default-features = false }
|
||||
actix-service = "2"
|
||||
actix-utils = "3"
|
||||
actix-codec = "0.4.0"
|
||||
actix-rt = { version = "2.2.0", default-features = false }
|
||||
actix-service = "2.0.0"
|
||||
actix-utils = "3.0.0"
|
||||
|
||||
derive_more = "0.99.5"
|
||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||
impl-more = "0.1"
|
||||
pin-project-lite = "0.2.7"
|
||||
tokio = "1.23.1"
|
||||
tokio-util = "0.7"
|
||||
tracing = { version = "0.1.30", default-features = false, features = ["log"] }
|
||||
|
||||
# uri
|
||||
http = { version = "0.2.3", optional = true }
|
||||
log = "0.4"
|
||||
tokio-util = { version = "0.6.3", default-features = false }
|
||||
|
||||
# openssl
|
||||
tls-openssl = { package = "openssl", version = "0.10.48", optional = true }
|
||||
tls-openssl = { package = "openssl", version = "0.10.9", optional = true }
|
||||
tokio-openssl = { version = "0.6", optional = true }
|
||||
|
||||
# rustls
|
||||
@@ -65,18 +61,15 @@ webpki-roots = { version = "0.22", optional = true }
|
||||
tokio-native-tls = { version = "0.3", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-codec = "0.5"
|
||||
actix-rt = "2.2"
|
||||
actix-server = "2"
|
||||
actix-rt = "2.2.0"
|
||||
actix-server = "2.0.0-beta.8"
|
||||
bytes = "1"
|
||||
env_logger = "0.10"
|
||||
futures-util = { version = "0.3.17", default-features = false, features = ["sink"] }
|
||||
env_logger = "0.9"
|
||||
futures-util = { version = "0.3.7", default-features = false, features = ["sink"] }
|
||||
log = "0.4"
|
||||
rcgen = "0.10"
|
||||
rustls-pemfile = "1"
|
||||
tokio-rustls = { version = "0.23", features = ["dangerous_configuration"] }
|
||||
trust-dns-resolver = "0.22"
|
||||
rustls-pemfile = "0.2.1"
|
||||
trust-dns-resolver = "0.20.0"
|
||||
|
||||
[[example]]
|
||||
name = "accept-rustls"
|
||||
name = "tcp-rustls"
|
||||
required-features = ["accept", "rustls"]
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//! No-Op TLS Acceptor Server
|
||||
//! TLS Acceptor Server
|
||||
//!
|
||||
//! Using either HTTPie (`http`) or cURL:
|
||||
//!
|
||||
@@ -15,12 +15,12 @@
|
||||
//! http --verify=false https://127.0.0.1:8443
|
||||
//! ```
|
||||
|
||||
#[rustfmt::skip]
|
||||
// this `use` is only exists because of how we have organised the crate
|
||||
// it is not necessary for your actual code; you should import from `rustls` directly
|
||||
// this use only exists because of how we have organised the crate
|
||||
// it is not necessary for your actual code
|
||||
use tokio_rustls::rustls;
|
||||
|
||||
use std::{
|
||||
env,
|
||||
fs::File,
|
||||
io::{self, BufReader},
|
||||
sync::{
|
||||
@@ -34,13 +34,14 @@ use actix_server::Server;
|
||||
use actix_service::ServiceFactoryExt as _;
|
||||
use actix_tls::accept::rustls::{Acceptor as RustlsAcceptor, TlsStream};
|
||||
use futures_util::future::ok;
|
||||
use log::info;
|
||||
use rustls::{server::ServerConfig, Certificate, PrivateKey};
|
||||
use rustls_pemfile::{certs, rsa_private_keys};
|
||||
use tracing::info;
|
||||
|
||||
#[actix_rt::main]
|
||||
async fn main() -> io::Result<()> {
|
||||
env_logger::init_from_env(env_logger::Env::default().default_filter_or("info"));
|
||||
env::set_var("RUST_LOG", "info");
|
||||
env_logger::init();
|
||||
|
||||
// Load TLS key and cert files
|
||||
let cert_file = &mut BufReader::new(File::open("./examples/cert.pem").unwrap());
|
@@ -1,11 +1,11 @@
|
||||
//! TLS connection acceptor services.
|
||||
//! TLS acceptor services for Actix ecosystem.
|
||||
//!
|
||||
//! ## Crate Features
|
||||
//! * `openssl` - TLS acceptor using the `openssl` crate.
|
||||
//! * `rustls` - TLS acceptor using the `rustls` crate.
|
||||
//! * `native-tls` - TLS acceptor using the `native-tls` crate.
|
||||
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
error::Error,
|
||||
fmt,
|
||||
sync::atomic::{AtomicUsize, Ordering},
|
||||
};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use actix_utils::counter::Counter;
|
||||
|
||||
@@ -20,10 +20,6 @@ pub mod native_tls;
|
||||
|
||||
pub(crate) static MAX_CONN: AtomicUsize = AtomicUsize::new(256);
|
||||
|
||||
#[cfg(any(feature = "openssl", feature = "rustls", feature = "native-tls"))]
|
||||
pub(crate) const DEFAULT_TLS_HANDSHAKE_TIMEOUT: std::time::Duration =
|
||||
std::time::Duration::from_secs(3);
|
||||
|
||||
thread_local! {
|
||||
static MAX_CONN_COUNTER: Counter = Counter::new(MAX_CONN.load(Ordering::Relaxed));
|
||||
}
|
||||
@@ -38,74 +34,9 @@ pub fn max_concurrent_tls_connect(num: usize) {
|
||||
MAX_CONN.store(num, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// TLS handshake error, TLS timeout, or inner service error.
|
||||
///
|
||||
/// All TLS acceptors from this crate will return the `SvcErr` type parameter as [`Infallible`],
|
||||
/// which can be cast to your own service type, inferred or otherwise, using [`into_service_error`].
|
||||
///
|
||||
/// [`into_service_error`]: Self::into_service_error
|
||||
/// TLS error combined with service error.
|
||||
#[derive(Debug)]
|
||||
pub enum TlsError<TlsErr, SvcErr> {
|
||||
/// TLS handshake has timed-out.
|
||||
Timeout,
|
||||
|
||||
/// Wraps TLS service errors.
|
||||
Tls(TlsErr),
|
||||
|
||||
/// Wraps service errors.
|
||||
Service(SvcErr),
|
||||
}
|
||||
|
||||
impl<TlsErr> TlsError<TlsErr, Infallible> {
|
||||
/// Casts the infallible service error type returned from acceptors into caller's type.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use std::convert::Infallible;
|
||||
/// # use actix_tls::accept::TlsError;
|
||||
/// let a: TlsError<u32, Infallible> = TlsError::Tls(42);
|
||||
/// let _b: TlsError<u32, u64> = a.into_service_error();
|
||||
/// ```
|
||||
pub fn into_service_error<SvcErr>(self) -> TlsError<TlsErr, SvcErr> {
|
||||
match self {
|
||||
Self::Timeout => TlsError::Timeout,
|
||||
Self::Tls(err) => TlsError::Tls(err),
|
||||
Self::Service(err) => match err {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<TlsErr, SvcErr> fmt::Display for TlsError<TlsErr, SvcErr> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Timeout => f.write_str("TLS handshake has timed-out"),
|
||||
Self::Tls(_) => f.write_str("TLS handshake error"),
|
||||
Self::Service(_) => f.write_str("Service error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<TlsErr, SvcErr> Error for TlsError<TlsErr, SvcErr>
|
||||
where
|
||||
TlsErr: Error + 'static,
|
||||
SvcErr: Error + 'static,
|
||||
{
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
match self {
|
||||
TlsError::Tls(err) => Some(err),
|
||||
TlsError::Service(err) => Some(err),
|
||||
TlsError::Timeout => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn tls_service_error_inference() {
|
||||
let a: TlsError<u32, Infallible> = TlsError::Tls(42);
|
||||
let _b: TlsError<u32, u64> = a.into_service_error();
|
||||
}
|
||||
pub enum TlsError<E1, E2> {
|
||||
Tls(E1),
|
||||
Service(E2),
|
||||
}
|
||||
|
@@ -1,43 +1,45 @@
|
||||
//! `native-tls` based TLS connection acceptor service.
|
||||
//!
|
||||
//! See [`Acceptor`] for main service factory docs.
|
||||
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
io::{self, IoSlice},
|
||||
ops::{Deref, DerefMut},
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use actix_rt::{
|
||||
net::{ActixStream, Ready},
|
||||
time::timeout,
|
||||
};
|
||||
use actix_codec::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
use actix_rt::net::{ActixStream, Ready};
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use actix_utils::{
|
||||
counter::Counter,
|
||||
future::{ready, Ready as FutReady},
|
||||
};
|
||||
use actix_utils::counter::Counter;
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
use tokio_native_tls::{native_tls::Error, TlsAcceptor};
|
||||
|
||||
use super::{TlsError, DEFAULT_TLS_HANDSHAKE_TIMEOUT, MAX_CONN_COUNTER};
|
||||
pub use tokio_native_tls::native_tls::Error;
|
||||
pub use tokio_native_tls::TlsAcceptor;
|
||||
|
||||
pub mod reexports {
|
||||
//! Re-exports from `native-tls` that are useful for acceptors.
|
||||
use super::MAX_CONN_COUNTER;
|
||||
|
||||
pub use tokio_native_tls::{native_tls::Error, TlsAcceptor};
|
||||
/// Wrapper type for `tokio_native_tls::TlsStream` in order to impl `ActixStream` trait.
|
||||
pub struct TlsStream<T>(tokio_native_tls::TlsStream<T>);
|
||||
|
||||
impl<T> From<tokio_native_tls::TlsStream<T>> for TlsStream<T> {
|
||||
fn from(stream: tokio_native_tls::TlsStream<T>) -> Self {
|
||||
Self(stream)
|
||||
}
|
||||
}
|
||||
|
||||
/// Wraps a `native-tls` based async TLS stream in order to implement [`ActixStream`].
|
||||
pub struct TlsStream<IO>(tokio_native_tls::TlsStream<IO>);
|
||||
impl<T: ActixStream> Deref for TlsStream<T> {
|
||||
type Target = tokio_native_tls::TlsStream<T>;
|
||||
|
||||
impl_more::impl_from!(<IO> in tokio_native_tls::TlsStream<IO> => TlsStream<IO>);
|
||||
impl_more::impl_deref_and_mut!(<IO> in TlsStream<IO> => tokio_native_tls::TlsStream<IO>);
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<IO: ActixStream> AsyncRead for TlsStream<IO> {
|
||||
impl<T: ActixStream> DerefMut for TlsStream<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ActixStream> AsyncRead for TlsStream<T> {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
@@ -47,7 +49,7 @@ impl<IO: ActixStream> AsyncRead for TlsStream<IO> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<IO: ActixStream> AsyncWrite for TlsStream<IO> {
|
||||
impl<T: ActixStream> AsyncWrite for TlsStream<T> {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
@@ -73,41 +75,32 @@ impl<IO: ActixStream> AsyncWrite for TlsStream<IO> {
|
||||
}
|
||||
|
||||
fn is_write_vectored(&self) -> bool {
|
||||
(**self).is_write_vectored()
|
||||
(&**self).is_write_vectored()
|
||||
}
|
||||
}
|
||||
|
||||
impl<IO: ActixStream> ActixStream for TlsStream<IO> {
|
||||
impl<T: ActixStream> ActixStream for TlsStream<T> {
|
||||
fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
|
||||
IO::poll_read_ready((**self).get_ref().get_ref().get_ref(), cx)
|
||||
T::poll_read_ready((&**self).get_ref().get_ref().get_ref(), cx)
|
||||
}
|
||||
|
||||
fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
|
||||
IO::poll_write_ready((**self).get_ref().get_ref().get_ref(), cx)
|
||||
T::poll_write_ready((&**self).get_ref().get_ref().get_ref(), cx)
|
||||
}
|
||||
}
|
||||
|
||||
/// Accept TLS connections via the `native-tls` crate.
|
||||
/// Accept TLS connections via `native-tls` package.
|
||||
///
|
||||
/// `native-tls` feature enables this `Acceptor` type.
|
||||
pub struct Acceptor {
|
||||
acceptor: TlsAcceptor,
|
||||
handshake_timeout: Duration,
|
||||
}
|
||||
|
||||
impl Acceptor {
|
||||
/// Constructs `native-tls` based acceptor service factory.
|
||||
/// Create `native-tls` based `Acceptor` service factory.
|
||||
#[inline]
|
||||
pub fn new(acceptor: TlsAcceptor) -> Self {
|
||||
Acceptor {
|
||||
acceptor,
|
||||
handshake_timeout: DEFAULT_TLS_HANDSHAKE_TIMEOUT,
|
||||
}
|
||||
}
|
||||
|
||||
/// Limit the amount of time that the acceptor will wait for a TLS handshake to complete.
|
||||
///
|
||||
/// Default timeout is 3 seconds.
|
||||
pub fn set_handshake_timeout(&mut self, handshake_timeout: Duration) -> &mut Self {
|
||||
self.handshake_timeout = handshake_timeout;
|
||||
self
|
||||
Acceptor { acceptor }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,43 +109,39 @@ impl Clone for Acceptor {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
acceptor: self.acceptor.clone(),
|
||||
handshake_timeout: self.handshake_timeout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<IO: ActixStream + 'static> ServiceFactory<IO> for Acceptor {
|
||||
type Response = TlsStream<IO>;
|
||||
type Error = TlsError<Error, Infallible>;
|
||||
impl<T: ActixStream + 'static> ServiceFactory<T> for Acceptor {
|
||||
type Response = TlsStream<T>;
|
||||
type Error = Error;
|
||||
type Config = ();
|
||||
type Service = AcceptorService;
|
||||
|
||||
type Service = NativeTlsAcceptorService;
|
||||
type InitError = ();
|
||||
type Future = FutReady<Result<Self::Service, Self::InitError>>;
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
let res = MAX_CONN_COUNTER.with(|conns| {
|
||||
Ok(AcceptorService {
|
||||
Ok(NativeTlsAcceptorService {
|
||||
acceptor: self.acceptor.clone(),
|
||||
conns: conns.clone(),
|
||||
handshake_timeout: self.handshake_timeout,
|
||||
})
|
||||
});
|
||||
|
||||
ready(res)
|
||||
Box::pin(async { res })
|
||||
}
|
||||
}
|
||||
|
||||
/// Native-TLS based acceptor service.
|
||||
pub struct AcceptorService {
|
||||
pub struct NativeTlsAcceptorService {
|
||||
acceptor: TlsAcceptor,
|
||||
conns: Counter,
|
||||
handshake_timeout: Duration,
|
||||
}
|
||||
|
||||
impl<IO: ActixStream + 'static> Service<IO> for AcceptorService {
|
||||
type Response = TlsStream<IO>;
|
||||
type Error = TlsError<Error, Infallible>;
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||
impl<T: ActixStream + 'static> Service<T> for NativeTlsAcceptorService {
|
||||
type Response = TlsStream<T>;
|
||||
type Error = Error;
|
||||
type Future = LocalBoxFuture<'static, Result<TlsStream<T>, Error>>;
|
||||
|
||||
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
if self.conns.available(cx) {
|
||||
@@ -162,21 +151,13 @@ impl<IO: ActixStream + 'static> Service<IO> for AcceptorService {
|
||||
}
|
||||
}
|
||||
|
||||
fn call(&self, io: IO) -> Self::Future {
|
||||
fn call(&self, io: T) -> Self::Future {
|
||||
let guard = self.conns.get();
|
||||
let acceptor = self.acceptor.clone();
|
||||
|
||||
let dur = self.handshake_timeout;
|
||||
|
||||
Box::pin(async move {
|
||||
match timeout(dur, acceptor.accept(io)).await {
|
||||
Ok(Ok(io)) => {
|
||||
drop(guard);
|
||||
Ok(TlsStream(io))
|
||||
}
|
||||
Ok(Err(err)) => Err(TlsError::Tls(err)),
|
||||
Err(_timeout) => Err(TlsError::Timeout),
|
||||
}
|
||||
let io = acceptor.accept(io).await;
|
||||
drop(guard);
|
||||
io.map(Into::into)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -1,46 +1,47 @@
|
||||
//! `openssl` based TLS acceptor service.
|
||||
//!
|
||||
//! See [`Acceptor`] for main service factory docs.
|
||||
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
future::Future,
|
||||
io::{self, IoSlice},
|
||||
ops::{Deref, DerefMut},
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use actix_rt::{
|
||||
net::{ActixStream, Ready},
|
||||
time::{sleep, Sleep},
|
||||
};
|
||||
use actix_codec::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
use actix_rt::net::{ActixStream, Ready};
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use actix_utils::{
|
||||
counter::{Counter, CounterGuard},
|
||||
future::{ready, Ready as FutReady},
|
||||
use actix_utils::counter::{Counter, CounterGuard};
|
||||
use futures_core::{future::LocalBoxFuture, ready};
|
||||
|
||||
pub use openssl::ssl::{
|
||||
AlpnError, Error as SslError, HandshakeError, Ssl, SslAcceptor, SslAcceptorBuilder,
|
||||
};
|
||||
use openssl::ssl::{Error, Ssl, SslAcceptor};
|
||||
use pin_project_lite::pin_project;
|
||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
|
||||
use super::{TlsError, DEFAULT_TLS_HANDSHAKE_TIMEOUT, MAX_CONN_COUNTER};
|
||||
use super::MAX_CONN_COUNTER;
|
||||
|
||||
pub mod reexports {
|
||||
//! Re-exports from `openssl` that are useful for acceptors.
|
||||
/// Wrapper type for `tokio_openssl::SslStream` in order to impl `ActixStream` trait.
|
||||
pub struct TlsStream<T>(tokio_openssl::SslStream<T>);
|
||||
|
||||
pub use openssl::ssl::{
|
||||
AlpnError, Error, HandshakeError, Ssl, SslAcceptor, SslAcceptorBuilder,
|
||||
};
|
||||
impl<T> From<tokio_openssl::SslStream<T>> for TlsStream<T> {
|
||||
fn from(stream: tokio_openssl::SslStream<T>) -> Self {
|
||||
Self(stream)
|
||||
}
|
||||
}
|
||||
|
||||
/// Wraps an `openssl` based async TLS stream in order to implement [`ActixStream`].
|
||||
pub struct TlsStream<IO>(tokio_openssl::SslStream<IO>);
|
||||
impl<T> Deref for TlsStream<T> {
|
||||
type Target = tokio_openssl::SslStream<T>;
|
||||
|
||||
impl_more::impl_from!(<IO> in tokio_openssl::SslStream<IO> => TlsStream<IO>);
|
||||
impl_more::impl_deref_and_mut!(<IO> in TlsStream<IO> => tokio_openssl::SslStream<IO>);
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<IO: ActixStream> AsyncRead for TlsStream<IO> {
|
||||
impl<T> DerefMut for TlsStream<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ActixStream> AsyncRead for TlsStream<T> {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
@@ -50,7 +51,7 @@ impl<IO: ActixStream> AsyncRead for TlsStream<IO> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<IO: ActixStream> AsyncWrite for TlsStream<IO> {
|
||||
impl<T: ActixStream> AsyncWrite for TlsStream<T> {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
@@ -76,42 +77,32 @@ impl<IO: ActixStream> AsyncWrite for TlsStream<IO> {
|
||||
}
|
||||
|
||||
fn is_write_vectored(&self) -> bool {
|
||||
(**self).is_write_vectored()
|
||||
(&**self).is_write_vectored()
|
||||
}
|
||||
}
|
||||
|
||||
impl<IO: ActixStream> ActixStream for TlsStream<IO> {
|
||||
impl<T: ActixStream> ActixStream for TlsStream<T> {
|
||||
fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
|
||||
IO::poll_read_ready((**self).get_ref(), cx)
|
||||
T::poll_read_ready((&**self).get_ref(), cx)
|
||||
}
|
||||
|
||||
fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
|
||||
IO::poll_write_ready((**self).get_ref(), cx)
|
||||
T::poll_write_ready((&**self).get_ref(), cx)
|
||||
}
|
||||
}
|
||||
|
||||
/// Accept TLS connections via the `openssl` crate.
|
||||
/// Accept TLS connections via `openssl` package.
|
||||
///
|
||||
/// `openssl` feature enables this `Acceptor` type.
|
||||
pub struct Acceptor {
|
||||
acceptor: SslAcceptor,
|
||||
handshake_timeout: Duration,
|
||||
}
|
||||
|
||||
impl Acceptor {
|
||||
/// Create `openssl` based acceptor service factory.
|
||||
/// Create OpenSSL based `Acceptor` service factory.
|
||||
#[inline]
|
||||
pub fn new(acceptor: SslAcceptor) -> Self {
|
||||
Acceptor {
|
||||
acceptor,
|
||||
handshake_timeout: DEFAULT_TLS_HANDSHAKE_TIMEOUT,
|
||||
}
|
||||
}
|
||||
|
||||
/// Limit the amount of time that the acceptor will wait for a TLS handshake to complete.
|
||||
///
|
||||
/// Default timeout is 3 seconds.
|
||||
pub fn set_handshake_timeout(&mut self, handshake_timeout: Duration) -> &mut Self {
|
||||
self.handshake_timeout = handshake_timeout;
|
||||
self
|
||||
Acceptor { acceptor }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,43 +111,38 @@ impl Clone for Acceptor {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
acceptor: self.acceptor.clone(),
|
||||
handshake_timeout: self.handshake_timeout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<IO: ActixStream> ServiceFactory<IO> for Acceptor {
|
||||
type Response = TlsStream<IO>;
|
||||
type Error = TlsError<Error, Infallible>;
|
||||
impl<T: ActixStream> ServiceFactory<T> for Acceptor {
|
||||
type Response = TlsStream<T>;
|
||||
type Error = SslError;
|
||||
type Config = ();
|
||||
type Service = AcceptorService;
|
||||
type InitError = ();
|
||||
type Future = FutReady<Result<Self::Service, Self::InitError>>;
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
let res = MAX_CONN_COUNTER.with(|conns| {
|
||||
Ok(AcceptorService {
|
||||
acceptor: self.acceptor.clone(),
|
||||
conns: conns.clone(),
|
||||
handshake_timeout: self.handshake_timeout,
|
||||
})
|
||||
});
|
||||
|
||||
ready(res)
|
||||
Box::pin(async { res })
|
||||
}
|
||||
}
|
||||
|
||||
/// OpenSSL based acceptor service.
|
||||
pub struct AcceptorService {
|
||||
acceptor: SslAcceptor,
|
||||
conns: Counter,
|
||||
handshake_timeout: Duration,
|
||||
}
|
||||
|
||||
impl<IO: ActixStream> Service<IO> for AcceptorService {
|
||||
type Response = TlsStream<IO>;
|
||||
type Error = TlsError<Error, Infallible>;
|
||||
type Future = AcceptFut<IO>;
|
||||
impl<T: ActixStream> Service<T> for AcceptorService {
|
||||
type Response = TlsStream<T>;
|
||||
type Error = SslError;
|
||||
type Future = AcceptorServiceResponse<T>;
|
||||
|
||||
fn poll_ready(&self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
if self.conns.available(ctx) {
|
||||
@@ -166,43 +152,30 @@ impl<IO: ActixStream> Service<IO> for AcceptorService {
|
||||
}
|
||||
}
|
||||
|
||||
fn call(&self, io: IO) -> Self::Future {
|
||||
fn call(&self, io: T) -> Self::Future {
|
||||
let ssl_ctx = self.acceptor.context();
|
||||
let ssl = Ssl::new(ssl_ctx).expect("Provided SSL acceptor was invalid.");
|
||||
|
||||
AcceptFut {
|
||||
AcceptorServiceResponse {
|
||||
_guard: self.conns.get(),
|
||||
timeout: sleep(self.handshake_timeout),
|
||||
stream: Some(tokio_openssl::SslStream::new(ssl, io).unwrap()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
/// Accept future for OpenSSL service.
|
||||
#[doc(hidden)]
|
||||
pub struct AcceptFut<IO: ActixStream> {
|
||||
stream: Option<tokio_openssl::SslStream<IO>>,
|
||||
#[pin]
|
||||
timeout: Sleep,
|
||||
_guard: CounterGuard,
|
||||
}
|
||||
pub struct AcceptorServiceResponse<T: ActixStream> {
|
||||
stream: Option<tokio_openssl::SslStream<T>>,
|
||||
_guard: CounterGuard,
|
||||
}
|
||||
|
||||
impl<IO: ActixStream> Future for AcceptFut<IO> {
|
||||
type Output = Result<TlsStream<IO>, TlsError<Error, Infallible>>;
|
||||
impl<T: ActixStream> Future for AcceptorServiceResponse<T> {
|
||||
type Output = Result<TlsStream<T>, SslError>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
|
||||
match Pin::new(this.stream.as_mut().unwrap()).poll_accept(cx) {
|
||||
Poll::Ready(Ok(())) => Poll::Ready(Ok(this
|
||||
.stream
|
||||
.take()
|
||||
.expect("Acceptor should not be polled after it has completed.")
|
||||
.into())),
|
||||
Poll::Ready(Err(err)) => Poll::Ready(Err(TlsError::Tls(err))),
|
||||
Poll::Pending => this.timeout.poll(cx).map(|_| Err(TlsError::Timeout)),
|
||||
}
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
ready!(Pin::new(self.stream.as_mut().unwrap()).poll_accept(cx))?;
|
||||
Poll::Ready(Ok(self
|
||||
.stream
|
||||
.take()
|
||||
.expect("SSL connect has resolved.")
|
||||
.into()))
|
||||
}
|
||||
}
|
||||
|
@@ -1,45 +1,47 @@
|
||||
//! `rustls` based TLS connection acceptor service.
|
||||
//!
|
||||
//! See [`Acceptor`] for main service factory docs.
|
||||
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
future::Future,
|
||||
io::{self, IoSlice},
|
||||
ops::{Deref, DerefMut},
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use actix_rt::{
|
||||
net::{ActixStream, Ready},
|
||||
time::{sleep, Sleep},
|
||||
};
|
||||
use actix_codec::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
use actix_rt::net::{ActixStream, Ready};
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use actix_utils::{
|
||||
counter::{Counter, CounterGuard},
|
||||
future::{ready, Ready as FutReady},
|
||||
};
|
||||
use pin_project_lite::pin_project;
|
||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
use tokio_rustls::{rustls::ServerConfig, Accept, TlsAcceptor};
|
||||
use actix_utils::counter::{Counter, CounterGuard};
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use tokio_rustls::{Accept, TlsAcceptor};
|
||||
|
||||
use super::{TlsError, DEFAULT_TLS_HANDSHAKE_TIMEOUT, MAX_CONN_COUNTER};
|
||||
pub use tokio_rustls::rustls::ServerConfig;
|
||||
|
||||
pub mod reexports {
|
||||
//! Re-exports from `rustls` that are useful for acceptors.
|
||||
use super::MAX_CONN_COUNTER;
|
||||
|
||||
pub use tokio_rustls::rustls::ServerConfig;
|
||||
/// Wrapper type for `tokio_openssl::SslStream` in order to impl `ActixStream` trait.
|
||||
pub struct TlsStream<T>(tokio_rustls::server::TlsStream<T>);
|
||||
|
||||
impl<T> From<tokio_rustls::server::TlsStream<T>> for TlsStream<T> {
|
||||
fn from(stream: tokio_rustls::server::TlsStream<T>) -> Self {
|
||||
Self(stream)
|
||||
}
|
||||
}
|
||||
|
||||
/// Wraps a `rustls` based async TLS stream in order to implement [`ActixStream`].
|
||||
pub struct TlsStream<IO>(tokio_rustls::server::TlsStream<IO>);
|
||||
impl<T> Deref for TlsStream<T> {
|
||||
type Target = tokio_rustls::server::TlsStream<T>;
|
||||
|
||||
impl_more::impl_from!(<IO> in tokio_rustls::server::TlsStream<IO> => TlsStream<IO>);
|
||||
impl_more::impl_deref_and_mut!(<IO> in TlsStream<IO> => tokio_rustls::server::TlsStream<IO>);
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<IO: ActixStream> AsyncRead for TlsStream<IO> {
|
||||
impl<T> DerefMut for TlsStream<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ActixStream> AsyncRead for TlsStream<T> {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
@@ -49,7 +51,7 @@ impl<IO: ActixStream> AsyncRead for TlsStream<IO> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<IO: ActixStream> AsyncWrite for TlsStream<IO> {
|
||||
impl<T: ActixStream> AsyncWrite for TlsStream<T> {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
@@ -75,85 +77,76 @@ impl<IO: ActixStream> AsyncWrite for TlsStream<IO> {
|
||||
}
|
||||
|
||||
fn is_write_vectored(&self) -> bool {
|
||||
(**self).is_write_vectored()
|
||||
(&**self).is_write_vectored()
|
||||
}
|
||||
}
|
||||
|
||||
impl<IO: ActixStream> ActixStream for TlsStream<IO> {
|
||||
impl<T: ActixStream> ActixStream for TlsStream<T> {
|
||||
fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
|
||||
IO::poll_read_ready((**self).get_ref().0, cx)
|
||||
T::poll_read_ready((&**self).get_ref().0, cx)
|
||||
}
|
||||
|
||||
fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
|
||||
IO::poll_write_ready((**self).get_ref().0, cx)
|
||||
T::poll_write_ready((&**self).get_ref().0, cx)
|
||||
}
|
||||
}
|
||||
|
||||
/// Accept TLS connections via the `rustls` crate.
|
||||
/// Accept TLS connections via `rustls` package.
|
||||
///
|
||||
/// `rustls` feature enables this `Acceptor` type.
|
||||
pub struct Acceptor {
|
||||
config: Arc<ServerConfig>,
|
||||
handshake_timeout: Duration,
|
||||
}
|
||||
|
||||
impl Acceptor {
|
||||
/// Constructs `rustls` based acceptor service factory.
|
||||
/// Create Rustls based `Acceptor` service factory.
|
||||
#[inline]
|
||||
pub fn new(config: ServerConfig) -> Self {
|
||||
Acceptor {
|
||||
config: Arc::new(config),
|
||||
handshake_timeout: DEFAULT_TLS_HANDSHAKE_TIMEOUT,
|
||||
}
|
||||
}
|
||||
|
||||
/// Limit the amount of time that the acceptor will wait for a TLS handshake to complete.
|
||||
///
|
||||
/// Default timeout is 3 seconds.
|
||||
pub fn set_handshake_timeout(&mut self, handshake_timeout: Duration) -> &mut Self {
|
||||
self.handshake_timeout = handshake_timeout;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Acceptor {
|
||||
#[inline]
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
config: self.config.clone(),
|
||||
handshake_timeout: self.handshake_timeout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<IO: ActixStream> ServiceFactory<IO> for Acceptor {
|
||||
type Response = TlsStream<IO>;
|
||||
type Error = TlsError<io::Error, Infallible>;
|
||||
impl<T: ActixStream> ServiceFactory<T> for Acceptor {
|
||||
type Response = TlsStream<T>;
|
||||
type Error = io::Error;
|
||||
type Config = ();
|
||||
|
||||
type Service = AcceptorService;
|
||||
type InitError = ();
|
||||
type Future = FutReady<Result<Self::Service, Self::InitError>>;
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
let res = MAX_CONN_COUNTER.with(|conns| {
|
||||
Ok(AcceptorService {
|
||||
acceptor: self.config.clone().into(),
|
||||
conns: conns.clone(),
|
||||
handshake_timeout: self.handshake_timeout,
|
||||
})
|
||||
});
|
||||
|
||||
ready(res)
|
||||
Box::pin(async { res })
|
||||
}
|
||||
}
|
||||
|
||||
/// Rustls based acceptor service.
|
||||
/// Rustls based `Acceptor` service
|
||||
pub struct AcceptorService {
|
||||
acceptor: TlsAcceptor,
|
||||
conns: Counter,
|
||||
handshake_timeout: Duration,
|
||||
}
|
||||
|
||||
impl<IO: ActixStream> Service<IO> for AcceptorService {
|
||||
type Response = TlsStream<IO>;
|
||||
type Error = TlsError<io::Error, Infallible>;
|
||||
type Future = AcceptFut<IO>;
|
||||
impl<T: ActixStream> Service<T> for AcceptorService {
|
||||
type Response = TlsStream<T>;
|
||||
type Error = io::Error;
|
||||
type Future = AcceptorServiceFut<T>;
|
||||
|
||||
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
if self.conns.available(cx) {
|
||||
@@ -163,35 +156,24 @@ impl<IO: ActixStream> Service<IO> for AcceptorService {
|
||||
}
|
||||
}
|
||||
|
||||
fn call(&self, req: IO) -> Self::Future {
|
||||
AcceptFut {
|
||||
fut: self.acceptor.accept(req),
|
||||
timeout: sleep(self.handshake_timeout),
|
||||
fn call(&self, req: T) -> Self::Future {
|
||||
AcceptorServiceFut {
|
||||
_guard: self.conns.get(),
|
||||
fut: self.acceptor.accept(req),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
/// Accept future for Rustls service.
|
||||
#[doc(hidden)]
|
||||
pub struct AcceptFut<IO: ActixStream> {
|
||||
fut: Accept<IO>,
|
||||
#[pin]
|
||||
timeout: Sleep,
|
||||
_guard: CounterGuard,
|
||||
}
|
||||
pub struct AcceptorServiceFut<T: ActixStream> {
|
||||
fut: Accept<T>,
|
||||
_guard: CounterGuard,
|
||||
}
|
||||
|
||||
impl<IO: ActixStream> Future for AcceptFut<IO> {
|
||||
type Output = Result<TlsStream<IO>, TlsError<io::Error, Infallible>>;
|
||||
impl<T: ActixStream> Future for AcceptorServiceFut<T> {
|
||||
type Output = Result<TlsStream<T>, io::Error>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let mut this = self.project();
|
||||
match Pin::new(&mut this.fut).poll(cx) {
|
||||
Poll::Ready(Ok(stream)) => Poll::Ready(Ok(TlsStream(stream))),
|
||||
Poll::Ready(Err(err)) => Poll::Ready(Err(TlsError::Tls(err))),
|
||||
Poll::Pending => this.timeout.poll(cx).map(|_| Err(TlsError::Timeout)),
|
||||
}
|
||||
let this = self.get_mut();
|
||||
Pin::new(&mut this.fut).poll(cx).map_ok(TlsStream)
|
||||
}
|
||||
}
|
||||
|
350
actix-tls/src/connect/connect.rs
Executable file
350
actix-tls/src/connect/connect.rs
Executable file
@@ -0,0 +1,350 @@
|
||||
use std::{
|
||||
collections::{vec_deque, VecDeque},
|
||||
fmt,
|
||||
iter::{self, FromIterator as _},
|
||||
mem,
|
||||
net::{IpAddr, SocketAddr},
|
||||
};
|
||||
|
||||
/// Parse a host into parts (hostname and port).
|
||||
pub trait Address: Unpin + 'static {
|
||||
/// Get hostname part.
|
||||
fn hostname(&self) -> &str;
|
||||
|
||||
/// Get optional port part.
|
||||
fn port(&self) -> Option<u16> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Address for String {
|
||||
fn hostname(&self) -> &str {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Address for &'static str {
|
||||
fn hostname(&self) -> &str {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Hash)]
|
||||
pub(crate) enum ConnectAddrs {
|
||||
None,
|
||||
One(SocketAddr),
|
||||
Multi(VecDeque<SocketAddr>),
|
||||
}
|
||||
|
||||
impl ConnectAddrs {
|
||||
pub(crate) fn is_none(&self) -> bool {
|
||||
matches!(self, Self::None)
|
||||
}
|
||||
|
||||
pub(crate) fn is_some(&self) -> bool {
|
||||
!self.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ConnectAddrs {
|
||||
fn default() -> Self {
|
||||
Self::None
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<SocketAddr>> for ConnectAddrs {
|
||||
fn from(addr: Option<SocketAddr>) -> Self {
|
||||
match addr {
|
||||
Some(addr) => ConnectAddrs::One(addr),
|
||||
None => ConnectAddrs::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Connection info.
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Connect<T> {
|
||||
pub(crate) req: T,
|
||||
pub(crate) port: u16,
|
||||
pub(crate) addr: ConnectAddrs,
|
||||
pub(crate) local_addr: Option<IpAddr>,
|
||||
}
|
||||
|
||||
impl<T: Address> Connect<T> {
|
||||
/// Create `Connect` instance by splitting the string by ':' and convert the second part to u16
|
||||
pub fn new(req: T) -> Connect<T> {
|
||||
let (_, port) = parse_host(req.hostname());
|
||||
|
||||
Connect {
|
||||
req,
|
||||
port: port.unwrap_or(0),
|
||||
addr: ConnectAddrs::None,
|
||||
local_addr: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create new `Connect` instance from host and address. Connector skips name resolution stage
|
||||
/// for such connect messages.
|
||||
pub fn with_addr(req: T, addr: SocketAddr) -> Connect<T> {
|
||||
Connect {
|
||||
req,
|
||||
port: 0,
|
||||
addr: ConnectAddrs::One(addr),
|
||||
local_addr: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Use port if address does not provide one.
|
||||
///
|
||||
/// Default value is 0.
|
||||
pub fn set_port(mut self, port: u16) -> Self {
|
||||
self.port = port;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set address.
|
||||
pub fn set_addr(mut self, addr: Option<SocketAddr>) -> Self {
|
||||
self.addr = ConnectAddrs::from(addr);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set list of addresses.
|
||||
pub fn set_addrs<I>(mut self, addrs: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = SocketAddr>,
|
||||
{
|
||||
let mut addrs = VecDeque::from_iter(addrs);
|
||||
self.addr = if addrs.len() < 2 {
|
||||
ConnectAddrs::from(addrs.pop_front())
|
||||
} else {
|
||||
ConnectAddrs::Multi(addrs)
|
||||
};
|
||||
self
|
||||
}
|
||||
|
||||
/// Set local_addr of connect.
|
||||
pub fn set_local_addr(mut self, addr: impl Into<IpAddr>) -> Self {
|
||||
self.local_addr = Some(addr.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Get hostname.
|
||||
pub fn hostname(&self) -> &str {
|
||||
self.req.hostname()
|
||||
}
|
||||
|
||||
/// Get request port.
|
||||
pub fn port(&self) -> u16 {
|
||||
self.req.port().unwrap_or(self.port)
|
||||
}
|
||||
|
||||
/// Get resolved request addresses.
|
||||
pub fn addrs(&self) -> ConnectAddrsIter<'_> {
|
||||
match self.addr {
|
||||
ConnectAddrs::None => ConnectAddrsIter::None,
|
||||
ConnectAddrs::One(addr) => ConnectAddrsIter::One(addr),
|
||||
ConnectAddrs::Multi(ref addrs) => ConnectAddrsIter::Multi(addrs.iter()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Take resolved request addresses.
|
||||
pub fn take_addrs(&mut self) -> ConnectAddrsIter<'static> {
|
||||
match mem::take(&mut self.addr) {
|
||||
ConnectAddrs::None => ConnectAddrsIter::None,
|
||||
ConnectAddrs::One(addr) => ConnectAddrsIter::One(addr),
|
||||
ConnectAddrs::Multi(addrs) => ConnectAddrsIter::MultiOwned(addrs.into_iter()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Address> From<T> for Connect<T> {
|
||||
fn from(addr: T) -> Self {
|
||||
Connect::new(addr)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Address> fmt::Display for Connect<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}:{}", self.hostname(), self.port())
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator over addresses in a [`Connect`] request.
|
||||
#[derive(Clone)]
|
||||
pub enum ConnectAddrsIter<'a> {
|
||||
None,
|
||||
One(SocketAddr),
|
||||
Multi(vec_deque::Iter<'a, SocketAddr>),
|
||||
MultiOwned(vec_deque::IntoIter<SocketAddr>),
|
||||
}
|
||||
|
||||
impl Iterator for ConnectAddrsIter<'_> {
|
||||
type Item = SocketAddr;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match *self {
|
||||
Self::None => None,
|
||||
Self::One(addr) => {
|
||||
*self = Self::None;
|
||||
Some(addr)
|
||||
}
|
||||
Self::Multi(ref mut iter) => iter.next().copied(),
|
||||
Self::MultiOwned(ref mut iter) => iter.next(),
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
match *self {
|
||||
Self::None => (0, Some(0)),
|
||||
Self::One(_) => (1, Some(1)),
|
||||
Self::Multi(ref iter) => iter.size_hint(),
|
||||
Self::MultiOwned(ref iter) => iter.size_hint(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ConnectAddrsIter<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_list().entries(self.clone()).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl iter::ExactSizeIterator for ConnectAddrsIter<'_> {}
|
||||
|
||||
impl iter::FusedIterator for ConnectAddrsIter<'_> {}
|
||||
|
||||
pub struct Connection<T, U> {
|
||||
io: U,
|
||||
req: T,
|
||||
}
|
||||
|
||||
impl<T, U> Connection<T, U> {
|
||||
pub fn new(io: U, req: T) -> Self {
|
||||
Self { io, req }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Connection<T, U> {
|
||||
/// Reconstruct from a parts.
|
||||
pub fn from_parts(io: U, req: T) -> Self {
|
||||
Self { io, req }
|
||||
}
|
||||
|
||||
/// Deconstruct into a parts.
|
||||
pub fn into_parts(self) -> (U, T) {
|
||||
(self.io, self.req)
|
||||
}
|
||||
|
||||
/// Replace inclosed object, return new Stream and old object
|
||||
pub fn replace_io<Y>(self, io: Y) -> (U, Connection<T, Y>) {
|
||||
(self.io, Connection { io, req: self.req })
|
||||
}
|
||||
|
||||
/// Returns a shared reference to the underlying stream.
|
||||
pub fn io_ref(&self) -> &U {
|
||||
&self.io
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying stream.
|
||||
pub fn io_mut(&mut self) -> &mut U {
|
||||
&mut self.io
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Address, U> Connection<T, U> {
|
||||
/// Get hostname.
|
||||
pub fn host(&self) -> &str {
|
||||
self.req.hostname()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> std::ops::Deref for Connection<T, U> {
|
||||
type Target = U;
|
||||
|
||||
fn deref(&self) -> &U {
|
||||
&self.io
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> std::ops::DerefMut for Connection<T, U> {
|
||||
fn deref_mut(&mut self) -> &mut U {
|
||||
&mut self.io
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U: fmt::Debug> fmt::Debug for Connection<T, U> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Stream {{{:?}}}", self.io)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_host(host: &str) -> (&str, Option<u16>) {
|
||||
let mut parts_iter = host.splitn(2, ':');
|
||||
|
||||
match parts_iter.next() {
|
||||
Some(hostname) => {
|
||||
let port_str = parts_iter.next().unwrap_or("");
|
||||
let port = port_str.parse::<u16>().ok();
|
||||
(hostname, port)
|
||||
}
|
||||
|
||||
None => (host, None),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_host_parser() {
|
||||
assert_eq!(parse_host("example.com"), ("example.com", None));
|
||||
assert_eq!(parse_host("example.com:8080"), ("example.com", Some(8080)));
|
||||
assert_eq!(parse_host("example:8080"), ("example", Some(8080)));
|
||||
assert_eq!(parse_host("example.com:false"), ("example.com", None));
|
||||
assert_eq!(parse_host("example.com:false:false"), ("example.com", None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_addr_iter_multi() {
|
||||
let localhost = SocketAddr::from((IpAddr::from(Ipv4Addr::LOCALHOST), 8080));
|
||||
let unspecified = SocketAddr::from((IpAddr::from(Ipv4Addr::UNSPECIFIED), 8080));
|
||||
|
||||
let mut addrs = VecDeque::new();
|
||||
addrs.push_back(localhost);
|
||||
addrs.push_back(unspecified);
|
||||
|
||||
let mut iter = ConnectAddrsIter::Multi(addrs.iter());
|
||||
assert_eq!(iter.next(), Some(localhost));
|
||||
assert_eq!(iter.next(), Some(unspecified));
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
let mut iter = ConnectAddrsIter::MultiOwned(addrs.into_iter());
|
||||
assert_eq!(iter.next(), Some(localhost));
|
||||
assert_eq!(iter.next(), Some(unspecified));
|
||||
assert_eq!(iter.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_addr_iter_single() {
|
||||
let localhost = SocketAddr::from((IpAddr::from(Ipv4Addr::LOCALHOST), 8080));
|
||||
|
||||
let mut iter = ConnectAddrsIter::One(localhost);
|
||||
assert_eq!(iter.next(), Some(localhost));
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
let mut iter = ConnectAddrsIter::None;
|
||||
assert_eq!(iter.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_local_addr() {
|
||||
let conn = Connect::new("hello").set_local_addr([127, 0, 0, 1]);
|
||||
assert_eq!(
|
||||
conn.local_addr.unwrap(),
|
||||
IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))
|
||||
)
|
||||
}
|
||||
}
|
@@ -1,82 +0,0 @@
|
||||
use std::{
|
||||
collections::{vec_deque, VecDeque},
|
||||
fmt, iter,
|
||||
net::SocketAddr,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub(crate) enum ConnectAddrs {
|
||||
None,
|
||||
One(SocketAddr),
|
||||
// TODO: consider using smallvec
|
||||
Multi(VecDeque<SocketAddr>),
|
||||
}
|
||||
|
||||
impl ConnectAddrs {
|
||||
pub(crate) fn is_unresolved(&self) -> bool {
|
||||
matches!(self, Self::None)
|
||||
}
|
||||
|
||||
pub(crate) fn is_resolved(&self) -> bool {
|
||||
!self.is_unresolved()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ConnectAddrs {
|
||||
fn default() -> Self {
|
||||
Self::None
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<SocketAddr>> for ConnectAddrs {
|
||||
fn from(addr: Option<SocketAddr>) -> Self {
|
||||
match addr {
|
||||
Some(addr) => ConnectAddrs::One(addr),
|
||||
None => ConnectAddrs::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator over addresses in a [`Connect`] request.
|
||||
#[derive(Clone)]
|
||||
pub(crate) enum ConnectAddrsIter<'a> {
|
||||
None,
|
||||
One(SocketAddr),
|
||||
Multi(vec_deque::Iter<'a, SocketAddr>),
|
||||
MultiOwned(vec_deque::IntoIter<SocketAddr>),
|
||||
}
|
||||
|
||||
impl Iterator for ConnectAddrsIter<'_> {
|
||||
type Item = SocketAddr;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match *self {
|
||||
Self::None => None,
|
||||
Self::One(addr) => {
|
||||
*self = Self::None;
|
||||
Some(addr)
|
||||
}
|
||||
Self::Multi(ref mut iter) => iter.next().copied(),
|
||||
Self::MultiOwned(ref mut iter) => iter.next(),
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
match *self {
|
||||
Self::None => (0, Some(0)),
|
||||
Self::One(_) => (1, Some(1)),
|
||||
Self::Multi(ref iter) => iter.size_hint(),
|
||||
Self::MultiOwned(ref iter) => iter.size_hint(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ConnectAddrsIter<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_list().entries(self.clone()).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl iter::ExactSizeIterator for ConnectAddrsIter<'_> {}
|
||||
|
||||
impl iter::FusedIterator for ConnectAddrsIter<'_> {}
|
@@ -1,51 +0,0 @@
|
||||
use super::Host;
|
||||
|
||||
/// Wraps underlying I/O and the connection request that initiated it.
|
||||
#[derive(Debug)]
|
||||
pub struct Connection<R, IO> {
|
||||
pub(crate) req: R,
|
||||
pub(crate) io: IO,
|
||||
}
|
||||
|
||||
impl_more::impl_deref_and_mut!(<R, IO> in Connection<R, IO> => io: IO);
|
||||
|
||||
impl<R, IO> Connection<R, IO> {
|
||||
/// Construct new `Connection` from request and IO parts.
|
||||
pub fn new(req: R, io: IO) -> Self {
|
||||
Self { req, io }
|
||||
}
|
||||
}
|
||||
|
||||
impl<R, IO> Connection<R, IO> {
|
||||
/// Deconstructs into IO and request parts.
|
||||
pub fn into_parts(self) -> (IO, R) {
|
||||
(self.io, self.req)
|
||||
}
|
||||
|
||||
/// Replaces underlying IO, returning old IO and new `Connection`.
|
||||
pub fn replace_io<IO2>(self, io: IO2) -> (IO, Connection<R, IO2>) {
|
||||
(self.io, Connection { io, req: self.req })
|
||||
}
|
||||
|
||||
/// Returns a shared reference to the underlying IO.
|
||||
pub fn io_ref(&self) -> &IO {
|
||||
&self.io
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying IO.
|
||||
pub fn io_mut(&mut self) -> &mut IO {
|
||||
&mut self.io
|
||||
}
|
||||
|
||||
/// Returns a reference to the connection request.
|
||||
pub fn request(&self) -> &R {
|
||||
&self.req
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Host, IO> Connection<R, IO> {
|
||||
/// Returns hostname.
|
||||
pub fn hostname(&self) -> &str {
|
||||
self.req.hostname()
|
||||
}
|
||||
}
|
242
actix-tls/src/connect/connector.rs
Normal file → Executable file
242
actix-tls/src/connect/connector.rs
Normal file → Executable file
@@ -1,128 +1,194 @@
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
future::Future,
|
||||
io,
|
||||
net::{IpAddr, SocketAddr, SocketAddrV4, SocketAddrV6},
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use actix_rt::net::TcpStream;
|
||||
use actix_rt::net::{TcpSocket, TcpStream};
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use actix_utils::future::{ok, Ready};
|
||||
use futures_core::ready;
|
||||
use futures_core::{future::LocalBoxFuture, ready};
|
||||
use log::{error, trace};
|
||||
use tokio_util::sync::ReusableBoxFuture;
|
||||
|
||||
use super::{
|
||||
error::ConnectError,
|
||||
resolver::{Resolver, ResolverService},
|
||||
tcp::{TcpConnector, TcpConnectorService},
|
||||
ConnectInfo, Connection, Host,
|
||||
};
|
||||
use super::connect::{Address, Connect, ConnectAddrs, Connection};
|
||||
use super::error::ConnectError;
|
||||
|
||||
/// Combined resolver and TCP connector service factory.
|
||||
///
|
||||
/// Used to create [`ConnectorService`]s which receive connection information, resolve DNS if
|
||||
/// required, and return a TCP stream.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Connector {
|
||||
resolver: Resolver,
|
||||
}
|
||||
/// TCP connector service factory
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct TcpConnectorFactory;
|
||||
|
||||
impl Connector {
|
||||
/// Constructs new connector factory with the given resolver.
|
||||
pub fn new(resolver: Resolver) -> Self {
|
||||
Connector { resolver }
|
||||
}
|
||||
|
||||
/// Build connector service.
|
||||
pub fn service(&self) -> ConnectorService {
|
||||
ConnectorService {
|
||||
tcp: TcpConnector::default().service(),
|
||||
resolver: self.resolver.service(),
|
||||
}
|
||||
impl TcpConnectorFactory {
|
||||
/// Create TCP connector service
|
||||
pub fn service(&self) -> TcpConnector {
|
||||
TcpConnector
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Host> ServiceFactory<ConnectInfo<R>> for Connector {
|
||||
type Response = Connection<R, TcpStream>;
|
||||
impl<T: Address> ServiceFactory<Connect<T>> for TcpConnectorFactory {
|
||||
type Response = Connection<T, TcpStream>;
|
||||
type Error = ConnectError;
|
||||
type Config = ();
|
||||
type Service = ConnectorService;
|
||||
type Service = TcpConnector;
|
||||
type InitError = ();
|
||||
type Future = Ready<Result<Self::Service, Self::InitError>>;
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
ok(self.service())
|
||||
let service = self.service();
|
||||
Box::pin(async move { Ok(service) })
|
||||
}
|
||||
}
|
||||
|
||||
/// Combined resolver and TCP connector service.
|
||||
///
|
||||
/// Service implementation receives connection information, resolves DNS if required, and returns
|
||||
/// a TCP stream.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct ConnectorService {
|
||||
tcp: TcpConnectorService,
|
||||
resolver: ResolverService,
|
||||
}
|
||||
/// TCP connector service
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct TcpConnector;
|
||||
|
||||
impl<R: Host> Service<ConnectInfo<R>> for ConnectorService {
|
||||
type Response = Connection<R, TcpStream>;
|
||||
impl<T: Address> Service<Connect<T>> for TcpConnector {
|
||||
type Response = Connection<T, TcpStream>;
|
||||
type Error = ConnectError;
|
||||
type Future = ConnectServiceResponse<R>;
|
||||
type Future = TcpConnectorResponse<T>;
|
||||
|
||||
actix_service::always_ready!();
|
||||
|
||||
fn call(&self, req: ConnectInfo<R>) -> Self::Future {
|
||||
ConnectServiceResponse {
|
||||
fut: ConnectFut::Resolve(self.resolver.call(req)),
|
||||
tcp: self.tcp,
|
||||
}
|
||||
fn call(&self, req: Connect<T>) -> Self::Future {
|
||||
let port = req.port();
|
||||
let Connect {
|
||||
req,
|
||||
addr,
|
||||
local_addr,
|
||||
..
|
||||
} = req;
|
||||
|
||||
TcpConnectorResponse::new(req, port, local_addr, addr)
|
||||
}
|
||||
}
|
||||
|
||||
/// Chains futures of resolve and connect steps.
|
||||
pub(crate) enum ConnectFut<R: Host> {
|
||||
Resolve(<ResolverService as Service<ConnectInfo<R>>>::Future),
|
||||
Connect(<TcpConnectorService as Service<ConnectInfo<R>>>::Future),
|
||||
/// TCP stream connector response future
|
||||
pub enum TcpConnectorResponse<T> {
|
||||
Response {
|
||||
req: Option<T>,
|
||||
port: u16,
|
||||
local_addr: Option<IpAddr>,
|
||||
addrs: Option<VecDeque<SocketAddr>>,
|
||||
stream: ReusableBoxFuture<Result<TcpStream, io::Error>>,
|
||||
},
|
||||
Error(Option<ConnectError>),
|
||||
}
|
||||
|
||||
/// Container for the intermediate states of [`ConnectFut`].
|
||||
pub(crate) enum ConnectFutState<R: Host> {
|
||||
Resolved(ConnectInfo<R>),
|
||||
Connected(Connection<R, TcpStream>),
|
||||
}
|
||||
|
||||
impl<R: Host> ConnectFut<R> {
|
||||
fn poll_connect(
|
||||
&mut self,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Result<ConnectFutState<R>, ConnectError>> {
|
||||
match self {
|
||||
ConnectFut::Resolve(ref mut fut) => {
|
||||
Pin::new(fut).poll(cx).map_ok(ConnectFutState::Resolved)
|
||||
}
|
||||
|
||||
ConnectFut::Connect(ref mut fut) => {
|
||||
Pin::new(fut).poll(cx).map_ok(ConnectFutState::Connected)
|
||||
}
|
||||
impl<T: Address> TcpConnectorResponse<T> {
|
||||
pub(crate) fn new(
|
||||
req: T,
|
||||
port: u16,
|
||||
local_addr: Option<IpAddr>,
|
||||
addr: ConnectAddrs,
|
||||
) -> TcpConnectorResponse<T> {
|
||||
if addr.is_none() {
|
||||
error!("TCP connector: unresolved connection address");
|
||||
return TcpConnectorResponse::Error(Some(ConnectError::Unresolved));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConnectServiceResponse<R: Host> {
|
||||
fut: ConnectFut<R>,
|
||||
tcp: TcpConnectorService,
|
||||
}
|
||||
trace!(
|
||||
"TCP connector: connecting to {} on port {}",
|
||||
req.hostname(),
|
||||
port
|
||||
);
|
||||
|
||||
impl<R: Host> Future for ConnectServiceResponse<R> {
|
||||
type Output = Result<Connection<R, TcpStream>, ConnectError>;
|
||||
match addr {
|
||||
ConnectAddrs::None => unreachable!("none variant already checked"),
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
loop {
|
||||
match ready!(self.fut.poll_connect(cx))? {
|
||||
ConnectFutState::Resolved(res) => {
|
||||
self.fut = ConnectFut::Connect(self.tcp.call(res));
|
||||
ConnectAddrs::One(addr) => TcpConnectorResponse::Response {
|
||||
req: Some(req),
|
||||
port,
|
||||
local_addr,
|
||||
addrs: None,
|
||||
stream: ReusableBoxFuture::new(connect(addr, local_addr)),
|
||||
},
|
||||
|
||||
// when resolver returns multiple socket addr for request they would be popped from
|
||||
// front end of queue and returns with the first successful tcp connection.
|
||||
ConnectAddrs::Multi(mut addrs) => {
|
||||
let addr = addrs.pop_front().unwrap();
|
||||
|
||||
TcpConnectorResponse::Response {
|
||||
req: Some(req),
|
||||
port,
|
||||
local_addr,
|
||||
addrs: Some(addrs),
|
||||
stream: ReusableBoxFuture::new(connect(addr, local_addr)),
|
||||
}
|
||||
ConnectFutState::Connected(res) => return Poll::Ready(Ok(res)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Address> Future for TcpConnectorResponse<T> {
|
||||
type Output = Result<Connection<T, TcpStream>, ConnectError>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
match self.get_mut() {
|
||||
TcpConnectorResponse::Error(err) => Poll::Ready(Err(err.take().unwrap())),
|
||||
|
||||
TcpConnectorResponse::Response {
|
||||
req,
|
||||
port,
|
||||
local_addr,
|
||||
addrs,
|
||||
stream,
|
||||
} => loop {
|
||||
match ready!(stream.poll(cx)) {
|
||||
Ok(sock) => {
|
||||
let req = req.take().unwrap();
|
||||
trace!(
|
||||
"TCP connector: successfully connected to {:?} - {:?}",
|
||||
req.hostname(),
|
||||
sock.peer_addr()
|
||||
);
|
||||
return Poll::Ready(Ok(Connection::new(sock, req)));
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
trace!(
|
||||
"TCP connector: failed to connect to {:?} port: {}",
|
||||
req.as_ref().unwrap().hostname(),
|
||||
port,
|
||||
);
|
||||
|
||||
if let Some(addr) = addrs.as_mut().and_then(|addrs| addrs.pop_front()) {
|
||||
stream.set(connect(addr, *local_addr));
|
||||
} else {
|
||||
return Poll::Ready(Err(ConnectError::Io(err)));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn connect(addr: SocketAddr, local_addr: Option<IpAddr>) -> io::Result<TcpStream> {
|
||||
// use local addr if connect asks for it.
|
||||
match local_addr {
|
||||
Some(ip_addr) => {
|
||||
let socket = match ip_addr {
|
||||
IpAddr::V4(ip_addr) => {
|
||||
let socket = TcpSocket::new_v4()?;
|
||||
let addr = SocketAddr::V4(SocketAddrV4::new(ip_addr, 0));
|
||||
socket.bind(addr)?;
|
||||
socket
|
||||
}
|
||||
IpAddr::V6(ip_addr) => {
|
||||
let socket = TcpSocket::new_v6()?;
|
||||
let addr = SocketAddr::V6(SocketAddrV6::new(ip_addr, 0, 0, 0));
|
||||
socket.bind(addr)?;
|
||||
socket
|
||||
}
|
||||
};
|
||||
|
||||
socket.connect(addr).await
|
||||
}
|
||||
|
||||
None => TcpStream::connect(addr).await,
|
||||
}
|
||||
}
|
||||
|
@@ -1,44 +1,25 @@
|
||||
use std::{error::Error, fmt, io};
|
||||
use std::io;
|
||||
|
||||
/// Errors that can result from using a connector service.
|
||||
#[derive(Debug)]
|
||||
use derive_more::Display;
|
||||
|
||||
#[derive(Debug, Display)]
|
||||
pub enum ConnectError {
|
||||
/// Failed to resolve the hostname.
|
||||
/// Failed to resolve the hostname
|
||||
#[display(fmt = "Failed resolving hostname: {}", _0)]
|
||||
Resolver(Box<dyn std::error::Error>),
|
||||
|
||||
/// No DNS records.
|
||||
/// No dns records
|
||||
#[display(fmt = "No dns records found for the input")]
|
||||
NoRecords,
|
||||
|
||||
/// Invalid input.
|
||||
/// Invalid input
|
||||
InvalidInput,
|
||||
|
||||
/// Unresolved host name.
|
||||
/// Unresolved host name
|
||||
#[display(fmt = "Connector received `Connect` method with unresolved host")]
|
||||
Unresolved,
|
||||
|
||||
/// Connection IO error.
|
||||
/// Connection IO error
|
||||
#[display(fmt = "{}", _0)]
|
||||
Io(io::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for ConnectError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::NoRecords => f.write_str("No DNS records found for the input"),
|
||||
Self::InvalidInput => f.write_str("Invalid input"),
|
||||
Self::Unresolved => {
|
||||
f.write_str("Connector received `Connect` method with unresolved host")
|
||||
}
|
||||
Self::Resolver(_) => f.write_str("Failed to resolve hostname"),
|
||||
Self::Io(_) => f.write_str("I/O error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ConnectError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
match self {
|
||||
Self::Resolver(err) => Some(&**err),
|
||||
Self::Io(err) => Some(err),
|
||||
Self::NoRecords | Self::InvalidInput | Self::Unresolved => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,71 +0,0 @@
|
||||
//! The [`Host`] trait.
|
||||
|
||||
/// An interface for types where host parts (hostname and port) can be derived.
|
||||
///
|
||||
/// The [WHATWG URL Standard] defines the terminology used for this trait and its methods.
|
||||
///
|
||||
/// ```plain
|
||||
/// +------------------------+
|
||||
/// | host |
|
||||
/// +-----------------+------+
|
||||
/// | hostname | port |
|
||||
/// | | |
|
||||
/// | sub.example.com : 8080 |
|
||||
/// +-----------------+------+
|
||||
/// ```
|
||||
///
|
||||
/// [WHATWG URL Standard]: https://url.spec.whatwg.org/
|
||||
pub trait Host: Unpin + 'static {
|
||||
/// Extract hostname.
|
||||
fn hostname(&self) -> &str;
|
||||
|
||||
/// Extract optional port.
|
||||
fn port(&self) -> Option<u16> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Host for String {
|
||||
fn hostname(&self) -> &str {
|
||||
self.split_once(':')
|
||||
.map(|(hostname, _)| hostname)
|
||||
.unwrap_or(self)
|
||||
}
|
||||
|
||||
fn port(&self) -> Option<u16> {
|
||||
self.split_once(':').and_then(|(_, port)| port.parse().ok())
|
||||
}
|
||||
}
|
||||
|
||||
impl Host for &'static str {
|
||||
fn hostname(&self) -> &str {
|
||||
self.split_once(':')
|
||||
.map(|(hostname, _)| hostname)
|
||||
.unwrap_or(self)
|
||||
}
|
||||
|
||||
fn port(&self) -> Option<u16> {
|
||||
self.split_once(':').and_then(|(_, port)| port.parse().ok())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
macro_rules! assert_connection_info_eq {
|
||||
($req:expr, $hostname:expr, $port:expr) => {{
|
||||
assert_eq!($req.hostname(), $hostname);
|
||||
assert_eq!($req.port(), $port);
|
||||
}};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn host_parsing() {
|
||||
assert_connection_info_eq!("example.com", "example.com", None);
|
||||
assert_connection_info_eq!("example.com:8080", "example.com", Some(8080));
|
||||
assert_connection_info_eq!("example:8080", "example", Some(8080));
|
||||
assert_connection_info_eq!("example.com:false", "example.com", None);
|
||||
assert_connection_info_eq!("example.com:false:false", "example.com", None);
|
||||
}
|
||||
}
|
@@ -1,249 +0,0 @@
|
||||
//! Connection info struct.
|
||||
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
fmt,
|
||||
iter::{self, FromIterator as _},
|
||||
mem,
|
||||
net::{IpAddr, SocketAddr},
|
||||
};
|
||||
|
||||
use super::{
|
||||
connect_addrs::{ConnectAddrs, ConnectAddrsIter},
|
||||
Host,
|
||||
};
|
||||
|
||||
/// Connection request information.
|
||||
///
|
||||
/// May contain known/pre-resolved socket address(es) or a host that needs resolving with DNS.
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ConnectInfo<R> {
|
||||
pub(crate) request: R,
|
||||
pub(crate) port: u16,
|
||||
pub(crate) addr: ConnectAddrs,
|
||||
pub(crate) local_addr: Option<IpAddr>,
|
||||
}
|
||||
|
||||
impl<R: Host> ConnectInfo<R> {
|
||||
/// Constructs new connection info using a request.
|
||||
pub fn new(request: R) -> ConnectInfo<R> {
|
||||
let port = request.port();
|
||||
|
||||
ConnectInfo {
|
||||
request,
|
||||
port: port.unwrap_or(0),
|
||||
addr: ConnectAddrs::None,
|
||||
local_addr: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs new connection info from request and known socket address.
|
||||
///
|
||||
/// Since socket address is known, [`Connector`](super::Connector) will skip the DNS
|
||||
/// resolution step.
|
||||
pub fn with_addr(request: R, addr: SocketAddr) -> ConnectInfo<R> {
|
||||
ConnectInfo {
|
||||
request,
|
||||
port: 0,
|
||||
addr: ConnectAddrs::One(addr),
|
||||
local_addr: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set connection port.
|
||||
///
|
||||
/// If request provided a port, this will override it.
|
||||
pub fn set_port(mut self, port: u16) -> Self {
|
||||
self.port = port;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set connection socket address.
|
||||
pub fn set_addr(mut self, addr: impl Into<Option<SocketAddr>>) -> Self {
|
||||
self.addr = ConnectAddrs::from(addr.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set list of addresses.
|
||||
pub fn set_addrs<I>(mut self, addrs: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = SocketAddr>,
|
||||
{
|
||||
let mut addrs = VecDeque::from_iter(addrs);
|
||||
self.addr = if addrs.len() < 2 {
|
||||
ConnectAddrs::from(addrs.pop_front())
|
||||
} else {
|
||||
ConnectAddrs::Multi(addrs)
|
||||
};
|
||||
self
|
||||
}
|
||||
|
||||
/// Set local address to connection with.
|
||||
///
|
||||
/// Useful in situations where the IP address bound to a particular network interface is known.
|
||||
/// This would make sure the socket is opened through that interface.
|
||||
pub fn set_local_addr(mut self, addr: impl Into<IpAddr>) -> Self {
|
||||
self.local_addr = Some(addr.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns a reference to the connection request.
|
||||
pub fn request(&self) -> &R {
|
||||
&self.request
|
||||
}
|
||||
|
||||
/// Returns request hostname.
|
||||
pub fn hostname(&self) -> &str {
|
||||
self.request.hostname()
|
||||
}
|
||||
|
||||
/// Returns request port.
|
||||
pub fn port(&self) -> u16 {
|
||||
self.request.port().unwrap_or(self.port)
|
||||
}
|
||||
|
||||
/// Get borrowed iterator of resolved request addresses.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use std::net::SocketAddr;
|
||||
/// # use actix_tls::connect::ConnectInfo;
|
||||
/// let addr = SocketAddr::from(([127, 0, 0, 1], 4242));
|
||||
///
|
||||
/// let conn = ConnectInfo::new("localhost");
|
||||
/// let mut addrs = conn.addrs();
|
||||
/// assert!(addrs.next().is_none());
|
||||
///
|
||||
/// let conn = ConnectInfo::with_addr("localhost", addr);
|
||||
/// let mut addrs = conn.addrs();
|
||||
/// assert_eq!(addrs.next().unwrap(), addr);
|
||||
/// ```
|
||||
pub fn addrs(
|
||||
&self,
|
||||
) -> impl Iterator<Item = SocketAddr>
|
||||
+ ExactSizeIterator
|
||||
+ iter::FusedIterator
|
||||
+ Clone
|
||||
+ fmt::Debug
|
||||
+ '_ {
|
||||
match self.addr {
|
||||
ConnectAddrs::None => ConnectAddrsIter::None,
|
||||
ConnectAddrs::One(addr) => ConnectAddrsIter::One(addr),
|
||||
ConnectAddrs::Multi(ref addrs) => ConnectAddrsIter::Multi(addrs.iter()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Take owned iterator resolved request addresses.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use std::net::SocketAddr;
|
||||
/// # use actix_tls::connect::ConnectInfo;
|
||||
/// let addr = SocketAddr::from(([127, 0, 0, 1], 4242));
|
||||
///
|
||||
/// let mut conn = ConnectInfo::new("localhost");
|
||||
/// let mut addrs = conn.take_addrs();
|
||||
/// assert!(addrs.next().is_none());
|
||||
///
|
||||
/// let mut conn = ConnectInfo::with_addr("localhost", addr);
|
||||
/// let mut addrs = conn.take_addrs();
|
||||
/// assert_eq!(addrs.next().unwrap(), addr);
|
||||
/// ```
|
||||
pub fn take_addrs(
|
||||
&mut self,
|
||||
) -> impl Iterator<Item = SocketAddr>
|
||||
+ ExactSizeIterator
|
||||
+ iter::FusedIterator
|
||||
+ Clone
|
||||
+ fmt::Debug
|
||||
+ 'static {
|
||||
match mem::take(&mut self.addr) {
|
||||
ConnectAddrs::None => ConnectAddrsIter::None,
|
||||
ConnectAddrs::One(addr) => ConnectAddrsIter::One(addr),
|
||||
ConnectAddrs::Multi(addrs) => ConnectAddrsIter::MultiOwned(addrs.into_iter()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Host> From<R> for ConnectInfo<R> {
|
||||
fn from(addr: R) -> Self {
|
||||
ConnectInfo::new(addr)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Host> fmt::Display for ConnectInfo<R> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}:{}", self.hostname(), self.port())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_addr_iter_multi() {
|
||||
let localhost = SocketAddr::from((IpAddr::from(Ipv4Addr::LOCALHOST), 8080));
|
||||
let unspecified = SocketAddr::from((IpAddr::from(Ipv4Addr::UNSPECIFIED), 8080));
|
||||
|
||||
let mut addrs = VecDeque::new();
|
||||
addrs.push_back(localhost);
|
||||
addrs.push_back(unspecified);
|
||||
|
||||
let mut iter = ConnectAddrsIter::Multi(addrs.iter());
|
||||
assert_eq!(iter.next(), Some(localhost));
|
||||
assert_eq!(iter.next(), Some(unspecified));
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
let mut iter = ConnectAddrsIter::MultiOwned(addrs.into_iter());
|
||||
assert_eq!(iter.next(), Some(localhost));
|
||||
assert_eq!(iter.next(), Some(unspecified));
|
||||
assert_eq!(iter.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_addr_iter_single() {
|
||||
let localhost = SocketAddr::from((IpAddr::from(Ipv4Addr::LOCALHOST), 8080));
|
||||
|
||||
let mut iter = ConnectAddrsIter::One(localhost);
|
||||
assert_eq!(iter.next(), Some(localhost));
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
let mut iter = ConnectAddrsIter::None;
|
||||
assert_eq!(iter.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_local_addr() {
|
||||
let conn = ConnectInfo::new("hello").set_local_addr([127, 0, 0, 1]);
|
||||
assert_eq!(
|
||||
conn.local_addr.unwrap(),
|
||||
IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_ref() {
|
||||
let conn = ConnectInfo::new("hello");
|
||||
assert_eq!(conn.request(), &"hello")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_connect_addr_into_option() {
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], 4242));
|
||||
|
||||
let conn = ConnectInfo::new("hello").set_addr(None);
|
||||
let mut addrs = conn.addrs();
|
||||
assert!(addrs.next().is_none());
|
||||
|
||||
let conn = ConnectInfo::new("hello").set_addr(addr);
|
||||
let mut addrs = conn.addrs();
|
||||
assert_eq!(addrs.next().unwrap(), addr);
|
||||
|
||||
let conn = ConnectInfo::new("hello").set_addr(Some(addr));
|
||||
let mut addrs = conn.addrs();
|
||||
assert_eq!(addrs.next().unwrap(), addr);
|
||||
}
|
||||
}
|
@@ -1,44 +1,76 @@
|
||||
//! TCP and TLS connector services.
|
||||
//! TCP connector services for Actix ecosystem.
|
||||
//!
|
||||
//! # Stages of the TCP connector service:
|
||||
//! 1. Resolve [`Host`] (if needed) with given [`Resolver`] and collect list of socket addresses.
|
||||
//! 1. Establish TCP connection and return [`TcpStream`].
|
||||
//! - Resolve [`Address`] with given [`Resolver`] and collect list of socket addresses.
|
||||
//! - Establish TCP connection and return [`TcpStream`].
|
||||
//!
|
||||
//! # Stages of TLS connector services:
|
||||
//! 1. Resolve DNS and establish a [`TcpStream`] with the TCP connector service.
|
||||
//! 1. Wrap the stream and perform connect handshake with remote peer.
|
||||
//! 1. Return wrapped stream type that implements `AsyncRead` and `AsyncWrite`.
|
||||
//! - Establish [`TcpStream`] with connector service.
|
||||
//! - Wrap the stream and perform connect handshake with remote peer.
|
||||
//! - Return certain stream type that impls `AsyncRead` and `AsyncWrite`.
|
||||
//!
|
||||
//! # Package feature
|
||||
//! * `openssl` - enables TLS support via `openssl` crate
|
||||
//! * `rustls` - enables TLS support via `rustls` crate
|
||||
//!
|
||||
//! [`TcpStream`]: actix_rt::net::TcpStream
|
||||
|
||||
mod connect_addrs;
|
||||
mod connection;
|
||||
#[allow(clippy::module_inception)]
|
||||
mod connect;
|
||||
mod connector;
|
||||
mod error;
|
||||
mod host;
|
||||
mod info;
|
||||
mod resolve;
|
||||
mod resolver;
|
||||
pub mod tcp;
|
||||
|
||||
mod service;
|
||||
pub mod tls;
|
||||
#[doc(hidden)]
|
||||
pub use tls as ssl;
|
||||
#[cfg(feature = "uri")]
|
||||
mod uri;
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
pub mod openssl;
|
||||
use actix_rt::net::TcpStream;
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
|
||||
#[cfg(feature = "rustls")]
|
||||
pub mod rustls;
|
||||
pub use self::connect::{Address, Connect, Connection};
|
||||
pub use self::connector::{TcpConnector, TcpConnectorFactory};
|
||||
pub use self::error::ConnectError;
|
||||
pub use self::resolve::{Resolve, Resolver, ResolverFactory};
|
||||
pub use self::service::{ConnectService, ConnectServiceFactory};
|
||||
|
||||
#[cfg(feature = "native-tls")]
|
||||
pub mod native_tls;
|
||||
/// Create TCP connector service.
|
||||
pub fn new_connector<T: Address + 'static>(
|
||||
resolver: Resolver,
|
||||
) -> impl Service<Connect<T>, Response = Connection<T, TcpStream>, Error = ConnectError> + Clone
|
||||
{
|
||||
ConnectServiceFactory::new(resolver).service()
|
||||
}
|
||||
|
||||
pub use self::{
|
||||
connection::Connection,
|
||||
connector::{Connector, ConnectorService},
|
||||
error::ConnectError,
|
||||
host::Host,
|
||||
info::ConnectInfo,
|
||||
resolve::Resolve,
|
||||
resolver::{Resolver, ResolverService},
|
||||
};
|
||||
/// Create TCP connector service factory.
|
||||
pub fn new_connector_factory<T: Address + 'static>(
|
||||
resolver: Resolver,
|
||||
) -> impl ServiceFactory<
|
||||
Connect<T>,
|
||||
Config = (),
|
||||
Response = Connection<T, TcpStream>,
|
||||
Error = ConnectError,
|
||||
InitError = (),
|
||||
> + Clone {
|
||||
ConnectServiceFactory::new(resolver)
|
||||
}
|
||||
|
||||
/// Create connector service with default parameters.
|
||||
pub fn default_connector<T: Address + 'static>(
|
||||
) -> impl Service<Connect<T>, Response = Connection<T, TcpStream>, Error = ConnectError> + Clone
|
||||
{
|
||||
new_connector(Resolver::Default)
|
||||
}
|
||||
|
||||
/// Create connector service factory with default parameters.
|
||||
pub fn default_connector_factory<T: Address + 'static>() -> impl ServiceFactory<
|
||||
Connect<T>,
|
||||
Config = (),
|
||||
Response = Connection<T, TcpStream>,
|
||||
Error = ConnectError,
|
||||
InitError = (),
|
||||
> + Clone {
|
||||
new_connector_factory(Resolver::Default)
|
||||
}
|
||||
|
@@ -1,90 +0,0 @@
|
||||
//! Native-TLS based connector service.
|
||||
//!
|
||||
//! See [`TlsConnector`] for main connector service factory docs.
|
||||
|
||||
use std::io;
|
||||
|
||||
use actix_rt::net::ActixStream;
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use actix_utils::future::{ok, Ready};
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use tokio_native_tls::{
|
||||
native_tls::TlsConnector as NativeTlsConnector, TlsConnector as AsyncNativeTlsConnector,
|
||||
TlsStream as AsyncTlsStream,
|
||||
};
|
||||
use tracing::trace;
|
||||
|
||||
use crate::connect::{Connection, Host};
|
||||
|
||||
pub mod reexports {
|
||||
//! Re-exports from `native-tls` and `tokio-native-tls` that are useful for connectors.
|
||||
|
||||
pub use tokio_native_tls::{native_tls::TlsConnector, TlsStream as AsyncTlsStream};
|
||||
}
|
||||
|
||||
/// Connector service and factory using `native-tls`.
|
||||
#[derive(Clone)]
|
||||
pub struct TlsConnector {
|
||||
connector: AsyncNativeTlsConnector,
|
||||
}
|
||||
|
||||
impl TlsConnector {
|
||||
/// Constructs new connector service from a `native-tls` connector.
|
||||
///
|
||||
/// This type is it's own service factory, so it can be used in that setting, too.
|
||||
pub fn new(connector: NativeTlsConnector) -> Self {
|
||||
Self {
|
||||
connector: AsyncNativeTlsConnector::from(connector),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Host, IO> ServiceFactory<Connection<R, IO>> for TlsConnector
|
||||
where
|
||||
IO: ActixStream + 'static,
|
||||
{
|
||||
type Response = Connection<R, AsyncTlsStream<IO>>;
|
||||
type Error = io::Error;
|
||||
type Config = ();
|
||||
type Service = Self;
|
||||
type InitError = ();
|
||||
type Future = Ready<Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
ok(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// The `native-tls` connector is both it's ServiceFactory and Service impl type.
|
||||
/// As the factory and service share the same type and state.
|
||||
impl<R, IO> Service<Connection<R, IO>> for TlsConnector
|
||||
where
|
||||
R: Host,
|
||||
IO: ActixStream + 'static,
|
||||
{
|
||||
type Response = Connection<R, AsyncTlsStream<IO>>;
|
||||
type Error = io::Error;
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||
|
||||
actix_service::always_ready!();
|
||||
|
||||
fn call(&self, stream: Connection<R, IO>) -> Self::Future {
|
||||
let (io, stream) = stream.replace_io(());
|
||||
let connector = self.connector.clone();
|
||||
|
||||
Box::pin(async move {
|
||||
trace!("TLS handshake start for: {:?}", stream.hostname());
|
||||
connector
|
||||
.connect(stream.hostname(), io)
|
||||
.await
|
||||
.map(|res| {
|
||||
trace!("TLS handshake success: {:?}", stream.hostname());
|
||||
stream.replace_io(res).1
|
||||
})
|
||||
.map_err(|e| {
|
||||
trace!("TLS handshake error: {:?}", e);
|
||||
io::Error::new(io::ErrorKind::Other, format!("{}", e))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
@@ -1,151 +0,0 @@
|
||||
//! OpenSSL based connector service.
|
||||
//!
|
||||
//! See [`TlsConnector`] for main connector service factory docs.
|
||||
|
||||
use std::{
|
||||
future::Future,
|
||||
io,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use actix_rt::net::ActixStream;
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use actix_utils::future::{ok, Ready};
|
||||
use futures_core::ready;
|
||||
use openssl::ssl::SslConnector;
|
||||
use tokio_openssl::SslStream as AsyncSslStream;
|
||||
use tracing::trace;
|
||||
|
||||
use crate::connect::{Connection, Host};
|
||||
|
||||
pub mod reexports {
|
||||
//! Re-exports from `openssl` and `tokio-openssl` that are useful for connectors.
|
||||
|
||||
pub use openssl::ssl::{Error, HandshakeError, SslConnector, SslConnectorBuilder, SslMethod};
|
||||
pub use tokio_openssl::SslStream as AsyncSslStream;
|
||||
}
|
||||
|
||||
/// Connector service factory using `openssl`.
|
||||
pub struct TlsConnector {
|
||||
connector: SslConnector,
|
||||
}
|
||||
|
||||
impl TlsConnector {
|
||||
/// Constructs new connector service factory from an `openssl` connector.
|
||||
pub fn new(connector: SslConnector) -> Self {
|
||||
TlsConnector { connector }
|
||||
}
|
||||
|
||||
/// Constructs new connector service from an `openssl` connector.
|
||||
pub fn service(connector: SslConnector) -> TlsConnectorService {
|
||||
TlsConnectorService { connector }
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for TlsConnector {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
connector: self.connector.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R, IO> ServiceFactory<Connection<R, IO>> for TlsConnector
|
||||
where
|
||||
R: Host,
|
||||
IO: ActixStream + 'static,
|
||||
{
|
||||
type Response = Connection<R, AsyncSslStream<IO>>;
|
||||
type Error = io::Error;
|
||||
type Config = ();
|
||||
type Service = TlsConnectorService;
|
||||
type InitError = ();
|
||||
type Future = Ready<Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
ok(TlsConnectorService {
|
||||
connector: self.connector.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Connector service using `openssl`.
|
||||
pub struct TlsConnectorService {
|
||||
connector: SslConnector,
|
||||
}
|
||||
|
||||
impl Clone for TlsConnectorService {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
connector: self.connector.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R, IO> Service<Connection<R, IO>> for TlsConnectorService
|
||||
where
|
||||
R: Host,
|
||||
IO: ActixStream,
|
||||
{
|
||||
type Response = Connection<R, AsyncSslStream<IO>>;
|
||||
type Error = io::Error;
|
||||
type Future = ConnectFut<R, IO>;
|
||||
|
||||
actix_service::always_ready!();
|
||||
|
||||
fn call(&self, stream: Connection<R, IO>) -> Self::Future {
|
||||
trace!("TLS handshake start for: {:?}", stream.hostname());
|
||||
|
||||
let (io, stream) = stream.replace_io(());
|
||||
let host = stream.hostname();
|
||||
|
||||
let config = self
|
||||
.connector
|
||||
.configure()
|
||||
.expect("SSL connect configuration was invalid.");
|
||||
|
||||
let ssl = config
|
||||
.into_ssl(host)
|
||||
.expect("SSL connect configuration was invalid.");
|
||||
|
||||
ConnectFut {
|
||||
io: Some(AsyncSslStream::new(ssl, io).unwrap()),
|
||||
stream: Some(stream),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Connect future for OpenSSL service.
|
||||
#[doc(hidden)]
|
||||
pub struct ConnectFut<R, IO> {
|
||||
io: Option<AsyncSslStream<IO>>,
|
||||
stream: Option<Connection<R, ()>>,
|
||||
}
|
||||
|
||||
impl<R: Host, IO> Future for ConnectFut<R, IO>
|
||||
where
|
||||
R: Host,
|
||||
IO: ActixStream,
|
||||
{
|
||||
type Output = Result<Connection<R, AsyncSslStream<IO>>, io::Error>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.get_mut();
|
||||
|
||||
match ready!(Pin::new(this.io.as_mut().unwrap()).poll_connect(cx)) {
|
||||
Ok(_) => {
|
||||
let stream = this.stream.take().unwrap();
|
||||
trace!("TLS handshake success: {:?}", stream.hostname());
|
||||
Poll::Ready(Ok(stream.replace_io(this.io.take().unwrap()).1))
|
||||
}
|
||||
Err(err) => {
|
||||
trace!("TLS handshake error: {:?}", err);
|
||||
Poll::Ready(Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("{}", err),
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
207
actix-tls/src/connect/resolve.rs
Normal file → Executable file
207
actix-tls/src/connect/resolve.rs
Normal file → Executable file
@@ -1,12 +1,61 @@
|
||||
//! The [`Resolve`] trait.
|
||||
use std::{
|
||||
future::Future,
|
||||
io,
|
||||
net::SocketAddr,
|
||||
pin::Pin,
|
||||
rc::Rc,
|
||||
task::{Context, Poll},
|
||||
vec::IntoIter,
|
||||
};
|
||||
|
||||
use std::{error::Error as StdError, net::SocketAddr};
|
||||
use actix_rt::task::{spawn_blocking, JoinHandle};
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use futures_core::{future::LocalBoxFuture, ready};
|
||||
use log::trace;
|
||||
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use super::connect::{Address, Connect};
|
||||
use super::error::ConnectError;
|
||||
|
||||
/// Custom async DNS resolvers.
|
||||
/// DNS Resolver Service Factory
|
||||
#[derive(Clone)]
|
||||
pub struct ResolverFactory {
|
||||
resolver: Resolver,
|
||||
}
|
||||
|
||||
impl ResolverFactory {
|
||||
pub fn new(resolver: Resolver) -> Self {
|
||||
Self { resolver }
|
||||
}
|
||||
|
||||
pub fn service(&self) -> Resolver {
|
||||
self.resolver.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Address> ServiceFactory<Connect<T>> for ResolverFactory {
|
||||
type Response = Connect<T>;
|
||||
type Error = ConnectError;
|
||||
type Config = ();
|
||||
type Service = Resolver;
|
||||
type InitError = ();
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
let service = self.resolver.clone();
|
||||
Box::pin(async { Ok(service) })
|
||||
}
|
||||
}
|
||||
|
||||
/// DNS Resolver Service
|
||||
#[derive(Clone)]
|
||||
pub enum Resolver {
|
||||
Default,
|
||||
Custom(Rc<dyn Resolve>),
|
||||
}
|
||||
|
||||
/// An interface for custom async DNS resolvers.
|
||||
///
|
||||
/// # Examples
|
||||
/// # Usage
|
||||
/// ```
|
||||
/// use std::net::SocketAddr;
|
||||
///
|
||||
@@ -40,23 +89,155 @@ use futures_core::future::LocalBoxFuture;
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let my_resolver = MyResolver {
|
||||
/// let resolver = MyResolver {
|
||||
/// trust_dns: TokioAsyncResolver::tokio_from_system_conf().unwrap(),
|
||||
/// };
|
||||
///
|
||||
/// // wrap custom resolver
|
||||
/// let resolver = Resolver::custom(my_resolver);
|
||||
/// // construct custom resolver
|
||||
/// let resolver = Resolver::new_custom(resolver);
|
||||
///
|
||||
/// // pass custom resolver to connector builder.
|
||||
/// // connector would then be usable as a service or awc's connector.
|
||||
/// let connector = actix_tls::connect::new_connector::<&str>(resolver.clone());
|
||||
///
|
||||
/// // resolver can be passed to connector factory where returned service factory
|
||||
/// // can be used to construct new connector services for use in clients
|
||||
/// let factory = actix_tls::connect::Connector::new(resolver);
|
||||
/// let connector = factory.service();
|
||||
/// // can be used to construct new connector services.
|
||||
/// let factory = actix_tls::connect::new_connector_factory::<&str>(resolver);
|
||||
/// ```
|
||||
pub trait Resolve {
|
||||
/// Given DNS lookup information, returns a future that completes with socket information.
|
||||
fn lookup<'a>(
|
||||
&'a self,
|
||||
host: &'a str,
|
||||
port: u16,
|
||||
) -> LocalBoxFuture<'a, Result<Vec<SocketAddr>, Box<dyn StdError>>>;
|
||||
) -> LocalBoxFuture<'a, Result<Vec<SocketAddr>, Box<dyn std::error::Error>>>;
|
||||
}
|
||||
|
||||
impl Resolver {
|
||||
/// Constructor for custom Resolve trait object and use it as resolver.
|
||||
pub fn new_custom(resolver: impl Resolve + 'static) -> Self {
|
||||
Self::Custom(Rc::new(resolver))
|
||||
}
|
||||
|
||||
// look up with default resolver variant.
|
||||
fn look_up<T: Address>(req: &Connect<T>) -> JoinHandle<io::Result<IntoIter<SocketAddr>>> {
|
||||
let host = req.hostname();
|
||||
// TODO: Connect should always return host with port if possible.
|
||||
let host = if req
|
||||
.hostname()
|
||||
.splitn(2, ':')
|
||||
.last()
|
||||
.and_then(|p| p.parse::<u16>().ok())
|
||||
.map(|p| p == req.port())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
host.to_string()
|
||||
} else {
|
||||
format!("{}:{}", host, req.port())
|
||||
};
|
||||
|
||||
// run blocking DNS lookup in thread pool
|
||||
spawn_blocking(move || std::net::ToSocketAddrs::to_socket_addrs(&host))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Address> Service<Connect<T>> for Resolver {
|
||||
type Response = Connect<T>;
|
||||
type Error = ConnectError;
|
||||
type Future = ResolverFuture<T>;
|
||||
|
||||
actix_service::always_ready!();
|
||||
|
||||
fn call(&self, req: Connect<T>) -> Self::Future {
|
||||
if req.addr.is_some() {
|
||||
ResolverFuture::Connected(Some(req))
|
||||
} else if let Ok(ip) = req.hostname().parse() {
|
||||
let addr = SocketAddr::new(ip, req.port());
|
||||
let req = req.set_addr(Some(addr));
|
||||
ResolverFuture::Connected(Some(req))
|
||||
} else {
|
||||
trace!("DNS resolver: resolving host {:?}", req.hostname());
|
||||
|
||||
match self {
|
||||
Self::Default => {
|
||||
let fut = Self::look_up(&req);
|
||||
ResolverFuture::LookUp(fut, Some(req))
|
||||
}
|
||||
|
||||
Self::Custom(resolver) => {
|
||||
let resolver = Rc::clone(resolver);
|
||||
ResolverFuture::LookupCustom(Box::pin(async move {
|
||||
let addrs = resolver
|
||||
.lookup(req.hostname(), req.port())
|
||||
.await
|
||||
.map_err(ConnectError::Resolver)?;
|
||||
|
||||
let req = req.set_addrs(addrs);
|
||||
|
||||
if req.addr.is_none() {
|
||||
Err(ConnectError::NoRecords)
|
||||
} else {
|
||||
Ok(req)
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ResolverFuture<T: Address> {
|
||||
Connected(Option<Connect<T>>),
|
||||
LookUp(
|
||||
JoinHandle<io::Result<IntoIter<SocketAddr>>>,
|
||||
Option<Connect<T>>,
|
||||
),
|
||||
LookupCustom(LocalBoxFuture<'static, Result<Connect<T>, ConnectError>>),
|
||||
}
|
||||
|
||||
impl<T: Address> Future for ResolverFuture<T> {
|
||||
type Output = Result<Connect<T>, ConnectError>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
match self.get_mut() {
|
||||
Self::Connected(conn) => Poll::Ready(Ok(conn
|
||||
.take()
|
||||
.expect("ResolverFuture polled after finished"))),
|
||||
|
||||
Self::LookUp(fut, req) => {
|
||||
let res = match ready!(Pin::new(fut).poll(cx)) {
|
||||
Ok(Ok(res)) => Ok(res),
|
||||
Ok(Err(e)) => Err(ConnectError::Resolver(Box::new(e))),
|
||||
Err(e) => Err(ConnectError::Io(e.into())),
|
||||
};
|
||||
|
||||
let req = req.take().unwrap();
|
||||
|
||||
let addrs = res.map_err(|err| {
|
||||
trace!(
|
||||
"DNS resolver: failed to resolve host {:?} err: {:?}",
|
||||
req.hostname(),
|
||||
err
|
||||
);
|
||||
|
||||
err
|
||||
})?;
|
||||
|
||||
let req = req.set_addrs(addrs);
|
||||
|
||||
trace!(
|
||||
"DNS resolver: host {:?} resolved to {:?}",
|
||||
req.hostname(),
|
||||
req.addrs()
|
||||
);
|
||||
|
||||
if req.addr.is_none() {
|
||||
Poll::Ready(Err(ConnectError::NoRecords))
|
||||
} else {
|
||||
Poll::Ready(Ok(req))
|
||||
}
|
||||
}
|
||||
|
||||
Self::LookupCustom(fut) => fut.as_mut().poll(cx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,201 +0,0 @@
|
||||
use std::{
|
||||
future::Future,
|
||||
io,
|
||||
net::SocketAddr,
|
||||
pin::Pin,
|
||||
rc::Rc,
|
||||
task::{Context, Poll},
|
||||
vec::IntoIter,
|
||||
};
|
||||
|
||||
use actix_rt::task::{spawn_blocking, JoinHandle};
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use actix_utils::future::{ok, Ready};
|
||||
use futures_core::{future::LocalBoxFuture, ready};
|
||||
use tracing::trace;
|
||||
|
||||
use super::{ConnectError, ConnectInfo, Host, Resolve};
|
||||
|
||||
/// DNS resolver service factory.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Resolver {
|
||||
resolver: ResolverService,
|
||||
}
|
||||
|
||||
impl Resolver {
|
||||
/// Constructs a new resolver factory with a custom resolver.
|
||||
pub fn custom(resolver: impl Resolve + 'static) -> Self {
|
||||
Self {
|
||||
resolver: ResolverService::custom(resolver),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new resolver service.
|
||||
pub fn service(&self) -> ResolverService {
|
||||
self.resolver.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Host> ServiceFactory<ConnectInfo<R>> for Resolver {
|
||||
type Response = ConnectInfo<R>;
|
||||
type Error = ConnectError;
|
||||
type Config = ();
|
||||
type Service = ResolverService;
|
||||
type InitError = ();
|
||||
type Future = Ready<Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
ok(self.resolver.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum ResolverKind {
|
||||
/// Built-in DNS resolver.
|
||||
///
|
||||
/// See [`std::net::ToSocketAddrs`] trait.
|
||||
Default,
|
||||
|
||||
/// Custom, user-provided DNS resolver.
|
||||
Custom(Rc<dyn Resolve>),
|
||||
}
|
||||
|
||||
impl Default for ResolverKind {
|
||||
fn default() -> Self {
|
||||
Self::Default
|
||||
}
|
||||
}
|
||||
|
||||
/// DNS resolver service.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct ResolverService {
|
||||
kind: ResolverKind,
|
||||
}
|
||||
|
||||
impl ResolverService {
|
||||
/// Constructor for custom Resolve trait object and use it as resolver.
|
||||
pub fn custom(resolver: impl Resolve + 'static) -> Self {
|
||||
Self {
|
||||
kind: ResolverKind::Custom(Rc::new(resolver)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve DNS with default resolver.
|
||||
fn default_lookup<R: Host>(
|
||||
req: &ConnectInfo<R>,
|
||||
) -> JoinHandle<io::Result<IntoIter<SocketAddr>>> {
|
||||
// reconstruct host; concatenate hostname and port together
|
||||
let host = format!("{}:{}", req.hostname(), req.port());
|
||||
|
||||
// run blocking DNS lookup in thread pool since DNS lookups can take upwards of seconds on
|
||||
// some platforms if conditions are poor and OS-level cache is not populated
|
||||
spawn_blocking(move || std::net::ToSocketAddrs::to_socket_addrs(&host))
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Host> Service<ConnectInfo<R>> for ResolverService {
|
||||
type Response = ConnectInfo<R>;
|
||||
type Error = ConnectError;
|
||||
type Future = ResolverFut<R>;
|
||||
|
||||
actix_service::always_ready!();
|
||||
|
||||
fn call(&self, req: ConnectInfo<R>) -> Self::Future {
|
||||
if req.addr.is_resolved() {
|
||||
// socket address(es) already resolved; return existing connection request
|
||||
ResolverFut::Resolved(Some(req))
|
||||
} else if let Ok(ip) = req.hostname().parse() {
|
||||
// request hostname is valid ip address; add address to request and return
|
||||
let addr = SocketAddr::new(ip, req.port());
|
||||
let req = req.set_addr(Some(addr));
|
||||
ResolverFut::Resolved(Some(req))
|
||||
} else {
|
||||
trace!("DNS resolver: resolving host {:?}", req.hostname());
|
||||
|
||||
match &self.kind {
|
||||
ResolverKind::Default => {
|
||||
let fut = Self::default_lookup(&req);
|
||||
ResolverFut::LookUp(fut, Some(req))
|
||||
}
|
||||
|
||||
ResolverKind::Custom(resolver) => {
|
||||
let resolver = Rc::clone(resolver);
|
||||
|
||||
ResolverFut::LookupCustom(Box::pin(async move {
|
||||
let addrs = resolver
|
||||
.lookup(req.hostname(), req.port())
|
||||
.await
|
||||
.map_err(ConnectError::Resolver)?;
|
||||
|
||||
let req = req.set_addrs(addrs);
|
||||
|
||||
if req.addr.is_unresolved() {
|
||||
Err(ConnectError::NoRecords)
|
||||
} else {
|
||||
Ok(req)
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Future for resolver service.
|
||||
#[doc(hidden)]
|
||||
pub enum ResolverFut<R: Host> {
|
||||
Resolved(Option<ConnectInfo<R>>),
|
||||
LookUp(
|
||||
JoinHandle<io::Result<IntoIter<SocketAddr>>>,
|
||||
Option<ConnectInfo<R>>,
|
||||
),
|
||||
LookupCustom(LocalBoxFuture<'static, Result<ConnectInfo<R>, ConnectError>>),
|
||||
}
|
||||
|
||||
impl<R: Host> Future for ResolverFut<R> {
|
||||
type Output = Result<ConnectInfo<R>, ConnectError>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
match self.get_mut() {
|
||||
Self::Resolved(conn) => Poll::Ready(Ok(conn
|
||||
.take()
|
||||
.expect("ResolverFuture polled after finished"))),
|
||||
|
||||
Self::LookUp(fut, req) => {
|
||||
let res = match ready!(Pin::new(fut).poll(cx)) {
|
||||
Ok(Ok(res)) => Ok(res),
|
||||
Ok(Err(err)) => Err(ConnectError::Resolver(Box::new(err))),
|
||||
Err(err) => Err(ConnectError::Io(err.into())),
|
||||
};
|
||||
|
||||
let req = req.take().unwrap();
|
||||
|
||||
let addrs = res.map_err(|err| {
|
||||
trace!(
|
||||
"DNS resolver: failed to resolve host {:?} err: {:?}",
|
||||
req.hostname(),
|
||||
err
|
||||
);
|
||||
|
||||
err
|
||||
})?;
|
||||
|
||||
let req = req.set_addrs(addrs);
|
||||
|
||||
trace!(
|
||||
"DNS resolver: host {:?} resolved to {:?}",
|
||||
req.hostname(),
|
||||
req.addrs()
|
||||
);
|
||||
|
||||
if req.addr.is_unresolved() {
|
||||
Poll::Ready(Err(ConnectError::NoRecords))
|
||||
} else {
|
||||
Poll::Ready(Ok(req))
|
||||
}
|
||||
}
|
||||
|
||||
Self::LookupCustom(fut) => fut.as_mut().poll(cx),
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,149 +0,0 @@
|
||||
//! Rustls based connector service.
|
||||
//!
|
||||
//! See [`TlsConnector`] for main connector service factory docs.
|
||||
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
future::Future,
|
||||
io,
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use actix_rt::net::ActixStream;
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use actix_utils::future::{ok, Ready};
|
||||
use futures_core::ready;
|
||||
use tokio_rustls::{
|
||||
client::TlsStream as AsyncTlsStream,
|
||||
rustls::{client::ServerName, ClientConfig, OwnedTrustAnchor, RootCertStore},
|
||||
Connect as RustlsConnect, TlsConnector as RustlsTlsConnector,
|
||||
};
|
||||
use tracing::trace;
|
||||
use webpki_roots::TLS_SERVER_ROOTS;
|
||||
|
||||
use crate::connect::{Connection, Host};
|
||||
|
||||
pub mod reexports {
|
||||
//! Re-exports from `rustls` and `webpki_roots` that are useful for connectors.
|
||||
|
||||
pub use tokio_rustls::{client::TlsStream as AsyncTlsStream, rustls::ClientConfig};
|
||||
pub use webpki_roots::TLS_SERVER_ROOTS;
|
||||
}
|
||||
|
||||
/// Returns standard root certificates from `webpki-roots` crate as a rustls certificate store.
|
||||
pub fn webpki_roots_cert_store() -> RootCertStore {
|
||||
let mut root_certs = RootCertStore::empty();
|
||||
for cert in TLS_SERVER_ROOTS.0 {
|
||||
let cert = OwnedTrustAnchor::from_subject_spki_name_constraints(
|
||||
cert.subject,
|
||||
cert.spki,
|
||||
cert.name_constraints,
|
||||
);
|
||||
let certs = vec![cert].into_iter();
|
||||
root_certs.add_server_trust_anchors(certs);
|
||||
}
|
||||
root_certs
|
||||
}
|
||||
|
||||
/// Connector service factory using `rustls`.
|
||||
#[derive(Clone)]
|
||||
pub struct TlsConnector {
|
||||
connector: Arc<ClientConfig>,
|
||||
}
|
||||
|
||||
impl TlsConnector {
|
||||
/// Constructs new connector service factory from a `rustls` client configuration.
|
||||
pub fn new(connector: Arc<ClientConfig>) -> Self {
|
||||
TlsConnector { connector }
|
||||
}
|
||||
|
||||
/// Constructs new connector service from a `rustls` client configuration.
|
||||
pub fn service(connector: Arc<ClientConfig>) -> TlsConnectorService {
|
||||
TlsConnectorService { connector }
|
||||
}
|
||||
}
|
||||
|
||||
impl<R, IO> ServiceFactory<Connection<R, IO>> for TlsConnector
|
||||
where
|
||||
R: Host,
|
||||
IO: ActixStream + 'static,
|
||||
{
|
||||
type Response = Connection<R, AsyncTlsStream<IO>>;
|
||||
type Error = io::Error;
|
||||
type Config = ();
|
||||
type Service = TlsConnectorService;
|
||||
type InitError = ();
|
||||
type Future = Ready<Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
ok(TlsConnectorService {
|
||||
connector: self.connector.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Connector service using `rustls`.
|
||||
#[derive(Clone)]
|
||||
pub struct TlsConnectorService {
|
||||
connector: Arc<ClientConfig>,
|
||||
}
|
||||
|
||||
impl<R, IO> Service<Connection<R, IO>> for TlsConnectorService
|
||||
where
|
||||
R: Host,
|
||||
IO: ActixStream,
|
||||
{
|
||||
type Response = Connection<R, AsyncTlsStream<IO>>;
|
||||
type Error = io::Error;
|
||||
type Future = ConnectFut<R, IO>;
|
||||
|
||||
actix_service::always_ready!();
|
||||
|
||||
fn call(&self, connection: Connection<R, IO>) -> Self::Future {
|
||||
trace!("TLS handshake start for: {:?}", connection.hostname());
|
||||
let (stream, connection) = connection.replace_io(());
|
||||
|
||||
match ServerName::try_from(connection.hostname()) {
|
||||
Ok(host) => ConnectFut::Future {
|
||||
connect: RustlsTlsConnector::from(self.connector.clone()).connect(host, stream),
|
||||
connection: Some(connection),
|
||||
},
|
||||
Err(_) => ConnectFut::InvalidDns,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Connect future for Rustls service.
|
||||
#[doc(hidden)]
|
||||
pub enum ConnectFut<R, IO> {
|
||||
/// See issue <https://github.com/briansmith/webpki/issues/54>
|
||||
InvalidDns,
|
||||
Future {
|
||||
connect: RustlsConnect<IO>,
|
||||
connection: Option<Connection<R, ()>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<R, IO> Future for ConnectFut<R, IO>
|
||||
where
|
||||
R: Host,
|
||||
IO: ActixStream,
|
||||
{
|
||||
type Output = Result<Connection<R, AsyncTlsStream<IO>>, io::Error>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
match self.get_mut() {
|
||||
Self::InvalidDns => Poll::Ready(Err(
|
||||
io::Error::new(io::ErrorKind::Other, "rustls currently only handles hostname-based connections. See https://github.com/briansmith/webpki/issues/54")
|
||||
)),
|
||||
Self::Future { connect, connection } => {
|
||||
let stream = ready!(Pin::new(connect).poll(cx))?;
|
||||
let connection = connection.take().unwrap();
|
||||
trace!("TLS handshake success: {:?}", connection.hostname());
|
||||
Poll::Ready(Ok(connection.replace_io(stream).1))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
129
actix-tls/src/connect/service.rs
Executable file
129
actix-tls/src/connect/service.rs
Executable file
@@ -0,0 +1,129 @@
|
||||
use std::{
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use actix_rt::net::TcpStream;
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use futures_core::{future::LocalBoxFuture, ready};
|
||||
|
||||
use super::connect::{Address, Connect, Connection};
|
||||
use super::connector::{TcpConnector, TcpConnectorFactory};
|
||||
use super::error::ConnectError;
|
||||
use super::resolve::{Resolver, ResolverFactory};
|
||||
|
||||
pub struct ConnectServiceFactory {
|
||||
tcp: TcpConnectorFactory,
|
||||
resolver: ResolverFactory,
|
||||
}
|
||||
|
||||
impl ConnectServiceFactory {
|
||||
/// Construct new ConnectService factory
|
||||
pub fn new(resolver: Resolver) -> Self {
|
||||
ConnectServiceFactory {
|
||||
tcp: TcpConnectorFactory,
|
||||
resolver: ResolverFactory::new(resolver),
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct new service
|
||||
pub fn service(&self) -> ConnectService {
|
||||
ConnectService {
|
||||
tcp: self.tcp.service(),
|
||||
resolver: self.resolver.service(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for ConnectServiceFactory {
|
||||
fn clone(&self) -> Self {
|
||||
ConnectServiceFactory {
|
||||
tcp: self.tcp,
|
||||
resolver: self.resolver.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Address> ServiceFactory<Connect<T>> for ConnectServiceFactory {
|
||||
type Response = Connection<T, TcpStream>;
|
||||
type Error = ConnectError;
|
||||
type Config = ();
|
||||
type Service = ConnectService;
|
||||
type InitError = ();
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
let service = self.service();
|
||||
Box::pin(async { Ok(service) })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ConnectService {
|
||||
tcp: TcpConnector,
|
||||
resolver: Resolver,
|
||||
}
|
||||
|
||||
impl<T: Address> Service<Connect<T>> for ConnectService {
|
||||
type Response = Connection<T, TcpStream>;
|
||||
type Error = ConnectError;
|
||||
type Future = ConnectServiceResponse<T>;
|
||||
|
||||
actix_service::always_ready!();
|
||||
|
||||
fn call(&self, req: Connect<T>) -> Self::Future {
|
||||
ConnectServiceResponse {
|
||||
fut: ConnectFuture::Resolve(self.resolver.call(req)),
|
||||
tcp: self.tcp,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// helper enum to generic over futures of resolve and connect phase.
|
||||
pub(crate) enum ConnectFuture<T: Address> {
|
||||
Resolve(<Resolver as Service<Connect<T>>>::Future),
|
||||
Connect(<TcpConnector as Service<Connect<T>>>::Future),
|
||||
}
|
||||
|
||||
// helper enum to contain the future output of ConnectFuture
|
||||
pub(crate) enum ConnectOutput<T: Address> {
|
||||
Resolved(Connect<T>),
|
||||
Connected(Connection<T, TcpStream>),
|
||||
}
|
||||
|
||||
impl<T: Address> ConnectFuture<T> {
|
||||
fn poll_connect(
|
||||
&mut self,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Result<ConnectOutput<T>, ConnectError>> {
|
||||
match self {
|
||||
ConnectFuture::Resolve(ref mut fut) => {
|
||||
Pin::new(fut).poll(cx).map_ok(ConnectOutput::Resolved)
|
||||
}
|
||||
ConnectFuture::Connect(ref mut fut) => {
|
||||
Pin::new(fut).poll(cx).map_ok(ConnectOutput::Connected)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConnectServiceResponse<T: Address> {
|
||||
fut: ConnectFuture<T>,
|
||||
tcp: TcpConnector,
|
||||
}
|
||||
|
||||
impl<T: Address> Future for ConnectServiceResponse<T> {
|
||||
type Output = Result<Connection<T, TcpStream>, ConnectError>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
loop {
|
||||
match ready!(self.fut.poll_connect(cx))? {
|
||||
ConnectOutput::Resolved(res) => {
|
||||
self.fut = ConnectFuture::Connect(self.tcp.call(res));
|
||||
}
|
||||
ConnectOutput::Connected(res) => return Poll::Ready(Ok(res)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,204 +0,0 @@
|
||||
//! TCP connector service.
|
||||
//!
|
||||
//! See [`TcpConnector`] for main connector service factory docs.
|
||||
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
future::Future,
|
||||
io,
|
||||
net::{IpAddr, SocketAddr, SocketAddrV4, SocketAddrV6},
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use actix_rt::net::{TcpSocket, TcpStream};
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use actix_utils::future::{ok, Ready};
|
||||
use futures_core::ready;
|
||||
use tokio_util::sync::ReusableBoxFuture;
|
||||
use tracing::{error, trace};
|
||||
|
||||
use super::{connect_addrs::ConnectAddrs, error::ConnectError, ConnectInfo, Connection, Host};
|
||||
|
||||
/// TCP connector service factory.
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
#[non_exhaustive]
|
||||
pub struct TcpConnector;
|
||||
|
||||
impl TcpConnector {
|
||||
/// Returns a new TCP connector service.
|
||||
pub fn service(&self) -> TcpConnectorService {
|
||||
TcpConnectorService::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Host> ServiceFactory<ConnectInfo<R>> for TcpConnector {
|
||||
type Response = Connection<R, TcpStream>;
|
||||
type Error = ConnectError;
|
||||
type Config = ();
|
||||
type Service = TcpConnectorService;
|
||||
type InitError = ();
|
||||
type Future = Ready<Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
ok(self.service())
|
||||
}
|
||||
}
|
||||
|
||||
/// TCP connector service.
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
#[non_exhaustive]
|
||||
pub struct TcpConnectorService;
|
||||
|
||||
impl<R: Host> Service<ConnectInfo<R>> for TcpConnectorService {
|
||||
type Response = Connection<R, TcpStream>;
|
||||
type Error = ConnectError;
|
||||
type Future = TcpConnectorFut<R>;
|
||||
|
||||
actix_service::always_ready!();
|
||||
|
||||
fn call(&self, req: ConnectInfo<R>) -> Self::Future {
|
||||
let port = req.port();
|
||||
|
||||
let ConnectInfo {
|
||||
request: req,
|
||||
addr,
|
||||
local_addr,
|
||||
..
|
||||
} = req;
|
||||
|
||||
TcpConnectorFut::new(req, port, local_addr, addr)
|
||||
}
|
||||
}
|
||||
|
||||
/// Connect future for TCP service.
|
||||
#[doc(hidden)]
|
||||
pub enum TcpConnectorFut<R> {
|
||||
Response {
|
||||
req: Option<R>,
|
||||
port: u16,
|
||||
local_addr: Option<IpAddr>,
|
||||
addrs: Option<VecDeque<SocketAddr>>,
|
||||
stream: ReusableBoxFuture<'static, Result<TcpStream, io::Error>>,
|
||||
},
|
||||
|
||||
Error(Option<ConnectError>),
|
||||
}
|
||||
|
||||
impl<R: Host> TcpConnectorFut<R> {
|
||||
pub(crate) fn new(
|
||||
req: R,
|
||||
port: u16,
|
||||
local_addr: Option<IpAddr>,
|
||||
addr: ConnectAddrs,
|
||||
) -> TcpConnectorFut<R> {
|
||||
if addr.is_unresolved() {
|
||||
error!("TCP connector: unresolved connection address");
|
||||
return TcpConnectorFut::Error(Some(ConnectError::Unresolved));
|
||||
}
|
||||
|
||||
trace!(
|
||||
"TCP connector: connecting to {} on port {}",
|
||||
req.hostname(),
|
||||
port
|
||||
);
|
||||
|
||||
match addr {
|
||||
ConnectAddrs::None => unreachable!("none variant already checked"),
|
||||
|
||||
ConnectAddrs::One(addr) => TcpConnectorFut::Response {
|
||||
req: Some(req),
|
||||
port,
|
||||
local_addr,
|
||||
addrs: None,
|
||||
stream: ReusableBoxFuture::new(connect(addr, local_addr)),
|
||||
},
|
||||
|
||||
// When resolver returns multiple socket addr for request they would be popped from
|
||||
// front end of queue and returns with the first successful TCP connection.
|
||||
ConnectAddrs::Multi(mut addrs) => {
|
||||
let addr = addrs.pop_front().unwrap();
|
||||
|
||||
TcpConnectorFut::Response {
|
||||
req: Some(req),
|
||||
port,
|
||||
local_addr,
|
||||
addrs: Some(addrs),
|
||||
stream: ReusableBoxFuture::new(connect(addr, local_addr)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Host> Future for TcpConnectorFut<R> {
|
||||
type Output = Result<Connection<R, TcpStream>, ConnectError>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
match self.get_mut() {
|
||||
TcpConnectorFut::Error(err) => Poll::Ready(Err(err.take().unwrap())),
|
||||
|
||||
TcpConnectorFut::Response {
|
||||
req,
|
||||
port,
|
||||
local_addr,
|
||||
addrs,
|
||||
stream,
|
||||
} => loop {
|
||||
match ready!(stream.poll(cx)) {
|
||||
Ok(sock) => {
|
||||
let req = req.take().unwrap();
|
||||
|
||||
trace!(
|
||||
"TCP connector: successfully connected to {:?} - {:?}",
|
||||
req.hostname(),
|
||||
sock.peer_addr()
|
||||
);
|
||||
|
||||
return Poll::Ready(Ok(Connection::new(req, sock)));
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
trace!(
|
||||
"TCP connector: failed to connect to {:?} port: {}",
|
||||
req.as_ref().unwrap().hostname(),
|
||||
port,
|
||||
);
|
||||
|
||||
if let Some(addr) = addrs.as_mut().and_then(|addrs| addrs.pop_front()) {
|
||||
stream.set(connect(addr, *local_addr));
|
||||
} else {
|
||||
return Poll::Ready(Err(ConnectError::Io(err)));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn connect(addr: SocketAddr, local_addr: Option<IpAddr>) -> io::Result<TcpStream> {
|
||||
// use local addr if connect asks for it
|
||||
match local_addr {
|
||||
Some(ip_addr) => {
|
||||
let socket = match ip_addr {
|
||||
IpAddr::V4(ip_addr) => {
|
||||
let socket = TcpSocket::new_v4()?;
|
||||
let addr = SocketAddr::V4(SocketAddrV4::new(ip_addr, 0));
|
||||
socket.bind(addr)?;
|
||||
socket
|
||||
}
|
||||
IpAddr::V6(ip_addr) => {
|
||||
let socket = TcpSocket::new_v6()?;
|
||||
let addr = SocketAddr::V6(SocketAddrV6::new(ip_addr, 0, 0, 0));
|
||||
socket.bind(addr)?;
|
||||
socket
|
||||
}
|
||||
};
|
||||
|
||||
socket.connect(addr).await
|
||||
}
|
||||
|
||||
None => TcpStream::connect(addr).await,
|
||||
}
|
||||
}
|
10
actix-tls/src/connect/tls/mod.rs
Normal file
10
actix-tls/src/connect/tls/mod.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
//! TLS Services
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
pub mod openssl;
|
||||
|
||||
#[cfg(feature = "rustls")]
|
||||
pub mod rustls;
|
||||
|
||||
#[cfg(feature = "native-tls")]
|
||||
pub mod native_tls;
|
88
actix-tls/src/connect/tls/native_tls.rs
Normal file
88
actix-tls/src/connect/tls/native_tls.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
use std::io;
|
||||
|
||||
use actix_rt::net::ActixStream;
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use log::trace;
|
||||
use tokio_native_tls::{TlsConnector as TokioNativetlsConnector, TlsStream};
|
||||
|
||||
pub use tokio_native_tls::native_tls::TlsConnector;
|
||||
|
||||
use crate::connect::{Address, Connection};
|
||||
|
||||
/// Native-tls connector factory and service
|
||||
pub struct NativetlsConnector {
|
||||
connector: TokioNativetlsConnector,
|
||||
}
|
||||
|
||||
impl NativetlsConnector {
|
||||
pub fn new(connector: TlsConnector) -> Self {
|
||||
Self {
|
||||
connector: TokioNativetlsConnector::from(connector),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NativetlsConnector {
|
||||
pub fn service(connector: TlsConnector) -> Self {
|
||||
Self::new(connector)
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for NativetlsConnector {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
connector: self.connector.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Address, U> ServiceFactory<Connection<T, U>> for NativetlsConnector
|
||||
where
|
||||
U: ActixStream + 'static,
|
||||
{
|
||||
type Response = Connection<T, TlsStream<U>>;
|
||||
type Error = io::Error;
|
||||
type Config = ();
|
||||
type Service = Self;
|
||||
type InitError = ();
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
let connector = self.clone();
|
||||
Box::pin(async { Ok(connector) })
|
||||
}
|
||||
}
|
||||
|
||||
// NativetlsConnector is both it's ServiceFactory and Service impl type.
|
||||
// As the factory and service share the same type and state.
|
||||
impl<T, U> Service<Connection<T, U>> for NativetlsConnector
|
||||
where
|
||||
T: Address,
|
||||
U: ActixStream + 'static,
|
||||
{
|
||||
type Response = Connection<T, TlsStream<U>>;
|
||||
type Error = io::Error;
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||
|
||||
actix_service::always_ready!();
|
||||
|
||||
fn call(&self, stream: Connection<T, U>) -> Self::Future {
|
||||
let (io, stream) = stream.replace_io(());
|
||||
let connector = self.connector.clone();
|
||||
Box::pin(async move {
|
||||
trace!("SSL Handshake start for: {:?}", stream.host());
|
||||
connector
|
||||
.connect(stream.host(), io)
|
||||
.await
|
||||
.map(|res| {
|
||||
trace!("SSL Handshake success: {:?}", stream.host());
|
||||
stream.replace_io(res).1
|
||||
})
|
||||
.map_err(|e| {
|
||||
trace!("SSL Handshake error: {:?}", e);
|
||||
io::Error::new(io::ErrorKind::Other, format!("{}", e))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
130
actix-tls/src/connect/tls/openssl.rs
Executable file
130
actix-tls/src/connect/tls/openssl.rs
Executable file
@@ -0,0 +1,130 @@
|
||||
use std::{
|
||||
future::Future,
|
||||
io,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use actix_rt::net::ActixStream;
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use futures_core::{future::LocalBoxFuture, ready};
|
||||
use log::trace;
|
||||
|
||||
pub use openssl::ssl::{Error as SslError, HandshakeError, SslConnector, SslMethod};
|
||||
pub use tokio_openssl::SslStream;
|
||||
|
||||
use crate::connect::{Address, Connection};
|
||||
|
||||
/// OpenSSL connector factory
|
||||
pub struct OpensslConnector {
|
||||
connector: SslConnector,
|
||||
}
|
||||
|
||||
impl OpensslConnector {
|
||||
pub fn new(connector: SslConnector) -> Self {
|
||||
OpensslConnector { connector }
|
||||
}
|
||||
|
||||
pub fn service(connector: SslConnector) -> OpensslConnectorService {
|
||||
OpensslConnectorService { connector }
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for OpensslConnector {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
connector: self.connector.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> ServiceFactory<Connection<T, U>> for OpensslConnector
|
||||
where
|
||||
T: Address,
|
||||
U: ActixStream + 'static,
|
||||
{
|
||||
type Response = Connection<T, SslStream<U>>;
|
||||
type Error = io::Error;
|
||||
type Config = ();
|
||||
type Service = OpensslConnectorService;
|
||||
type InitError = ();
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
let connector = self.connector.clone();
|
||||
Box::pin(async { Ok(OpensslConnectorService { connector }) })
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OpensslConnectorService {
|
||||
connector: SslConnector,
|
||||
}
|
||||
|
||||
impl Clone for OpensslConnectorService {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
connector: self.connector.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Service<Connection<T, U>> for OpensslConnectorService
|
||||
where
|
||||
T: Address,
|
||||
U: ActixStream,
|
||||
{
|
||||
type Response = Connection<T, SslStream<U>>;
|
||||
type Error = io::Error;
|
||||
type Future = ConnectAsyncExt<T, U>;
|
||||
|
||||
actix_service::always_ready!();
|
||||
|
||||
fn call(&self, stream: Connection<T, U>) -> Self::Future {
|
||||
trace!("SSL Handshake start for: {:?}", stream.host());
|
||||
let (io, stream) = stream.replace_io(());
|
||||
let host = stream.host();
|
||||
|
||||
let config = self
|
||||
.connector
|
||||
.configure()
|
||||
.expect("SSL connect configuration was invalid.");
|
||||
|
||||
let ssl = config
|
||||
.into_ssl(host)
|
||||
.expect("SSL connect configuration was invalid.");
|
||||
|
||||
ConnectAsyncExt {
|
||||
io: Some(SslStream::new(ssl, io).unwrap()),
|
||||
stream: Some(stream),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConnectAsyncExt<T, U> {
|
||||
io: Option<SslStream<U>>,
|
||||
stream: Option<Connection<T, ()>>,
|
||||
}
|
||||
|
||||
impl<T: Address, U> Future for ConnectAsyncExt<T, U>
|
||||
where
|
||||
T: Address,
|
||||
U: ActixStream,
|
||||
{
|
||||
type Output = Result<Connection<T, SslStream<U>>, io::Error>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.get_mut();
|
||||
|
||||
match ready!(Pin::new(this.io.as_mut().unwrap()).poll_connect(cx)) {
|
||||
Ok(_) => {
|
||||
let stream = this.stream.take().unwrap();
|
||||
trace!("SSL Handshake success: {:?}", stream.host());
|
||||
Poll::Ready(Ok(stream.replace_io(this.io.take().unwrap()).1))
|
||||
}
|
||||
Err(e) => {
|
||||
trace!("SSL Handshake error: {:?}", e);
|
||||
Poll::Ready(Err(io::Error::new(io::ErrorKind::Other, format!("{}", e))))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
146
actix-tls/src/connect/tls/rustls.rs
Executable file
146
actix-tls/src/connect/tls/rustls.rs
Executable file
@@ -0,0 +1,146 @@
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
future::Future,
|
||||
io,
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
pub use tokio_rustls::{client::TlsStream, rustls::ClientConfig};
|
||||
pub use webpki_roots::TLS_SERVER_ROOTS;
|
||||
|
||||
use actix_rt::net::ActixStream;
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use futures_core::{future::LocalBoxFuture, ready};
|
||||
use log::trace;
|
||||
use tokio_rustls::rustls::{client::ServerName, OwnedTrustAnchor, RootCertStore};
|
||||
use tokio_rustls::{Connect, TlsConnector};
|
||||
|
||||
use crate::connect::{Address, Connection};
|
||||
|
||||
/// Returns standard root certificates from `webpki-roots` crate as a rustls certificate store.
|
||||
pub fn webpki_roots_cert_store() -> RootCertStore {
|
||||
let mut root_certs = RootCertStore::empty();
|
||||
for cert in TLS_SERVER_ROOTS.0 {
|
||||
let cert = OwnedTrustAnchor::from_subject_spki_name_constraints(
|
||||
cert.subject,
|
||||
cert.spki,
|
||||
cert.name_constraints,
|
||||
);
|
||||
let certs = vec![cert].into_iter();
|
||||
root_certs.add_server_trust_anchors(certs);
|
||||
}
|
||||
root_certs
|
||||
}
|
||||
|
||||
/// Rustls connector factory
|
||||
pub struct RustlsConnector {
|
||||
connector: Arc<ClientConfig>,
|
||||
}
|
||||
|
||||
impl RustlsConnector {
|
||||
pub fn new(connector: Arc<ClientConfig>) -> Self {
|
||||
RustlsConnector { connector }
|
||||
}
|
||||
}
|
||||
|
||||
impl RustlsConnector {
|
||||
pub fn service(connector: Arc<ClientConfig>) -> RustlsConnectorService {
|
||||
RustlsConnectorService { connector }
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for RustlsConnector {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
connector: self.connector.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> ServiceFactory<Connection<T, U>> for RustlsConnector
|
||||
where
|
||||
T: Address,
|
||||
U: ActixStream + 'static,
|
||||
{
|
||||
type Response = Connection<T, TlsStream<U>>;
|
||||
type Error = io::Error;
|
||||
type Config = ();
|
||||
type Service = RustlsConnectorService;
|
||||
type InitError = ();
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
let connector = self.connector.clone();
|
||||
Box::pin(async { Ok(RustlsConnectorService { connector }) })
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RustlsConnectorService {
|
||||
connector: Arc<ClientConfig>,
|
||||
}
|
||||
|
||||
impl Clone for RustlsConnectorService {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
connector: self.connector.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Service<Connection<T, U>> for RustlsConnectorService
|
||||
where
|
||||
T: Address,
|
||||
U: ActixStream,
|
||||
{
|
||||
type Response = Connection<T, TlsStream<U>>;
|
||||
type Error = io::Error;
|
||||
type Future = RustlsConnectorServiceFuture<T, U>;
|
||||
|
||||
actix_service::always_ready!();
|
||||
|
||||
fn call(&self, connection: Connection<T, U>) -> Self::Future {
|
||||
trace!("SSL Handshake start for: {:?}", connection.host());
|
||||
let (stream, connection) = connection.replace_io(());
|
||||
|
||||
match ServerName::try_from(connection.host()) {
|
||||
Ok(host) => RustlsConnectorServiceFuture::Future {
|
||||
connect: TlsConnector::from(self.connector.clone()).connect(host, stream),
|
||||
connection: Some(connection),
|
||||
},
|
||||
Err(_) => RustlsConnectorServiceFuture::InvalidDns,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum RustlsConnectorServiceFuture<T, U> {
|
||||
/// See issue https://github.com/briansmith/webpki/issues/54
|
||||
InvalidDns,
|
||||
Future {
|
||||
connect: Connect<U>,
|
||||
connection: Option<Connection<T, ()>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<T, U> Future for RustlsConnectorServiceFuture<T, U>
|
||||
where
|
||||
T: Address,
|
||||
U: ActixStream,
|
||||
{
|
||||
type Output = Result<Connection<T, TlsStream<U>>, io::Error>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
match self.get_mut() {
|
||||
Self::InvalidDns => Poll::Ready(Err(
|
||||
io::Error::new(io::ErrorKind::Other, "rustls currently only handles hostname-based connections. See https://github.com/briansmith/webpki/issues/54")
|
||||
)),
|
||||
Self::Future { connect, connection } => {
|
||||
let stream = ready!(Pin::new(connect).poll(cx))?;
|
||||
let connection = connection.take().unwrap();
|
||||
trace!("SSL Handshake success: {:?}", connection.host());
|
||||
Poll::Ready(Ok(connection.replace_io(stream).1))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,8 +1,8 @@
|
||||
use http::Uri;
|
||||
|
||||
use super::Host;
|
||||
use super::Address;
|
||||
|
||||
impl Host for Uri {
|
||||
impl Address for Uri {
|
||||
fn hostname(&self) -> &str {
|
||||
self.host().unwrap_or("")
|
||||
}
|
||||
@@ -35,18 +35,9 @@ fn scheme_to_port(scheme: Option<&str>) -> Option<u16> {
|
||||
Some("mqtts") => Some(8883),
|
||||
|
||||
// File Transfer Protocol (FTP)
|
||||
Some("ftp") => Some(21),
|
||||
Some("ftp") => Some(1883),
|
||||
Some("ftps") => Some(990),
|
||||
|
||||
// Redis
|
||||
Some("redis") => Some(6379),
|
||||
|
||||
// MySQL
|
||||
Some("mysql") => Some(3306),
|
||||
|
||||
// PostgreSQL
|
||||
Some("postgres") => Some(5432),
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@@ -1,10 +1,8 @@
|
||||
//! TLS acceptor and connector services for the Actix ecosystem.
|
||||
//! TLS acceptor and connector services for Actix ecosystem
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible, missing_docs)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
#[allow(unused_extern_crates)]
|
||||
@@ -12,6 +10,5 @@ extern crate tls_openssl as openssl;
|
||||
|
||||
#[cfg(feature = "accept")]
|
||||
pub mod accept;
|
||||
|
||||
#[cfg(feature = "connect")]
|
||||
pub mod connect;
|
||||
|
@@ -1,128 +0,0 @@
|
||||
//! Use Rustls connector to test OpenSSL acceptor.
|
||||
|
||||
#![cfg(all(
|
||||
feature = "accept",
|
||||
feature = "connect",
|
||||
feature = "rustls",
|
||||
feature = "openssl"
|
||||
))]
|
||||
|
||||
use std::{convert::TryFrom, io::Write, sync::Arc};
|
||||
|
||||
use actix_rt::net::TcpStream;
|
||||
use actix_server::TestServer;
|
||||
use actix_service::ServiceFactoryExt as _;
|
||||
use actix_tls::accept::openssl::{Acceptor, TlsStream};
|
||||
use actix_utils::future::ok;
|
||||
use tokio_rustls::rustls::{Certificate, ClientConfig, RootCertStore, ServerName};
|
||||
|
||||
fn new_cert_and_key() -> (String, String) {
|
||||
let cert =
|
||||
rcgen::generate_simple_self_signed(vec!["127.0.0.1".to_owned(), "localhost".to_owned()])
|
||||
.unwrap();
|
||||
|
||||
let key = cert.serialize_private_key_pem();
|
||||
let cert = cert.serialize_pem().unwrap();
|
||||
|
||||
(cert, key)
|
||||
}
|
||||
|
||||
fn openssl_acceptor(cert: String, key: String) -> tls_openssl::ssl::SslAcceptor {
|
||||
use tls_openssl::{
|
||||
pkey::PKey,
|
||||
ssl::{SslAcceptor, SslMethod},
|
||||
x509::X509,
|
||||
};
|
||||
|
||||
let cert = X509::from_pem(cert.as_bytes()).unwrap();
|
||||
let key = PKey::private_key_from_pem(key.as_bytes()).unwrap();
|
||||
|
||||
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
|
||||
builder.set_certificate(&cert).unwrap();
|
||||
builder.set_private_key(&key).unwrap();
|
||||
builder.set_alpn_select_callback(|_, _protocols| Ok(b"http/1.1"));
|
||||
builder.set_alpn_protos(b"\x08http/1.1").unwrap();
|
||||
builder.build()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
mod danger {
|
||||
use std::time::SystemTime;
|
||||
|
||||
use tokio_rustls::rustls::{
|
||||
self,
|
||||
client::{ServerCertVerified, ServerCertVerifier},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
pub struct NoCertificateVerification;
|
||||
|
||||
impl ServerCertVerifier for NoCertificateVerification {
|
||||
fn verify_server_cert(
|
||||
&self,
|
||||
_end_entity: &Certificate,
|
||||
_intermediates: &[Certificate],
|
||||
_server_name: &ServerName,
|
||||
_scts: &mut dyn Iterator<Item = &[u8]>,
|
||||
_ocsp_response: &[u8],
|
||||
_now: SystemTime,
|
||||
) -> Result<ServerCertVerified, rustls::Error> {
|
||||
Ok(ServerCertVerified::assertion())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn rustls_connector(_cert: String, _key: String) -> ClientConfig {
|
||||
let mut config = ClientConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_root_certificates(RootCertStore::empty())
|
||||
.with_no_client_auth();
|
||||
|
||||
config
|
||||
.dangerous()
|
||||
.set_certificate_verifier(Arc::new(danger::NoCertificateVerification));
|
||||
|
||||
config.alpn_protocols = vec![b"http/1.1".to_vec()];
|
||||
config
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn accepts_connections() {
|
||||
let (cert, key) = new_cert_and_key();
|
||||
|
||||
let srv = TestServer::start({
|
||||
let cert = cert.clone();
|
||||
let key = key.clone();
|
||||
|
||||
move || {
|
||||
let openssl_acceptor = openssl_acceptor(cert.clone(), key.clone());
|
||||
let tls_acceptor = Acceptor::new(openssl_acceptor);
|
||||
|
||||
tls_acceptor
|
||||
.map_err(|err| println!("OpenSSL error: {:?}", err))
|
||||
.and_then(move |_stream: TlsStream<TcpStream>| ok(()))
|
||||
}
|
||||
});
|
||||
|
||||
let mut sock = srv
|
||||
.connect()
|
||||
.expect("cannot connect to test server")
|
||||
.into_std()
|
||||
.unwrap();
|
||||
sock.set_nonblocking(false).unwrap();
|
||||
|
||||
let config = rustls_connector(cert, key);
|
||||
let config = Arc::new(config);
|
||||
|
||||
let mut conn = tokio_rustls::rustls::ClientConnection::new(
|
||||
config,
|
||||
ServerName::try_from("localhost").unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut stream = tokio_rustls::rustls::Stream::new(&mut conn, &mut sock);
|
||||
|
||||
stream.flush().expect("TLS handshake failed");
|
||||
}
|
@@ -1,106 +0,0 @@
|
||||
//! Use OpenSSL connector to test Rustls acceptor.
|
||||
|
||||
#![cfg(all(
|
||||
feature = "accept",
|
||||
feature = "connect",
|
||||
feature = "rustls",
|
||||
feature = "openssl"
|
||||
))]
|
||||
|
||||
extern crate tls_openssl as openssl;
|
||||
|
||||
use std::io::{BufReader, Write};
|
||||
|
||||
use actix_rt::net::TcpStream;
|
||||
use actix_server::TestServer;
|
||||
use actix_service::ServiceFactoryExt as _;
|
||||
use actix_tls::{
|
||||
accept::rustls::{Acceptor, TlsStream},
|
||||
connect::openssl::reexports::SslConnector,
|
||||
};
|
||||
use actix_utils::future::ok;
|
||||
use rustls_pemfile::{certs, pkcs8_private_keys};
|
||||
use tls_openssl::ssl::SslVerifyMode;
|
||||
use tokio_rustls::rustls::{self, Certificate, PrivateKey, ServerConfig};
|
||||
|
||||
fn new_cert_and_key() -> (String, String) {
|
||||
let cert =
|
||||
rcgen::generate_simple_self_signed(vec!["127.0.0.1".to_owned(), "localhost".to_owned()])
|
||||
.unwrap();
|
||||
|
||||
let key = cert.serialize_private_key_pem();
|
||||
let cert = cert.serialize_pem().unwrap();
|
||||
|
||||
(cert, key)
|
||||
}
|
||||
|
||||
fn rustls_server_config(cert: String, key: String) -> rustls::ServerConfig {
|
||||
// Load TLS key and cert files
|
||||
|
||||
let cert = &mut BufReader::new(cert.as_bytes());
|
||||
let key = &mut BufReader::new(key.as_bytes());
|
||||
|
||||
let cert_chain = certs(cert).unwrap().into_iter().map(Certificate).collect();
|
||||
let mut keys = pkcs8_private_keys(key).unwrap();
|
||||
|
||||
let mut config = ServerConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_no_client_auth()
|
||||
.with_single_cert(cert_chain, PrivateKey(keys.remove(0)))
|
||||
.unwrap();
|
||||
|
||||
config.alpn_protocols = vec![b"http/1.1".to_vec()];
|
||||
|
||||
config
|
||||
}
|
||||
|
||||
fn openssl_connector(cert: String, key: String) -> SslConnector {
|
||||
use actix_tls::connect::openssl::reexports::SslMethod;
|
||||
use openssl::{pkey::PKey, x509::X509};
|
||||
|
||||
let cert = X509::from_pem(cert.as_bytes()).unwrap();
|
||||
let key = PKey::private_key_from_pem(key.as_bytes()).unwrap();
|
||||
|
||||
let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap();
|
||||
ssl.set_verify(SslVerifyMode::NONE);
|
||||
ssl.set_certificate(&cert).unwrap();
|
||||
ssl.set_private_key(&key).unwrap();
|
||||
ssl.set_alpn_protos(b"\x08http/1.1").unwrap();
|
||||
|
||||
ssl.build()
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn accepts_connections() {
|
||||
let (cert, key) = new_cert_and_key();
|
||||
|
||||
let srv = TestServer::start({
|
||||
let cert = cert.clone();
|
||||
let key = key.clone();
|
||||
|
||||
move || {
|
||||
let tls_acceptor = Acceptor::new(rustls_server_config(cert.clone(), key.clone()));
|
||||
|
||||
tls_acceptor
|
||||
.map_err(|err| println!("Rustls error: {:?}", err))
|
||||
.and_then(move |_stream: TlsStream<TcpStream>| ok(()))
|
||||
}
|
||||
});
|
||||
|
||||
let sock = srv
|
||||
.connect()
|
||||
.expect("cannot connect to test server")
|
||||
.into_std()
|
||||
.unwrap();
|
||||
sock.set_nonblocking(false).unwrap();
|
||||
|
||||
let connector = openssl_connector(cert, key);
|
||||
|
||||
let mut stream = connector
|
||||
.connect("localhost", sock)
|
||||
.expect("TLS handshake failed");
|
||||
|
||||
stream.do_handshake().expect("TLS handshake failed");
|
||||
|
||||
stream.flush().expect("TLS handshake failed");
|
||||
}
|
81
actix-tls/tests/test_connect.rs
Normal file → Executable file
81
actix-tls/tests/test_connect.rs
Normal file → Executable file
@@ -9,14 +9,15 @@ use actix_codec::{BytesCodec, Framed};
|
||||
use actix_rt::net::TcpStream;
|
||||
use actix_server::TestServer;
|
||||
use actix_service::{fn_service, Service, ServiceFactory};
|
||||
use actix_tls::connect::{ConnectError, ConnectInfo, Connection, Connector, Host};
|
||||
use bytes::Bytes;
|
||||
use futures_util::sink::SinkExt;
|
||||
|
||||
use actix_tls::connect::{self as actix_connect, Connect};
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
#[actix_rt::test]
|
||||
async fn test_string() {
|
||||
let srv = TestServer::start(|| {
|
||||
let srv = TestServer::with(|| {
|
||||
fn_service(|io: TcpStream| async {
|
||||
let mut framed = Framed::new(io, BytesCodec);
|
||||
framed.send(Bytes::from_static(b"test")).await?;
|
||||
@@ -24,16 +25,16 @@ async fn test_string() {
|
||||
})
|
||||
});
|
||||
|
||||
let connector = Connector::default().service();
|
||||
let conn = actix_connect::default_connector();
|
||||
let addr = format!("localhost:{}", srv.port());
|
||||
let con = connector.call(addr.into()).await.unwrap();
|
||||
let con = conn.call(addr.into()).await.unwrap();
|
||||
assert_eq!(con.peer_addr().unwrap(), srv.addr());
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls")]
|
||||
#[actix_rt::test]
|
||||
async fn test_rustls_string() {
|
||||
let srv = TestServer::start(|| {
|
||||
let srv = TestServer::with(|| {
|
||||
fn_service(|io: TcpStream| async {
|
||||
let mut framed = Framed::new(io, BytesCodec);
|
||||
framed.send(Bytes::from_static(b"test")).await?;
|
||||
@@ -41,7 +42,7 @@ async fn test_rustls_string() {
|
||||
})
|
||||
});
|
||||
|
||||
let conn = Connector::default().service();
|
||||
let conn = actix_connect::default_connector();
|
||||
let addr = format!("localhost:{}", srv.port());
|
||||
let con = conn.call(addr.into()).await.unwrap();
|
||||
assert_eq!(con.peer_addr().unwrap(), srv.addr());
|
||||
@@ -49,7 +50,7 @@ async fn test_rustls_string() {
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_static_str() {
|
||||
let srv = TestServer::start(|| {
|
||||
let srv = TestServer::with(|| {
|
||||
fn_service(|io: TcpStream| async {
|
||||
let mut framed = Framed::new(io, BytesCodec);
|
||||
framed.send(Bytes::from_static(b"test")).await?;
|
||||
@@ -57,30 +58,24 @@ async fn test_static_str() {
|
||||
})
|
||||
});
|
||||
|
||||
let info = ConnectInfo::with_addr("10", srv.addr());
|
||||
let connector = Connector::default().service();
|
||||
let conn = connector.call(info).await.unwrap();
|
||||
assert_eq!(conn.peer_addr().unwrap(), srv.addr());
|
||||
let conn = actix_connect::default_connector();
|
||||
|
||||
let info = ConnectInfo::new(srv.host().to_owned());
|
||||
let connector = Connector::default().service();
|
||||
let conn = connector.call(info).await;
|
||||
assert!(conn.is_err());
|
||||
let con = conn
|
||||
.call(Connect::with_addr("10", srv.addr()))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(con.peer_addr().unwrap(), srv.addr());
|
||||
|
||||
let connect = Connect::new(srv.host().to_owned());
|
||||
|
||||
let conn = actix_connect::default_connector();
|
||||
let con = conn.call(connect).await;
|
||||
assert!(con.is_err());
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn service_factory() {
|
||||
pub fn default_connector_factory<T: Host + 'static>() -> impl ServiceFactory<
|
||||
ConnectInfo<T>,
|
||||
Config = (),
|
||||
Response = Connection<T, TcpStream>,
|
||||
Error = ConnectError,
|
||||
InitError = (),
|
||||
> {
|
||||
Connector::default()
|
||||
}
|
||||
|
||||
let srv = TestServer::start(|| {
|
||||
async fn test_new_service() {
|
||||
let srv = TestServer::with(|| {
|
||||
fn_service(|io: TcpStream| async {
|
||||
let mut framed = Framed::new(io, BytesCodec);
|
||||
framed.send(Bytes::from_static(b"test")).await?;
|
||||
@@ -88,11 +83,14 @@ async fn service_factory() {
|
||||
})
|
||||
});
|
||||
|
||||
let info = ConnectInfo::with_addr("10", srv.addr());
|
||||
let factory = default_connector_factory();
|
||||
let connector = factory.new_service(()).await.unwrap();
|
||||
let con = connector.call(info).await;
|
||||
assert_eq!(con.unwrap().peer_addr().unwrap(), srv.addr());
|
||||
let factory = actix_connect::default_connector_factory();
|
||||
|
||||
let conn = factory.new_service(()).await.unwrap();
|
||||
let con = conn
|
||||
.call(Connect::with_addr("10", srv.addr()))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(con.peer_addr().unwrap(), srv.addr());
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "openssl", feature = "uri"))]
|
||||
@@ -100,7 +98,7 @@ async fn service_factory() {
|
||||
async fn test_openssl_uri() {
|
||||
use std::convert::TryFrom;
|
||||
|
||||
let srv = TestServer::start(|| {
|
||||
let srv = TestServer::with(|| {
|
||||
fn_service(|io: TcpStream| async {
|
||||
let mut framed = Framed::new(io, BytesCodec);
|
||||
framed.send(Bytes::from_static(b"test")).await?;
|
||||
@@ -108,9 +106,9 @@ async fn test_openssl_uri() {
|
||||
})
|
||||
});
|
||||
|
||||
let connector = Connector::default().service();
|
||||
let conn = actix_connect::default_connector();
|
||||
let addr = http::Uri::try_from(format!("https://localhost:{}", srv.port())).unwrap();
|
||||
let con = connector.call(addr.into()).await.unwrap();
|
||||
let con = conn.call(addr.into()).await.unwrap();
|
||||
assert_eq!(con.peer_addr().unwrap(), srv.addr());
|
||||
}
|
||||
|
||||
@@ -119,7 +117,7 @@ async fn test_openssl_uri() {
|
||||
async fn test_rustls_uri() {
|
||||
use std::convert::TryFrom;
|
||||
|
||||
let srv = TestServer::start(|| {
|
||||
let srv = TestServer::with(|| {
|
||||
fn_service(|io: TcpStream| async {
|
||||
let mut framed = Framed::new(io, BytesCodec);
|
||||
framed.send(Bytes::from_static(b"test")).await?;
|
||||
@@ -127,7 +125,7 @@ async fn test_rustls_uri() {
|
||||
})
|
||||
});
|
||||
|
||||
let conn = Connector::default().service();
|
||||
let conn = actix_connect::default_connector();
|
||||
let addr = http::Uri::try_from(format!("https://localhost:{}", srv.port())).unwrap();
|
||||
let con = conn.call(addr.into()).await.unwrap();
|
||||
assert_eq!(con.peer_addr().unwrap(), srv.addr());
|
||||
@@ -135,7 +133,7 @@ async fn test_rustls_uri() {
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_local_addr() {
|
||||
let srv = TestServer::start(|| {
|
||||
let srv = TestServer::with(|| {
|
||||
fn_service(|io: TcpStream| async {
|
||||
let mut framed = Framed::new(io, BytesCodec);
|
||||
framed.send(Bytes::from_static(b"test")).await?;
|
||||
@@ -143,14 +141,11 @@ async fn test_local_addr() {
|
||||
})
|
||||
});
|
||||
|
||||
// if you've arrived here because of a failing test on macOS run this in your terminal:
|
||||
// sudo ifconfig lo0 alias 127.0.0.3
|
||||
|
||||
let conn = Connector::default().service();
|
||||
let conn = actix_connect::default_connector();
|
||||
let local = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 3));
|
||||
|
||||
let (con, _) = conn
|
||||
.call(ConnectInfo::with_addr("10", srv.addr()).set_local_addr(local))
|
||||
.call(Connect::with_addr("10", srv.addr()).set_local_addr(local))
|
||||
.await
|
||||
.unwrap()
|
||||
.into_parts();
|
||||
|
@@ -8,11 +8,10 @@ use std::{
|
||||
use actix_rt::net::TcpStream;
|
||||
use actix_server::TestServer;
|
||||
use actix_service::{fn_service, Service, ServiceFactory};
|
||||
use actix_tls::connect::{
|
||||
ConnectError, ConnectInfo, Connection, Connector, Host, Resolve, Resolver,
|
||||
};
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
|
||||
use actix_tls::connect::{new_connector_factory, Connect, Resolve, Resolver};
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn custom_resolver() {
|
||||
/// Always resolves to localhost with the given port.
|
||||
@@ -37,21 +36,10 @@ async fn custom_resolver() {
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn custom_resolver_connect() {
|
||||
pub fn connector_factory<T: Host + 'static>(
|
||||
resolver: Resolver,
|
||||
) -> impl ServiceFactory<
|
||||
ConnectInfo<T>,
|
||||
Config = (),
|
||||
Response = Connection<T, TcpStream>,
|
||||
Error = ConnectError,
|
||||
InitError = (),
|
||||
> {
|
||||
Connector::new(resolver)
|
||||
}
|
||||
|
||||
use trust_dns_resolver::TokioAsyncResolver;
|
||||
|
||||
let srv = TestServer::start(|| fn_service(|_io: TcpStream| async { Ok::<_, io::Error>(()) }));
|
||||
let srv =
|
||||
TestServer::with(|| fn_service(|_io: TcpStream| async { Ok::<_, io::Error>(()) }));
|
||||
|
||||
struct MyResolver {
|
||||
trust_dns: TokioAsyncResolver,
|
||||
@@ -80,11 +68,12 @@ async fn custom_resolver_connect() {
|
||||
trust_dns: TokioAsyncResolver::tokio_from_system_conf().unwrap(),
|
||||
};
|
||||
|
||||
let factory = connector_factory(Resolver::custom(resolver));
|
||||
let resolver = Resolver::new_custom(resolver);
|
||||
let factory = new_connector_factory(resolver);
|
||||
|
||||
let conn = factory.new_service(()).await.unwrap();
|
||||
let con = conn
|
||||
.call(ConnectInfo::with_addr("example.com", srv.addr()))
|
||||
.call(Connect::with_addr("example.com", srv.addr()))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(con.peer_addr().unwrap(), srv.addr());
|
||||
|
@@ -1,9 +1,5 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2023-xx-xx
|
||||
## [0.1.0] - 2020-01-15
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.65.
|
||||
|
||||
## 0.1.0 - 2020-01-15
|
||||
|
||||
- Initial release
|
||||
* Initial release
|
||||
|
@@ -9,16 +9,19 @@ repository = "https://github.com/actix/actix-net.git"
|
||||
documentation = "https://docs.rs/actix-tracing"
|
||||
categories = ["network-programming", "asynchronous"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
name = "actix_tracing"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
actix-service = "2"
|
||||
actix-utils = "3"
|
||||
actix-service = "2.0.0"
|
||||
actix-utils = "3.0.0"
|
||||
|
||||
tracing = "0.1.35"
|
||||
tracing = "0.1"
|
||||
tracing-futures = "0.2"
|
||||
|
||||
[dev_dependencies]
|
||||
actix-rt = "2"
|
||||
actix-rt = "2.0.0"
|
||||
slab = "0.4"
|
||||
|
@@ -1,7 +1,6 @@
|
||||
//! Actix tracing - support for tokio tracing with Actix services.
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
|
||||
@@ -118,18 +117,16 @@ where
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
use super::*;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use actix_service::{fn_factory, fn_service};
|
||||
use slab::Slab;
|
||||
use tracing::{span, Event, Level, Metadata, Subscriber};
|
||||
|
||||
use super::*;
|
||||
|
||||
thread_local! {
|
||||
static SPAN: RefCell<Vec<span::Id>> = RefCell::new(Vec::new());
|
||||
}
|
||||
|
@@ -1,188 +1,182 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2023-xx-xx
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.65.
|
||||
|
||||
## 3.0.1 - 2022-10-21
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.57.
|
||||
|
||||
## 3.0.0 - 2021-04-16
|
||||
* No significant changes from `3.0.0-beta.4`.
|
||||
|
||||
- No significant changes from `3.0.0-beta.4`.
|
||||
|
||||
## 3.0.0-beta.4 - 2021-04-01
|
||||
|
||||
- Add `future::Either` type. [#305]
|
||||
* Add `future::Either` type. [#305]
|
||||
|
||||
[#305]: https://github.com/actix/actix-net/pull/305
|
||||
|
||||
## 3.0.0-beta.3 - 2021-04-01
|
||||
|
||||
- Moved `mpsc` to own crate `local-channel`. [#301]
|
||||
- Moved `task::LocalWaker` to own crate `local-waker`. [#301]
|
||||
- Remove `timeout` module. [#301]
|
||||
- Remove `dispatcher` module. [#301]
|
||||
- Expose `future` mod with `ready` and `poll_fn` helpers. [#301]
|
||||
## 3.0.0-beta.3 - 2021-04-01
|
||||
* Moved `mpsc` to own crate `local-channel`. [#301]
|
||||
* Moved `task::LocalWaker` to own crate `local-waker`. [#301]
|
||||
* Remove `timeout` module. [#301]
|
||||
* Remove `dispatcher` module. [#301]
|
||||
* Expose `future` mod with `ready` and `poll_fn` helpers. [#301]
|
||||
|
||||
[#301]: https://github.com/actix/actix-net/pull/301
|
||||
|
||||
## 3.0.0-beta.2 - 2021-02-06
|
||||
|
||||
- Update `actix-rt` to `2.0.0`. [#273]
|
||||
## 3.0.0-beta.2 - 2021-02-06
|
||||
* Update `actix-rt` to `2.0.0`. [#273]
|
||||
|
||||
[#273]: https://github.com/actix/actix-net/pull/273
|
||||
|
||||
## 3.0.0-beta.1 - 2020-12-28
|
||||
|
||||
- Update `bytes` dependency to `1`. [#237]
|
||||
- Use `pin-project-lite` to replace `pin-project`. [#229]
|
||||
- Remove `condition`,`either`,`inflight`,`keepalive`,`oneshot`,`order`,`stream` and `time` mods. [#229]
|
||||
## 3.0.0-beta.1 - 2020-12-28
|
||||
* Update `bytes` dependency to `1`. [#237]
|
||||
* Use `pin-project-lite` to replace `pin-project`. [#229]
|
||||
* Remove `condition`,`either`,`inflight`,`keepalive`,`oneshot`,`order`,`stream` and `time` mods. [#229]
|
||||
|
||||
[#229]: https://github.com/actix/actix-net/pull/229
|
||||
[#237]: https://github.com/actix/actix-net/pull/237
|
||||
|
||||
## 2.0.0 - 2020-08-23
|
||||
|
||||
- No changes from beta 1.
|
||||
## 2.0.0 - 2020-08-23
|
||||
* No changes from beta 1.
|
||||
|
||||
|
||||
## 2.0.0-beta.1 - 2020-08-19
|
||||
* Upgrade `tokio-util` to `0.3`.
|
||||
* Remove unsound custom Cell and use `std::cell::RefCell` instead, as well as `actix-service`.
|
||||
* Rename method to correctly spelled `LocalWaker::is_registered`.
|
||||
|
||||
- Upgrade `tokio-util` to `0.3`.
|
||||
- Remove unsound custom Cell and use `std::cell::RefCell` instead, as well as `actix-service`.
|
||||
- Rename method to correctly spelled `LocalWaker::is_registered`.
|
||||
|
||||
## 1.0.6 - 2020-01-08
|
||||
* Add `Clone` impl for `condition::Waiter`.
|
||||
|
||||
- Add `Clone` impl for `condition::Waiter`.
|
||||
|
||||
## 1.0.5 - 2020-01-08
|
||||
* Add `Condition` type.
|
||||
* Add `Pool` of one-shot's.
|
||||
|
||||
- Add `Condition` type.
|
||||
- Add `Pool` of one-shot's.
|
||||
|
||||
## 1.0.4 - 2019-12-20
|
||||
* Add methods to check `LocalWaker` registration state.
|
||||
|
||||
- Add methods to check `LocalWaker` registration state.
|
||||
|
||||
## 1.0.3 - 2019-12-11
|
||||
* Revert InOrder service changes
|
||||
|
||||
- Revert InOrder service changes
|
||||
|
||||
## 1.0.2 - 2019-12-11
|
||||
* Allow to create `framed::Dispatcher` with custom `mpsc::Receiver`.
|
||||
* Add `oneshot::Sender::is_canceled()` method.
|
||||
|
||||
- Allow to create `framed::Dispatcher` with custom `mpsc::Receiver`.
|
||||
- Add `oneshot::Sender::is_canceled()` method.
|
||||
|
||||
## 1.0.1 - 2019-12-11
|
||||
* Optimize InOrder service.
|
||||
|
||||
- Optimize InOrder service.
|
||||
|
||||
## 1.0.0 - 2019-12-11
|
||||
* Simplify oneshot and mpsc implementations.
|
||||
|
||||
- Simplify oneshot and mpsc implementations.
|
||||
|
||||
## 1.0.0-alpha.3 - 2019-12-07
|
||||
* Migrate to tokio 0.2.
|
||||
* Fix oneshot.
|
||||
|
||||
- Migrate to tokio 0.2.
|
||||
- Fix oneshot.
|
||||
|
||||
## 1.0.0-alpha.2 - 2019-12-02
|
||||
* Migrate to `std::future`.
|
||||
|
||||
- Migrate to `std::future`.
|
||||
|
||||
## 0.4.7 - 2019-10-14
|
||||
* Re-register task on every framed transport poll.
|
||||
|
||||
- Re-register task on every framed transport poll.
|
||||
|
||||
## 0.4.6 - 2019-10-08
|
||||
* Refactor `Counter` type. register current task in available method.
|
||||
|
||||
- Refactor `Counter` type. register current task in available method.
|
||||
|
||||
## 0.4.5 - 2019-07-19
|
||||
* Deprecated `CloneableService` as it is not safe.
|
||||
|
||||
- Deprecated `CloneableService` as it is not safe.
|
||||
|
||||
## 0.4.4 - 2019-07-17
|
||||
* Undeprecate `FramedTransport` as it is actually useful.
|
||||
|
||||
- Undeprecate `FramedTransport` as it is actually useful.
|
||||
|
||||
## 0.4.3 - 2019-07-17
|
||||
* Deprecate `CloneableService` as it is not safe and in general not very useful.
|
||||
* Deprecate `FramedTransport` in favor of `actix-ioframe`.
|
||||
|
||||
- Deprecate `CloneableService` as it is not safe and in general not very useful.
|
||||
- Deprecate `FramedTransport` in favor of `actix-ioframe`.
|
||||
|
||||
## 0.4.2 - 2019-06-26
|
||||
* Do not block on sink drop for FramedTransport.
|
||||
|
||||
- Do not block on sink drop for FramedTransport.
|
||||
|
||||
## 0.4.1 - 2019-05-15
|
||||
* Change `Either` constructor.
|
||||
|
||||
- Change `Either` constructor.
|
||||
|
||||
## 0.4.0 - 2019-05-11
|
||||
* Change `Either` to handle two nexted services.
|
||||
* Upgrade actix-service 0.4.
|
||||
* Removed framed related services.
|
||||
* Removed stream related services.
|
||||
|
||||
- Change `Either` to handle two nexted services.
|
||||
- Upgrade actix-service 0.4.
|
||||
- Removed framed related services.
|
||||
- Removed stream related services.
|
||||
|
||||
## 0.3.5 - 2019-04-04
|
||||
* Allow to send messages to `FramedTransport` via mpsc channel.
|
||||
* Remove `'static` constraint from Clonable service.
|
||||
|
||||
- Allow to send messages to `FramedTransport` via mpsc channel.
|
||||
- Remove `'static` constraint from Clonable service.
|
||||
|
||||
## 0.3.4 - 2019-03-12
|
||||
* `TimeoutService`, `InOrderService`, `InFlightService` accepts generic IntoService services.
|
||||
* Fix `InFlightService::poll_ready()` nested service readiness check.
|
||||
* Fix `InOrderService::poll_ready()` nested service readiness check.
|
||||
|
||||
- `TimeoutService`, `InOrderService`, `InFlightService` accepts generic IntoService services.
|
||||
- Fix `InFlightService::poll_ready()` nested service readiness check.
|
||||
- Fix `InOrderService::poll_ready()` nested service readiness check.
|
||||
|
||||
## 0.3.3 - 2019-03-09
|
||||
* Revert IntoFuture change.
|
||||
* Add generic config param for IntoFramed and TakeOne new services.
|
||||
|
||||
- Revert IntoFuture change.
|
||||
- Add generic config param for IntoFramed and TakeOne new services.
|
||||
|
||||
## 0.3.2 - 2019-03-04
|
||||
|
||||
- Use IntoFuture for new services.
|
||||
* Use IntoFuture for new services.
|
||||
|
||||
## 0.3.1 - 2019-03-04
|
||||
* Use new type of transform trait.
|
||||
|
||||
- Use new type of transform trait.
|
||||
|
||||
## 0.3.0 - 2019-03-02
|
||||
* Use new `NewService` trait
|
||||
* BoxedNewService` and `BoxedService` types moved to actix-service crate.
|
||||
|
||||
- Use new `NewService` trait
|
||||
- BoxedNewService`and`BoxedService` types moved to actix-service crate.
|
||||
|
||||
## 0.2.4 - 2019-02-21
|
||||
* Custom `BoxedNewService` implementation.
|
||||
|
||||
- Custom `BoxedNewService` implementation.
|
||||
|
||||
## 0.2.3 - 2019-02-21
|
||||
* Add `BoxedNewService` and `BoxedService`.
|
||||
|
||||
- Add `BoxedNewService` and `BoxedService`.
|
||||
|
||||
## 0.2.2 - 2019-02-11
|
||||
* Add `Display` impl for `TimeoutError`.
|
||||
* Add `Display` impl for `InOrderError`.
|
||||
|
||||
- Add `Display` impl for `TimeoutError`.
|
||||
- Add `Display` impl for `InOrderError`.
|
||||
|
||||
## 0.2.1 - 2019-02-06
|
||||
* Add `InOrder` service. the service yields responses as they become available,
|
||||
in the order that their originating requests were submitted to the service.
|
||||
* Convert `Timeout` and `InFlight` services to a transforms.
|
||||
|
||||
- Add `InOrder` service. the service yields responses as they become available, in the order that their originating requests were submitted to the service.
|
||||
- Convert `Timeout` and `InFlight` services to a transforms.
|
||||
|
||||
## 0.2.0 - 2019-02-01
|
||||
* Fix framed transport error handling.
|
||||
* Added Clone impl for Either service.
|
||||
* Added Clone impl for Timeout service factory.
|
||||
* Added Service and NewService for Stream dispatcher.
|
||||
* Switch to actix-service 0.2.
|
||||
|
||||
- Fix framed transport error handling.
|
||||
- Added Clone impl for Either service.
|
||||
- Added Clone impl for Timeout service factory.
|
||||
- Added Service and NewService for Stream dispatcher.
|
||||
- Switch to actix-service 0.2.
|
||||
|
||||
## 0.1.0 - 2018-12-09
|
||||
|
||||
- Move utils services to separate crate.
|
||||
* Move utils services to separate crate.
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user