mirror of
https://github.com/fafhrd91/actix-net
synced 2025-04-04 13:08:04 +02:00
Compare commits
344 Commits
codec-v0.4
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
7030b26a7c | ||
|
d7f60b88d3 | ||
|
b57ab7e8f0 | ||
|
887975ab11 | ||
|
9069fd8590 | ||
|
8204690568 | ||
|
bbb3139c45 | ||
|
0915904bdb | ||
|
70f0008f42 | ||
|
12df4d7027 | ||
|
6f5b81d2a0 | ||
|
4cf37171b5 | ||
|
00f40e1471 | ||
|
15f0b63492 | ||
|
9d4b1673aa | ||
|
fc902b2d56 | ||
|
f4175a4ad4 | ||
|
323a2e2931 | ||
|
4b9f7ae46d | ||
|
0e119fd9b2 | ||
|
b04b88e81a | ||
|
0a8f2baa11 | ||
|
d79d500ffe | ||
|
7a7e3de430 | ||
|
f062ede06f | ||
|
1338276934 | ||
|
4746b4e2fa | ||
|
0509f0cede | ||
|
3831e0d7fe | ||
|
60945d0481 | ||
|
2615a19e28 | ||
|
1a0f44fff1 | ||
|
8d1cd2ec87 | ||
|
b87174b5f2 | ||
|
8097af6a27 | ||
|
ecba6e21da | ||
|
23f797a81d | ||
|
d2a5091451 | ||
|
e0c09c2aa4 | ||
|
8234543066 | ||
|
34826c6253 | ||
|
42b788d131 | ||
|
9796593b24 | ||
|
c362fc4414 | ||
|
52733337e4 | ||
|
0e36c5f5c4 | ||
|
47f0017899 | ||
|
bb4fc31461 | ||
|
01a104eb82 | ||
|
4ab27bfc4a | ||
|
8084cec705 | ||
|
a2517da225 | ||
|
a4b6943ddc | ||
|
7d24196d5c | ||
|
582edf5444 | ||
|
57485f1a21 | ||
|
e8871d0d06 | ||
|
83e896a6e5 | ||
|
af00dada5c | ||
|
0681b515de | ||
|
3672137d17 | ||
|
fad1fda194 | ||
|
cfae737314 | ||
|
4583daa3c2 | ||
|
b1cbacc7f6 | ||
|
77588aba81 | ||
|
aad3a48edd | ||
|
97e8c571cf | ||
|
0d8c7e5085 | ||
|
baf1b6042a | ||
|
779fa28bd5 | ||
|
e282811d69 | ||
|
5c44115978 | ||
|
ead0e2b200 | ||
|
ace737fc4c | ||
|
c45ae294fb | ||
|
939377f6ab | ||
|
20149f957b | ||
|
544e5d3b40 | ||
|
d482af529c | ||
|
0030800b9a | ||
|
64fa2f8462 | ||
|
b0d1c8d193 | ||
|
46cc62c6d8 | ||
|
912daa3d0a | ||
|
aefa810496 | ||
|
2c443a7620 | ||
|
b3b1583115 | ||
|
0d3d1926bc | ||
|
0c26ecf9fa | ||
|
1bdb15ec20 | ||
|
a524f15e34 | ||
|
451a44c2e0 | ||
|
18071d1fc0 | ||
|
786014cc2f | ||
|
a7ef438f25 | ||
|
375c352810 | ||
|
2d1b5468d0 | ||
|
3696cda155 | ||
|
55e89d1f30 | ||
|
24be36b18d | ||
|
38ae762569 | ||
|
8cf79d3d13 | ||
|
73451070db | ||
|
2632c984cc | ||
|
af8e6cd656 | ||
|
db7988609e | ||
|
1db640f62e | ||
|
9e7d612121 | ||
|
5edbf9e3dc | ||
|
f028a74240 | ||
|
f4139a0878 | ||
|
3147dbe7ca | ||
|
95ca8f0318 | ||
|
f947374a73 | ||
|
85191934c8 | ||
|
234a4c9c7f | ||
|
d5171c2ab3 | ||
|
875218488c | ||
|
b826bf8471 | ||
|
10bd847177 | ||
|
481cf55414 | ||
|
b4990023c4 | ||
|
eb5cec0064 | ||
|
db925bf8e6 | ||
|
850f6c0491 | ||
|
0f71fd5a7a | ||
|
40b10847df | ||
|
39bab04800 | ||
|
3cb247874e | ||
|
5792d9f010 | ||
|
bd8bd1020b | ||
|
21be7d84bd | ||
|
57fd6ea809 | ||
|
9a3f3eef6a | ||
|
e427911cdb | ||
|
a1ae524512 | ||
|
88833355e4 | ||
|
7737ba5cfb | ||
|
fd32a0a97a | ||
|
07e7f82345 | ||
|
1a5d85ec8b | ||
|
bd1467e928 | ||
|
968ad3b854 | ||
|
079f0f66f0 | ||
|
d85903b31a | ||
|
a0675fb0dd | ||
|
af9ccd17d9 | ||
|
eb977e9aeb | ||
|
d28c7db3b3 | ||
|
c5b2d0cd36 | ||
|
02ac0bb4f7 | ||
|
86b000fe71 | ||
|
951e46186b | ||
|
9edc0b393a | ||
|
1945fa0675 | ||
|
923a443950 | ||
|
b526197a9a | ||
|
8fc2253c61 | ||
|
4c12b81492 | ||
|
ef716a8488 | ||
|
2a4df30c63 | ||
|
2d9b147cc3 | ||
|
5515a37002 | ||
|
4067fbe8f0 | ||
|
01f9910e7c | ||
|
df12c10a3f | ||
|
f632ef2ba8 | ||
|
19d03f0454 | ||
|
e9c2a0c318 | ||
|
f967562ac4 | ||
|
61b6e01b02 | ||
|
392e591820 | ||
|
87440e5734 | ||
|
665dec456f | ||
|
7d138f0c31 | ||
|
3cd5d8b07a | ||
|
09548c96b0 | ||
|
17fd135349 | ||
|
b9b628c47b | ||
|
580af3dec4 | ||
|
db54639f0f | ||
|
6d9eb7e162 | ||
|
69e50b5e66 | ||
|
17409cd203 | ||
|
4a7f2c95af | ||
|
c69b8e9ade | ||
|
9f59093adc | ||
|
bfeb4cd9e7 | ||
|
14272a1762 | ||
|
7e043048a0 | ||
|
45a7dcba90 | ||
|
5029beb866 | ||
|
910c181251 | ||
|
150d2c05d3 | ||
|
3b5716c23e | ||
|
0bc310a656 | ||
|
6ce8307060 | ||
|
9cb8a1fadc | ||
|
9017de439f | ||
|
3eba5b152e | ||
|
3c4b0c2755 | ||
|
462ab6a4f0 | ||
|
e539f83615 | ||
|
755b231e00 | ||
|
8d5d1dbf6f | ||
|
177590a7d8 | ||
|
4cbe741230 | ||
|
80320a0325 | ||
|
6d0dc9628b | ||
|
dbce150993 | ||
|
54ec06cd23 | ||
|
c0693da9ba | ||
|
28f36e4e30 | ||
|
c6ebbcf21b | ||
|
c60d2f9ddb | ||
|
a6bece7b33 | ||
|
fbb53f54ef | ||
|
9b388a83c9 | ||
|
878d3a1c74 | ||
|
045cf3f3e8 | ||
|
d13461b337 | ||
|
dde38bbe06 | ||
|
d973d5974a | ||
|
d7afd60606 | ||
|
7e47bf4055 | ||
|
05e7be337e | ||
|
8d964713c9 | ||
|
fe38312db0 | ||
|
2b83f08a40 | ||
|
8e9401f8e1 | ||
|
9ede174e81 | ||
|
bb36e2a072 | ||
|
6061a44a22 | ||
|
363984ad75 | ||
|
00654aadc5 | ||
|
428914e65e | ||
|
df9a9d1a1e | ||
|
056d2cd573 | ||
|
68228a6cf2 | ||
|
d5a9a6a1c5 | ||
|
ade71b7bd3 | ||
|
cb83922b29 | ||
|
25209f5bd8 | ||
|
c4a0f37d0c | ||
|
0e649329b9 | ||
|
66756bc448 | ||
|
126ed4c2e3 | ||
|
283974f3e6 | ||
|
bf2aa3902c | ||
|
71b4e55c92 | ||
|
eb5fa30ada | ||
|
49a034259f | ||
|
3337f63b4e | ||
|
86ce140249 | ||
|
635aebe887 | ||
|
4c1e581a54 | ||
|
dc67ba770d | ||
|
855e3f96fe | ||
|
737b438f73 | ||
|
0cd70b0536 | ||
|
4b6a581ef3 | ||
|
3e132d2bc6 | ||
|
c5d6174cec | ||
|
77d4a69b2f | ||
|
ae5377fd6e | ||
|
bd9bda0504 | ||
|
41ed48219d | ||
|
7804ed12eb | ||
|
2a54065fae | ||
|
217cbd2228 | ||
|
d229c1e886 | ||
|
6792f799a6 | ||
|
72481313cc | ||
|
59b629c74b | ||
|
7988694242 | ||
|
b8a7741524 | ||
|
5e290d76f8 | ||
|
0edb64575f | ||
|
941f67dec9 | ||
|
3e624b8376 | ||
|
26446fdbad | ||
|
b7b7bd2cbf | ||
|
637625f9b7 | ||
|
b1d5d85e72 | ||
|
ed2c07b304 | ||
|
4fe7fec5ef | ||
|
4c9ee88ec4 | ||
|
9ec3cc0fe7 | ||
|
01e0f922de | ||
|
10d3bb6d0d | ||
|
3ba4eafde5 | ||
|
5faa98f6d2 | ||
|
b552d847ed | ||
|
5058e2d14e | ||
|
ae9afd4de7 | ||
|
01d2f18f68 | ||
|
e92b5aaf31 | ||
|
459a6d1b02 | ||
|
9935883905 | ||
|
89a4c2ee27 | ||
|
a4681831a7 | ||
|
5d2da0fdc7 | ||
|
ef18a8342e | ||
|
621deba990 | ||
|
6a9f13c8b4 | ||
|
705b31230f | ||
|
eb490a9125 | ||
|
90f205a465 | ||
|
3a3d654cea | ||
|
ba901c70df | ||
|
4e0dd091f5 | ||
|
8c4ec34cd4 | ||
|
62ffe5f389 | ||
|
07e3b19461 | ||
|
183bcf6ae3 | ||
|
5dc2bfcb01 | ||
|
5556afd524 | ||
|
de5908bfe7 | ||
|
c63880a292 | ||
|
44e4381879 | ||
|
18eced7305 | ||
|
a2437eed29 | ||
|
67b357a175 | ||
|
3597af5c45 | ||
|
8891c2681e | ||
|
233c61ba08 | ||
|
161f239f12 | ||
|
7e7df2f931 | ||
|
ce8ec15eaa | ||
|
ae28ce5377 | ||
|
54d1d9e520 | ||
|
0b0cbd5388 | ||
|
443a328fb4 | ||
|
58a67ade32 | ||
|
38caa8f088 | ||
|
ed987eef06 | ||
|
3658929010 | ||
|
3f49d8ab54 | ||
|
161d1ee94b | ||
|
81ba7cafaa | ||
|
f8f51a2240 | ||
|
a2e765ea6e | ||
|
03dae6a4a4 |
@ -2,25 +2,14 @@
|
||||
lint = "clippy --workspace --tests --examples --bins -- -Dclippy::todo"
|
||||
lint-all = "clippy --workspace --all-features --tests --examples --bins -- -Dclippy::todo"
|
||||
|
||||
ci-doctest = "test --workspace --all-features --doc --no-fail-fast -- --nocapture"
|
||||
|
||||
# just check the library (without dev deps)
|
||||
ci-check-min = "hack --workspace check --no-default-features"
|
||||
ci-check-lib = "hack --workspace --feature-powerset --exclude-features=io-uring check"
|
||||
ci-check-lib-linux = "hack --workspace --feature-powerset check"
|
||||
ci-check-lib = "hack --workspace --feature-powerset --depth=2 --exclude-features=io-uring check"
|
||||
ci-check-lib-linux = "hack --workspace --feature-powerset --depth=2 check"
|
||||
|
||||
# check everything
|
||||
ci-check = "hack --workspace --feature-powerset --exclude-features=io-uring check --tests --examples"
|
||||
ci-check-linux = "hack --workspace --feature-powerset check --tests --examples"
|
||||
ci-check = "hack --workspace --feature-powerset --depth=2 --exclude-features=io-uring check --tests --examples"
|
||||
ci-check-linux = "hack --workspace --feature-powerset --depth=2 check --tests --examples"
|
||||
|
||||
# tests avoiding io-uring feature
|
||||
ci-test = "hack test --workspace --exclude=actix-rt --exclude=actix-server --all-features --lib --tests --no-fail-fast -- --nocapture"
|
||||
ci-test-rt = " hack --feature-powerset --exclude-features=io-uring test --package=actix-rt --lib --tests --no-fail-fast -- --nocapture"
|
||||
ci-test-server = "hack --feature-powerset --exclude-features=io-uring test --package=actix-server --lib --tests --no-fail-fast -- --nocapture"
|
||||
|
||||
# test with io-uring feature
|
||||
ci-test-rt-linux = " hack --feature-powerset test --package=actix-rt --lib --tests --no-fail-fast -- --nocapture"
|
||||
ci-test-server-linux = "hack --feature-powerset test --package=actix-server --lib --tests --no-fail-fast -- --nocapture"
|
||||
|
||||
# test lower msrv
|
||||
ci-test-lower-msrv = "hack --workspace --exclude=actix-server --exclude=actix-tls --feature-powerset test --lib --tests --no-fail-fast -- --nocapture"
|
||||
ci-test = "hack --feature-powerset --depth=2 --exclude-features=io-uring test --lib --tests --no-fail-fast -- --nocapture"
|
||||
|
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,10 +1,12 @@
|
||||
## PR Type
|
||||
|
||||
<!-- What kind of change does this PR make? -->
|
||||
<!-- Bug Fix / Feature / Refactor / Code Style / Other -->
|
||||
|
||||
INSERT_PR_TYPE
|
||||
|
||||
|
||||
## PR Checklist
|
||||
|
||||
Check your PR fulfills the following:
|
||||
|
||||
<!-- For draft PRs check the boxes as you complete them. -->
|
||||
@ -14,11 +16,10 @@ Check your PR fulfills the following:
|
||||
- [ ] A changelog entry has been made for the appropriate packages.
|
||||
- [ ] Format code with the latest stable rustfmt
|
||||
|
||||
|
||||
## Overview
|
||||
|
||||
<!-- Describe the current and new behavior. -->
|
||||
<!-- Emphasize any breaking changes. -->
|
||||
|
||||
|
||||
<!-- If this PR fixes or closes an issue, reference it here. -->
|
||||
<!-- Closes #000 -->
|
||||
|
10
.github/dependabot.yml
vendored
Normal file
10
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
- package-ecosystem: cargo
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
129
.github/workflows/ci-post-merge.yml
vendored
Normal file
129
.github/workflows/ci-post-merge.yml
vendored
Normal file
@ -0,0 +1,129 @@
|
||||
name: CI (post-merge)
|
||||
|
||||
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:
|
||||
# prettier-ignore
|
||||
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 }
|
||||
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@v4
|
||||
|
||||
- name: Free Disk Space
|
||||
if: matrix.target.os == 'ubuntu-latest'
|
||||
run: ./scripts/free-disk-space.sh
|
||||
|
||||
- name: Setup mold linker
|
||||
if: matrix.target.os == 'ubuntu-latest'
|
||||
uses: rui314/setup-mold@v1
|
||||
|
||||
- name: Install nasm
|
||||
if: matrix.target.os == 'windows-latest'
|
||||
uses: ilammy/setup-nasm@v1.5.2
|
||||
|
||||
- name: Install OpenSSL
|
||||
if: matrix.target.os == 'windows-latest'
|
||||
shell: bash
|
||||
run: |
|
||||
set -e
|
||||
choco install openssl --version=1.1.1.2100 -y --no-progress
|
||||
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' >> $GITHUB_ENV
|
||||
echo "RUSTFLAGS=-C target-feature=+crt-static" >> $GITHUB_ENV
|
||||
|
||||
- name: Install Rust (${{ matrix.version }})
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
|
||||
with:
|
||||
toolchain: ${{ matrix.version }}
|
||||
|
||||
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
|
||||
uses: taiki-e/install-action@v2.49.40
|
||||
with:
|
||||
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
|
||||
|
||||
- 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
|
||||
run: just test
|
||||
|
||||
# TODO: re-instate some io-uring tests PRs
|
||||
# - 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-rustls-020
|
||||
# && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo ci-test-rustls-021
|
||||
# && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo ci-test-linux
|
||||
# "
|
||||
|
||||
- name: CI cache clean
|
||||
run: cargo-ci-cache-clean
|
||||
|
||||
minimal-versions:
|
||||
name: minimal versions
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust (nightly)
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
|
||||
with:
|
||||
toolchain: nightly
|
||||
|
||||
- name: Install cargo-hack & cargo-minimal-versions
|
||||
uses: taiki-e/install-action@v2.49.40
|
||||
with:
|
||||
tool: cargo-hack,cargo-minimal-versions
|
||||
|
||||
- name: Check With Minimal Versions
|
||||
run: cargo minimal-versions check
|
227
.github/workflows/ci.yml
vendored
227
.github/workflows/ci.yml
vendored
@ -1,208 +1,133 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request: {}
|
||||
merge_group: { types: [checks_requested] }
|
||||
push: { branches: [master] }
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
read_msrv:
|
||||
name: Read MSRV
|
||||
uses: actions-rust-lang/msrv/.github/workflows/msrv.yml@v0.1.0
|
||||
|
||||
build_and_test:
|
||||
needs:
|
||||
- read_msrv
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# prettier-ignore
|
||||
target:
|
||||
- { name: Linux, os: ubuntu-latest, triple: x86_64-unknown-linux-gnu }
|
||||
- { name: macOS, os: macos-latest, triple: x86_64-apple-darwin }
|
||||
- { name: Windows, os: windows-latest, triple: x86_64-pc-windows-msvc }
|
||||
- { name: Windows (MinGW), os: windows-latest, triple: x86_64-pc-windows-gnu }
|
||||
- { name: Windows (32-bit), os: windows-latest, triple: i686-pc-windows-msvc }
|
||||
version:
|
||||
- 1.52.0 # MSRV for -server and -tls
|
||||
- stable
|
||||
- nightly
|
||||
- { name: msrv, version: "${{ needs.read_msrv.outputs.msrv }}" }
|
||||
- { name: stable, version: stable }
|
||||
|
||||
name: ${{ matrix.target.name }} / ${{ matrix.version }}
|
||||
name: ${{ matrix.target.name }} / ${{ matrix.version.name }}
|
||||
runs-on: ${{ matrix.target.os }}
|
||||
|
||||
env:
|
||||
VCPKGRS_DYNAMIC: 1
|
||||
env: {}
|
||||
|
||||
steps:
|
||||
- name: Setup Routing
|
||||
if: matrix.target.os == 'macos-latest'
|
||||
run: sudo ifconfig lo0 alias 127.0.0.3
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Free Disk Space
|
||||
if: matrix.target.os == 'ubuntu-latest'
|
||||
run: ./scripts/free-disk-space.sh
|
||||
|
||||
- name: Setup mold linker
|
||||
if: matrix.target.os == 'ubuntu-latest'
|
||||
uses: rui314/setup-mold@v1
|
||||
|
||||
- name: Install nasm
|
||||
if: matrix.target.os == 'windows-latest'
|
||||
uses: ilammy/setup-nasm@v1.5.2
|
||||
|
||||
# install OpenSSL on Windows
|
||||
- name: Set vcpkg root
|
||||
if: matrix.target.triple == 'x86_64-pc-windows-msvc' || matrix.target.triple == 'i686-pc-windows-msvc'
|
||||
run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append
|
||||
- name: Install OpenSSL
|
||||
if: matrix.target.triple == 'x86_64-pc-windows-msvc'
|
||||
run: vcpkg install openssl:x64-windows
|
||||
- name: Install OpenSSL
|
||||
if: matrix.target.triple == 'i686-pc-windows-msvc'
|
||||
run: vcpkg install openssl:x86-windows
|
||||
if: matrix.target.os == 'windows-latest'
|
||||
shell: bash
|
||||
run: |
|
||||
set -e
|
||||
choco install openssl --version=1.1.1.2100 -y --no-progress
|
||||
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' >> $GITHUB_ENV
|
||||
echo "RUSTFLAGS=-C target-feature=+crt-static" >> $GITHUB_ENV
|
||||
|
||||
- name: Install ${{ matrix.version }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
- name: Install Rust (${{ matrix.version.name }})
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
|
||||
with:
|
||||
toolchain: ${{ matrix.version }}-${{ matrix.target.triple }}
|
||||
profile: minimal
|
||||
override: true
|
||||
toolchain: ${{ matrix.version.version }}
|
||||
|
||||
# - name: Install MSYS2
|
||||
# if: matrix.target.triple == 'x86_64-pc-windows-gnu'
|
||||
# uses: msys2/setup-msys2@v2
|
||||
# - name: Install MinGW Packages
|
||||
# if: matrix.target.triple == 'x86_64-pc-windows-gnu'
|
||||
# run: |
|
||||
# msys2 -c 'pacman -Sy --noconfirm pacman'
|
||||
# msys2 -c 'pacman --noconfirm -S base-devel pkg-config'
|
||||
|
||||
# - name: Generate Cargo.lock
|
||||
# uses: actions-rs/cargo@v1
|
||||
# with: { command: generate-lockfile }
|
||||
# - name: Cache Dependencies
|
||||
# uses: Swatinem/rust-cache@v1.2.0
|
||||
|
||||
- name: Install cargo-hack
|
||||
uses: actions-rs/cargo@v1
|
||||
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
|
||||
uses: taiki-e/install-action@v2.49.40
|
||||
with:
|
||||
command: install
|
||||
args: cargo-hack
|
||||
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
|
||||
|
||||
- name: Generate Cargo.lock
|
||||
run: cargo generate-lockfile
|
||||
|
||||
- name: workaround MSRV issues
|
||||
if: matrix.version.name == 'msrv'
|
||||
run: just downgrade-for-msrv
|
||||
|
||||
- name: check lib
|
||||
if: >
|
||||
matrix.target.os != 'ubuntu-latest'
|
||||
&& matrix.target.triple != 'x86_64-pc-windows-gnu'
|
||||
uses: actions-rs/cargo@v1
|
||||
with: { command: ci-check-lib }
|
||||
run: cargo ci-check-lib
|
||||
- name: check lib
|
||||
if: matrix.target.os == 'ubuntu-latest'
|
||||
uses: actions-rs/cargo@v1
|
||||
with: { command: ci-check-lib-linux }
|
||||
run: cargo ci-check-lib-linux
|
||||
- name: check lib
|
||||
if: matrix.target.triple == 'x86_64-pc-windows-gnu'
|
||||
uses: actions-rs/cargo@v1
|
||||
with: { command: ci-check-min }
|
||||
|
||||
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'
|
||||
uses: actions-rs/cargo@v1
|
||||
with: { command: ci-check }
|
||||
run: cargo ci-check
|
||||
- name: check all
|
||||
if: matrix.target.os == 'ubuntu-latest'
|
||||
uses: actions-rs/cargo@v1
|
||||
with: { command: ci-check-linux }
|
||||
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
|
||||
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-rt-linux && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo ci-test-server-linux"
|
||||
run: just test
|
||||
|
||||
- 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)
|
||||
- name: CI cache clean
|
||||
run: cargo-ci-cache-clean
|
||||
|
||||
docs:
|
||||
name: Documentation
|
||||
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
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust (nightly)
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
|
||||
with:
|
||||
toolchain: stable-x86_64-unknown-linux-gnu
|
||||
profile: minimal
|
||||
override: true
|
||||
toolchain: nightly
|
||||
|
||||
- name: Generate Cargo.lock
|
||||
uses: actions-rs/cargo@v1
|
||||
with: { command: generate-lockfile }
|
||||
- name: Cache Dependencies
|
||||
uses: Swatinem/rust-cache@v1.3.0
|
||||
|
||||
- name: Generate coverage file
|
||||
if: github.ref == 'refs/heads/master'
|
||||
run: |
|
||||
cargo install cargo-tarpaulin
|
||||
cargo tarpaulin --out Xml --verbose
|
||||
- name: Upload to Codecov
|
||||
if: github.ref == 'refs/heads/master'
|
||||
uses: codecov/codecov-action@v1
|
||||
with: { file: cobertura.xml }
|
||||
|
||||
rustdoc:
|
||||
name: rustdoc
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install Rust (nightly)
|
||||
uses: actions-rs/toolchain@v1
|
||||
- name: Install just
|
||||
uses: taiki-e/install-action@v2.49.40
|
||||
with:
|
||||
toolchain: nightly-x86_64-unknown-linux-gnu
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Generate Cargo.lock
|
||||
uses: actions-rs/cargo@v1
|
||||
with: { command: generate-lockfile }
|
||||
- name: Cache Dependencies
|
||||
uses: Swatinem/rust-cache@v1.3.0
|
||||
|
||||
- name: Install cargo-hack
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: install
|
||||
args: cargo-hack
|
||||
tool: just
|
||||
|
||||
- name: doc tests
|
||||
uses: actions-rs/cargo@v1
|
||||
timeout-minutes: 40
|
||||
with: { command: ci-doctest }
|
||||
run: just test-docs
|
||||
|
42
.github/workflows/clippy-fmt.yml
vendored
42
.github/workflows/clippy-fmt.yml
vendored
@ -1,42 +0,0 @@
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
fmt:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
components: rustfmt
|
||||
override: true
|
||||
- name: Rustfmt Check
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
components: clippy
|
||||
override: true
|
||||
- name: Clippy Check
|
||||
uses: actions-rs/clippy-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --workspace --all-features --tests --examples --bins -- -Dclippy::todo
|
39
.github/workflows/coverage.yml
vendored
Normal file
39
.github/workflows/coverage.yml
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
name: Coverage
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
coverage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
|
||||
with:
|
||||
components: llvm-tools-preview
|
||||
|
||||
- name: Install cargo-llvm-cov
|
||||
uses: taiki-e/install-action@v2.49.40
|
||||
with:
|
||||
tool: cargo-llvm-cov
|
||||
|
||||
- name: Generate code coverage
|
||||
run: cargo llvm-cov --workspace --all-features --codecov --output-path codecov.json
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v5.4.0
|
||||
with:
|
||||
files: codecov.json
|
||||
fail_ci_if_error: true
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
69
.github/workflows/lint.yml
vendored
Normal file
69
.github/workflows/lint.yml
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
pull_request: {}
|
||||
merge_group: { types: [checks_requested] }
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
fmt:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: rustfmt
|
||||
|
||||
- name: Rustfmt Check
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
clippy:
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
|
||||
with: { components: clippy }
|
||||
|
||||
- uses: giraffate/clippy-action@v1.0.1
|
||||
with:
|
||||
reporter: "github-pr-check"
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
clippy_flags: --workspace --all-features --tests --examples --bins -- -Dclippy::todo -Aunknown_lints
|
||||
|
||||
check-external-types:
|
||||
if: false # rustdoc mismatch currently
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust (${{ vars.RUST_VERSION_EXTERNAL_TYPES }})
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
|
||||
with:
|
||||
toolchain: ${{ vars.RUST_VERSION_EXTERNAL_TYPES }}
|
||||
|
||||
- name: Install just
|
||||
uses: taiki-e/install-action@v2.49.40
|
||||
with:
|
||||
tool: just
|
||||
|
||||
- name: Install cargo-check-external-types
|
||||
uses: taiki-e/cache-cargo-install-action@v2.1.1
|
||||
with:
|
||||
tool: cargo-check-external-types
|
||||
|
||||
- name: check external types
|
||||
run: just check-external-types-all +${{ vars.RUST_VERSION_EXTERNAL_TYPES }}
|
35
.github/workflows/upload-doc.yml
vendored
35
.github/workflows/upload-doc.yml
vendored
@ -1,35 +0,0 @@
|
||||
name: Upload documentation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly-x86_64-unknown-linux-gnu
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Build Docs
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: doc
|
||||
args: --workspace --all-features --no-deps
|
||||
|
||||
- name: Tweak HTML
|
||||
run: echo '<meta http-equiv="refresh" content="0;url=actix_server/index.html">' > target/doc/index.html
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
uses: JamesIves/github-pages-deploy-action@3.7.1
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BRANCH: gh-pages
|
||||
FOLDER: target/doc
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,4 +1,3 @@
|
||||
Cargo.lock
|
||||
target/
|
||||
guide/build/
|
||||
/gh-pages
|
||||
@ -13,4 +12,8 @@ guide/build/
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# IDEs
|
||||
.idea
|
||||
|
||||
# direnv
|
||||
/.direnv
|
||||
|
3
.rustfmt.toml
Normal file
3
.rustfmt.toml
Normal file
@ -0,0 +1,3 @@
|
||||
group_imports = "StdExternalCrate"
|
||||
imports_granularity = "Crate"
|
||||
use_field_init_shorthand = true
|
29
.taplo.toml
Normal file
29
.taplo.toml
Normal file
@ -0,0 +1,29 @@
|
||||
exclude = ["target/*"]
|
||||
include = ["**/*.toml"]
|
||||
|
||||
[formatting]
|
||||
column_width = 110
|
||||
|
||||
[[rule]]
|
||||
include = ["**/Cargo.toml"]
|
||||
keys = [
|
||||
"dependencies",
|
||||
"*-dependencies",
|
||||
"workspace.dependencies",
|
||||
"workspace.*-dependencies",
|
||||
"target.*.dependencies",
|
||||
"target.*.*-dependencies",
|
||||
]
|
||||
formatting.reorder_keys = true
|
||||
|
||||
[[rule]]
|
||||
include = ["**/Cargo.toml"]
|
||||
keys = [
|
||||
"dependencies.*",
|
||||
"*-dependencies.*",
|
||||
"workspace.dependencies.*",
|
||||
"workspace.*-dependencies.*",
|
||||
"target.*.dependencies",
|
||||
"target.*.*-dependencies",
|
||||
]
|
||||
formatting.reorder_keys = false
|
@ -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
|
||||
|
||||
|
2941
Cargo.lock
generated
Normal file
2941
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
12
Cargo.toml
12
Cargo.toml
@ -1,4 +1,5 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"actix-codec",
|
||||
"actix-macros",
|
||||
@ -13,6 +14,11 @@ members = [
|
||||
"local-waker",
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.71.1"
|
||||
|
||||
[patch.crates-io]
|
||||
actix-codec = { path = "actix-codec" }
|
||||
actix-macros = { path = "actix-macros" }
|
||||
@ -30,3 +36,9 @@ local-waker = { path = "local-waker" }
|
||||
lto = true
|
||||
opt-level = 3
|
||||
codegen-units = 1
|
||||
|
||||
[workspace.lints.rust]
|
||||
rust_2018_idioms = "deny"
|
||||
nonstandard-style = "deny"
|
||||
future_incompatible = "deny"
|
||||
missing_docs = { level = "warn", priority = -1 }
|
||||
|
28
README.md
28
README.md
@ -2,29 +2,25 @@
|
||||
|
||||
> A collection of lower-level libraries for composable network services.
|
||||
|
||||

|
||||
[](https://codecov.io/gh/actix/actix-net)
|
||||
[](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)
|
||||
|
||||
## 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)") |
|
||||
## Examples
|
||||
|
||||
## Example
|
||||
See `actix-server/examples` and `actix-tls/examples` for some basic examples.
|
||||
See example folders for [`actix-server`](./actix-server/examples) and [`actix-tls`](./actix-tls/examples).
|
||||
|
||||
### MSRV
|
||||
This repo's Minimum Supported Rust Version (MSRV) is 1.46.0.
|
||||
## 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.
|
||||
|
||||
## License
|
||||
This project is licensed under either of
|
||||
|
||||
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0))
|
||||
* MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT))
|
||||
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))
|
||||
|
||||
at your option.
|
||||
|
||||
|
@ -1,68 +1,85 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
* Added `LinesCodec.` [#338]
|
||||
* `Framed::poll_ready` flushes when the buffer is full. [#409]
|
||||
## Unreleased
|
||||
|
||||
[#338]: https://github.com/actix/actix-net/pull/338
|
||||
[#409]: https://github.com/actix/actix-net/pull/409
|
||||
- Minimum supported Rust version (MSRV) is now 1.71.
|
||||
|
||||
## 0.5.2
|
||||
|
||||
## 0.4.0 - 2021-04-20
|
||||
* No significant changes since v0.4.0-beta.1.
|
||||
- Minimum supported Rust version (MSRV) is now 1.65.
|
||||
|
||||
## 0.5.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]
|
||||
- Logs emitted now use the `tracing` crate with `log` compatibility.
|
||||
- Minimum supported Rust version (MSRV) is now 1.49.
|
||||
|
||||
[#237]: https://github.com/actix/actix-net/pull/237
|
||||
## 0.5.0
|
||||
|
||||
- Updated `tokio-util` dependency to `0.7.0`.
|
||||
|
||||
## 0.3.0 - 2020-08-23
|
||||
* No changes from beta 2.
|
||||
## 0.4.2
|
||||
|
||||
- No significant changes since `0.4.1`.
|
||||
|
||||
## 0.3.0-beta.2 - 2020-08-19
|
||||
* Remove unused type parameter from `Framed::replace_codec`.
|
||||
## 0.4.1
|
||||
|
||||
- Added `LinesCodec`.
|
||||
- `Framed::poll_ready` flushes when the buffer is full.
|
||||
|
||||
## 0.3.0-beta.1 - 2020-08-19
|
||||
* Use `.advance()` instead of `.split_to()`.
|
||||
* Upgrade `tokio-util` to `0.3`.
|
||||
* Improve `BytesCodec::encode()` performance.
|
||||
* Simplify `BytesCodec::decode()`.
|
||||
* Rename methods on `Framed` to better describe their use.
|
||||
* Add method on `Framed` to get a pinned reference to the underlying I/O.
|
||||
* Add method on `Framed` check emptiness of read buffer.
|
||||
## 0.4.0
|
||||
|
||||
- No significant changes since v0.4.0-beta.1.
|
||||
|
||||
## 0.2.0 - 2019-12-10
|
||||
* Use specific futures dependencies.
|
||||
## 0.4.0-beta.1
|
||||
|
||||
- Replace `pin-project` with `pin-project-lite`.
|
||||
- Upgrade `tokio` dependency to `1`.
|
||||
- Upgrade `tokio-util` dependency to `0.6`.
|
||||
- Upgrade `bytes` dependency to `1`.
|
||||
|
||||
## 0.3.0
|
||||
|
||||
- No changes from beta 2.
|
||||
|
||||
## 0.3.0-beta.2
|
||||
|
||||
- Remove unused type parameter from `Framed::replace_codec`.
|
||||
|
||||
## 0.3.0-beta.1
|
||||
|
||||
- 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
|
||||
|
||||
- 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.
|
||||
## 0.1.2
|
||||
|
||||
- Added `Framed::map_io()` method.
|
||||
|
||||
## 0.1.1 - 2019-03-06
|
||||
* Added `FramedParts::with_read_buffer()` method.
|
||||
## 0.1.1
|
||||
|
||||
- Added `FramedParts::with_read_buffer()` method.
|
||||
|
||||
## 0.1.0 - 2018-12-09
|
||||
* Move codec to separate crate.
|
||||
## 0.1.0
|
||||
|
||||
- Move codec to separate crate.
|
||||
|
@ -1,36 +1,36 @@
|
||||
[package]
|
||||
name = "actix-codec"
|
||||
version = "0.4.0"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
]
|
||||
version = "0.5.2"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>", "Rob Ede <robjtede@icloud.com>"]
|
||||
description = "Codec utilities for working with framed protocols"
|
||||
keywords = ["network", "framework", "async", "futures"]
|
||||
repository = "https://github.com/actix/actix-net"
|
||||
categories = ["network-programming", "asynchronous"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "actix_codec"
|
||||
path = "src/lib.rs"
|
||||
[package.metadata.cargo_check_external_types]
|
||||
allowed_external_types = ["bytes::*", "futures_core::*", "futures_sink::*", "tokio::*", "tokio_util::*"]
|
||||
|
||||
[dependencies]
|
||||
bitflags = "1.2.1"
|
||||
bitflags = "2"
|
||||
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.5.1"
|
||||
tokio-util = { version = "0.6", features = ["codec", "io"] }
|
||||
tokio = "1.23.1"
|
||||
tokio-util = { version = "0.7", features = ["codec", "io"] }
|
||||
tracing = { version = "0.1.30", default-features = false, features = ["log"] }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.3", features = ["html_reports"] }
|
||||
criterion = { version = "0.5", features = ["html_reports"] }
|
||||
tokio-test = "0.4.2"
|
||||
|
||||
[[bench]]
|
||||
name = "lines"
|
||||
harness = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
@ -1,3 +1,5 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use bytes::BytesMut;
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
|
@ -1,10 +1,14 @@
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use std::{fmt, io};
|
||||
use std::{
|
||||
fmt, io,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
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};
|
||||
|
||||
@ -13,14 +17,15 @@ const LW: usize = 1024;
|
||||
/// High-water mark
|
||||
const HW: usize = 8 * 1024;
|
||||
|
||||
bitflags::bitflags! {
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct Flags: u8 {
|
||||
const EOF = 0b0001;
|
||||
const READABLE = 0b0010;
|
||||
}
|
||||
}
|
||||
|
||||
pin_project_lite::pin_project! {
|
||||
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.
|
||||
///
|
||||
@ -152,7 +157,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,
|
||||
@ -189,18 +194,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(e) => return Poll::Ready(Some(Err(e))),
|
||||
Err(err) => return Poll::Ready(Some(Err(err))),
|
||||
}
|
||||
}
|
||||
|
||||
log::trace!("attempting to decode a frame");
|
||||
tracing::trace!("attempting to decode a frame");
|
||||
|
||||
match this.codec.decode(this.read_buf) {
|
||||
Ok(Some(frame)) => {
|
||||
log::trace!("frame decoded from buffer");
|
||||
tracing::trace!("frame decoded from buffer");
|
||||
return Poll::Ready(Some(Ok(frame)));
|
||||
}
|
||||
Err(e) => return Poll::Ready(Some(Err(e))),
|
||||
Err(err) => return Poll::Ready(Some(Err(err))),
|
||||
_ => (), // Need more data
|
||||
}
|
||||
|
||||
@ -217,7 +222,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(e)) => return Poll::Ready(Some(Err(e.into()))),
|
||||
Poll::Ready(Err(err)) => return Poll::Ready(Some(Err(err.into()))),
|
||||
Poll::Ready(Ok(cnt)) => cnt,
|
||||
};
|
||||
|
||||
@ -229,19 +234,16 @@ 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();
|
||||
log::trace!("flushing framed transport");
|
||||
tracing::trace!("flushing framed transport");
|
||||
|
||||
while !this.write_buf.is_empty() {
|
||||
log::trace!("writing; remaining={}", this.write_buf.len());
|
||||
tracing::trace!("writing; remaining={}", this.write_buf.len());
|
||||
|
||||
let n = ready!(this.io.as_mut().poll_write(cx, this.write_buf))?;
|
||||
|
||||
@ -260,15 +262,12 @@ impl<T, U> Framed<T, U> {
|
||||
// Try flushing the underlying IO
|
||||
ready!(this.io.poll_flush(cx))?;
|
||||
|
||||
log::trace!("framed transport flushed");
|
||||
tracing::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,25 +1,26 @@
|
||||
//! 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, 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;
|
||||
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;
|
||||
pub use self::{
|
||||
bcodec::BytesCodec,
|
||||
framed::{Framed, FramedParts},
|
||||
lines::LinesCodec,
|
||||
};
|
||||
|
@ -7,8 +7,8 @@ use super::{Decoder, Encoder};
|
||||
|
||||
/// Lines codec. Reads/writes line delimited strings.
|
||||
///
|
||||
/// Will split input up by LF or CRLF delimiters. I.e. carriage return characters at the end of
|
||||
/// lines are not preserved.
|
||||
/// Will split input up by LF or CRLF delimiters. Carriage return characters at the end of lines are
|
||||
/// not preserved.
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
#[non_exhaustive]
|
||||
pub struct LinesCodec;
|
||||
|
@ -1,12 +1,18 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
io::{self, Write},
|
||||
pin::Pin,
|
||||
task::{
|
||||
Context,
|
||||
Poll::{self, Pending, Ready},
|
||||
},
|
||||
};
|
||||
|
||||
use actix_codec::*;
|
||||
use bytes::Buf;
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use bytes::{Buf as _, BufMut as _, 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 {
|
||||
@ -50,7 +56,7 @@ impl Write for Bilateral {
|
||||
assert_eq!(&data[..], &src[..data.len()]);
|
||||
Ok(data.len())
|
||||
}
|
||||
Some(Err(e)) => Err(e),
|
||||
Some(Err(err)) => Err(err),
|
||||
None => panic!("unexpected write; {:?}", src),
|
||||
}
|
||||
}
|
||||
@ -67,20 +73,17 @@ impl AsyncWrite for Bilateral {
|
||||
buf: &[u8],
|
||||
) -> Poll<Result<usize, io::Error>> {
|
||||
match Pin::get_mut(self).write(buf) {
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => Pending,
|
||||
Err(ref err) if err.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 e) if e.kind() == io::ErrorKind::WouldBlock => Pending,
|
||||
Err(ref err) if err.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!()
|
||||
}
|
||||
}
|
||||
@ -99,8 +102,8 @@ impl AsyncRead for Bilateral {
|
||||
buf.put_slice(&data);
|
||||
Ready(Ok(()))
|
||||
}
|
||||
Some(Err(ref e)) if e.kind() == WouldBlock => Pending,
|
||||
Some(Err(e)) => Ready(Err(e)),
|
||||
Some(Err(ref err)) if err.kind() == WouldBlock => Pending,
|
||||
Some(Err(err)) => Ready(Err(err)),
|
||||
None => Ready(Ok(())),
|
||||
}
|
||||
}
|
||||
|
@ -1,46 +1,53 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
## Unreleased
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.71.
|
||||
|
||||
## 0.2.3 - 2021-10-19
|
||||
* Fix test macro in presence of other imports named "test". [#399]
|
||||
## 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]
|
||||
|
||||
[#399]: https://github.com/actix/actix-net/pull/399
|
||||
|
||||
## 0.2.2
|
||||
|
||||
## 0.2.2 - 2021-10-14
|
||||
* Improve error recovery potential when macro input is invalid. [#391]
|
||||
* Allow custom `System`s on test macro. [#391]
|
||||
- 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
|
||||
|
||||
## 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]
|
||||
- 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
|
||||
|
||||
## 0.2.0 - 2021-02-02
|
||||
* Update to latest `actix_rt::System::new` signature. [#261]
|
||||
- Update to latest `actix_rt::System::new` signature. [#261]
|
||||
|
||||
[#261]: https://github.com/actix/actix-net/pull/261
|
||||
|
||||
## 0.2.0-beta.1
|
||||
|
||||
## 0.2.0-beta.1 - 2021-01-09
|
||||
* Remove `actix-reexport` feature. [#218]
|
||||
- Remove `actix-reexport` feature. [#218]
|
||||
|
||||
[#218]: https://github.com/actix/actix-net/pull/218
|
||||
|
||||
## 0.1.3
|
||||
|
||||
## 0.1.3 - 2020-12-03
|
||||
* Add `actix-reexport` feature. [#218]
|
||||
- Add `actix-reexport` feature. [#218]
|
||||
|
||||
[#218]: https://github.com/actix/actix-net/pull/218
|
||||
|
||||
## 0.1.2
|
||||
|
||||
## 0.1.2 - 2020-05-18
|
||||
* Forward actix_rt::test arguments to test function [#127]
|
||||
- Forward actix_rt::test arguments to test function [#127]
|
||||
|
||||
[#127]: https://github.com/actix/actix-net/pull/127
|
||||
|
@ -1,27 +1,39 @@
|
||||
[package]
|
||||
name = "actix-macros"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Ibraheem Ahmed <ibrah1440@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Ibraheem Ahmed <ibrah1440@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
]
|
||||
description = "Macros for Actix system and runtime"
|
||||
repository = "https://github.com/actix/actix-net.git"
|
||||
repository = "https://github.com/actix/actix-net"
|
||||
categories = ["network-programming", "asynchronous"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
license.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = [
|
||||
"proc_macro2", # specified for minimal versions compat
|
||||
]
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
quote = "1.0.3"
|
||||
syn = { version = "^1", features = ["full"] }
|
||||
quote = "1"
|
||||
syn = { version = "2", features = ["full"] }
|
||||
|
||||
# minimal versions compat
|
||||
[target.'cfg(any())'.dependencies]
|
||||
proc-macro2 = "1.0.60"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "2.0.0"
|
||||
|
||||
futures-util = { version = "0.3.7", default-features = false }
|
||||
rustversion = "1"
|
||||
actix-rt = "2"
|
||||
futures-util = { version = "0.3.17", default-features = false }
|
||||
rustversion-msrv = "0.100"
|
||||
trybuild = "1"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
@ -8,12 +8,14 @@
|
||||
//! # Tests
|
||||
//! See docs for the [`#[test]`](macro@test) macro.
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
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.
|
||||
///
|
||||
@ -24,9 +26,7 @@ use quote::quote;
|
||||
/// println!("Hello world");
|
||||
/// }
|
||||
/// ```
|
||||
#[allow(clippy::needless_doctest_main)]
|
||||
#[proc_macro_attribute]
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let mut input = match syn::parse::<syn::ItemFn>(item.clone()) {
|
||||
Ok(input) => input,
|
||||
@ -34,7 +34,11 @@ pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
Err(err) => return input_and_compile_error(item, err),
|
||||
};
|
||||
|
||||
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
|
||||
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 attrs = &input.attrs;
|
||||
let vis = &input.vis;
|
||||
@ -54,11 +58,15 @@ pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
|
||||
for arg in &args {
|
||||
match arg {
|
||||
syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue {
|
||||
lit: syn::Lit::Str(lit),
|
||||
syn::Meta::NameValue(syn::MetaNameValue {
|
||||
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()
|
||||
@ -77,6 +85,7 @@ pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
.into();
|
||||
}
|
||||
},
|
||||
|
||||
_ => {
|
||||
return syn::Error::new_spanned(arg, "Unknown attribute specified")
|
||||
.to_compile_error()
|
||||
@ -113,7 +122,11 @@ pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
Err(err) => return input_and_compile_error(item, err),
|
||||
};
|
||||
|
||||
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
|
||||
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 attrs = &input.attrs;
|
||||
let vis = &input.vis;
|
||||
@ -122,7 +135,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;
|
||||
}
|
||||
}
|
||||
@ -148,11 +161,15 @@ pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
|
||||
for arg in &args {
|
||||
match arg {
|
||||
syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue {
|
||||
lit: syn::Lit::Str(lit),
|
||||
syn::Meta::NameValue(syn::MetaNameValue {
|
||||
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,7 +1,10 @@
|
||||
#[rustversion::stable(1.46)] // MSRV
|
||||
#![allow(missing_docs)]
|
||||
|
||||
#[rustversion_msrv::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,14 +1,11 @@
|
||||
error: the async keyword is missing from the function declaration
|
||||
--> $DIR/main-02-only-async.rs:2:1
|
||||
--> tests/trybuild/main-02-only-async.rs:2:1
|
||||
|
|
||||
2 | fn main() {
|
||||
| ^^
|
||||
|
||||
error[E0601]: `main` function not found in crate `$CRATE`
|
||||
--> $DIR/main-02-only-async.rs:1:1
|
||||
--> tests/trybuild/main-02-only-async.rs:4:2
|
||||
|
|
||||
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`
|
||||
4 | }
|
||||
| ^ consider adding a `main` function to `$DIR/tests/trybuild/main-02-only-async.rs`
|
||||
|
@ -1,171 +1,174 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
* 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]
|
||||
## Unreleased
|
||||
|
||||
[#395]: https://github.com/actix/actix-net/pull/395
|
||||
[#408]: https://github.com/actix/actix-net/pull/408
|
||||
- Minimum supported Rust version (MSRV) is now 1.71.
|
||||
|
||||
## 2.10.0
|
||||
|
||||
## 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]
|
||||
- Relax `F`'s bound (`Fn => FnOnce`) on `{Arbiter, System}::with_tokio_rt()` functions.
|
||||
- Update `tokio-uring` dependency to `0.5`.
|
||||
- Minimum supported Rust version (MSRV) is now 1.70.
|
||||
|
||||
[#369]: https://github.com/actix/actix-net/pull/369
|
||||
[#374]: https://github.com/actix/actix-net/pull/374
|
||||
## 2.9.0
|
||||
|
||||
- Add `actix_rt::System::runtime()` method to retrieve the underlying `actix_rt::Runtime` runtime.
|
||||
- Add `actix_rt::Runtime::tokio_runtime()` method to retrieve the underlying Tokio runtime.
|
||||
- Minimum supported Rust version (MSRV) is now 1.65.
|
||||
|
||||
## 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.8.0
|
||||
|
||||
[#293]: https://github.com/actix/actix-net/pull/293
|
||||
- Add `#[track_caller]` attribute to `spawn` functions and methods.
|
||||
- Update `tokio-uring` dependency to `0.4`.
|
||||
- Minimum supported Rust version (MSRV) is now 1.59.
|
||||
|
||||
## 2.7.0
|
||||
|
||||
## 2.1.0 - 2021-02-24
|
||||
* Add `ActixStream` extension trait to include readiness methods. [#276]
|
||||
* Re-export `tokio::net::TcpSocket` in `net` module [#282]
|
||||
- Update `tokio-uring` dependency to `0.3`.
|
||||
- Minimum supported Rust version (MSRV) is now 1.49.
|
||||
|
||||
[#276]: https://github.com/actix/actix-net/pull/276
|
||||
[#282]: https://github.com/actix/actix-net/pull/282
|
||||
## 2.6.0
|
||||
|
||||
- Update `tokio-uring` dependency to `0.2`.
|
||||
|
||||
## 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.5.1
|
||||
|
||||
[#274]: https://github.com/actix/actix-net/pull/274
|
||||
[#275]: https://github.com/actix/actix-net/pull/275
|
||||
- Expose `System::with_tokio_rt` and `Arbiter::with_tokio_rt`.
|
||||
|
||||
## 2.5.0
|
||||
|
||||
## 2.0.1 - 2021-02-06
|
||||
* Expose `JoinError` from Tokio. [#271]
|
||||
- Add `System::run_with_code` to allow retrieving the exit code on stop.
|
||||
|
||||
[#271]: https://github.com/actix/actix-net/pull/271
|
||||
## 2.4.0
|
||||
|
||||
- Add `Arbiter::try_current` for situations where thread may or may not have Arbiter context.
|
||||
- Start io-uring with `System::new` when feature is enabled.
|
||||
|
||||
## 2.0.0 - 2021-02-02
|
||||
* Remove all Arbiter-local storage methods. [#262]
|
||||
* Re-export `tokio::pin`. [#262]
|
||||
## 2.3.0
|
||||
|
||||
[#262]: https://github.com/actix/actix-net/pull/262
|
||||
- The `spawn` method can now resolve with non-unit outputs.
|
||||
- Add experimental (semver-exempt) `io-uring` feature for enabling async file I/O on linux.
|
||||
|
||||
## 2.2.0
|
||||
|
||||
## 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]
|
||||
- **BREAKING** `ActixStream::{poll_read_ready, poll_write_ready}` methods now return `Ready` object in ok variant.
|
||||
- Breakage is acceptable since `ActixStream` was not intended to be public.
|
||||
|
||||
[#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.1.0
|
||||
|
||||
- Add `ActixStream` extension trait to include readiness methods.
|
||||
- Re-export `tokio::net::TcpSocket` in `net` module
|
||||
|
||||
## 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.2
|
||||
|
||||
[#245]: https://github.com/actix/actix-net/pull/245
|
||||
- Add `Arbiter::handle` to get a handle of an owned Arbiter.
|
||||
- Add `System::try_current` for situations where actix may or may not be running a System.
|
||||
|
||||
## 2.0.1
|
||||
|
||||
## 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]
|
||||
- Expose `JoinError` from Tokio.
|
||||
|
||||
[#207]: https://github.com/actix/actix-net/pull/207
|
||||
[#236]: https://github.com/actix/actix-net/pull/236
|
||||
## 2.0.0
|
||||
|
||||
- Remove all Arbiter-local storage methods.
|
||||
- Re-export `tokio::pin`.
|
||||
|
||||
## 1.1.1 - 2020-04-30
|
||||
* Fix memory leak due to [#94] (see [#129] for more detail)
|
||||
## 2.0.0-beta.3
|
||||
|
||||
[#129]: https://github.com/actix/actix-net/issues/129
|
||||
- Remove `run_in_tokio`, `attach_to_tokio` and `AsyncSystemRunner`.
|
||||
- Return `JoinHandle` from `actix_rt::spawn`.
|
||||
- Remove old `Arbiter::spawn`. Implementation is now inlined into `actix_rt::spawn`.
|
||||
- Rename `Arbiter::{send => spawn}` and `Arbiter::{exec_fn => spawn_fn}`.
|
||||
- Remove `Arbiter::exec`.
|
||||
- Remove deprecated `Arbiter::local_join` and `Arbiter::is_running`.
|
||||
- `Arbiter::spawn` now accepts !Unpin futures.
|
||||
- `System::new` no longer takes arguments.
|
||||
- Remove `System::with_current`.
|
||||
- Remove `Builder`.
|
||||
- Add `System::with_init` as replacement for `Builder::run`.
|
||||
- Rename `System::{is_set => is_registered}`.
|
||||
- Add `ArbiterHandle` for sending messages to non-current-thread arbiters.
|
||||
- `System::arbiter` now returns an `&ArbiterHandle`.
|
||||
- `Arbiter::current` now returns an `ArbiterHandle` instead.
|
||||
- `Arbiter::join` now takes self by value.
|
||||
|
||||
## 2.0.0-beta.2
|
||||
|
||||
## 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]
|
||||
- Add `task` mod with re-export of `tokio::task::{spawn_blocking, yield_now, JoinHandle}`
|
||||
- Add default "macros" feature to allow faster compile times when using `default-features=false`.
|
||||
|
||||
[#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
|
||||
## 2.0.0-beta.1
|
||||
|
||||
- Add `System::attach_to_tokio` method.
|
||||
- Update `tokio` dependency to `1.0`.
|
||||
- Rename `time` module `delay_for` to `sleep`, `delay_until` to `sleep_until`, `Delay` to `Sleep` to stay aligned with Tokio's naming.
|
||||
- Remove `'static` lifetime requirement for `Runtime::block_on` and `SystemRunner::block_on`.
|
||||
- These methods now accept `&self` when calling.
|
||||
- Remove `'static` lifetime requirement for `System::run` and `Builder::run`.
|
||||
- `Arbiter::spawn` now panics when `System` is not in scope.
|
||||
- Fix work load issue by removing `PENDING` thread local.
|
||||
|
||||
## 1.0.0 - 2019-12-11
|
||||
* Update dependencies
|
||||
## 1.1.1
|
||||
|
||||
- Fix memory leak due to
|
||||
|
||||
## 1.0.0-alpha.3 - 2019-12-07
|
||||
* Migrate to tokio 0.2
|
||||
* Fix compilation on non-unix platforms
|
||||
## 1.1.0 _(YANKED)_
|
||||
|
||||
- Expose `System::is_set` to check if current system has ben started
|
||||
- Add `Arbiter::is_running` to check if event loop is running
|
||||
- Add `Arbiter::local_join` associated function to get be able to `await` for spawned futures
|
||||
|
||||
## 1.0.0-alpha.2 - 2019-12-02
|
||||
* Export `main` and `test` attribute macros
|
||||
* Export `time` module (re-export of tokio-timer)
|
||||
* Export `net` module (re-export of tokio-net)
|
||||
## 1.0.0
|
||||
|
||||
- Update dependencies
|
||||
|
||||
## 1.0.0-alpha.1 - 2019-11-22
|
||||
* Migrate to std::future and tokio 0.2
|
||||
## 1.0.0-alpha.3
|
||||
|
||||
- Migrate to tokio 0.2
|
||||
- Fix compilation on non-unix platforms
|
||||
|
||||
## 0.2.6 - 2019-11-14
|
||||
* Allow to join arbiter's thread. #60
|
||||
* Fix arbiter's thread panic message.
|
||||
## 1.0.0-alpha.2
|
||||
|
||||
- Export `main` and `test` attribute macros
|
||||
- Export `time` module (re-export of tokio-timer)
|
||||
- Export `net` module (re-export of tokio-net)
|
||||
|
||||
## 0.2.5 - 2019-09-02
|
||||
* Add arbiter specific storage
|
||||
## 1.0.0-alpha.1
|
||||
|
||||
- Migrate to std::future and tokio 0.2
|
||||
|
||||
## 0.2.4 - 2019-07-17
|
||||
* Avoid a copy of the Future when initializing the Box. #29
|
||||
## 0.2.6
|
||||
|
||||
- Allow to join arbiter's thread. #60
|
||||
- Fix arbiter's thread panic message.
|
||||
|
||||
## 0.2.3 - 2019-06-22
|
||||
* Allow to start System using existing CurrentThread Handle #22
|
||||
## 0.2.5
|
||||
|
||||
- Add arbiter specific storage
|
||||
|
||||
## 0.2.2 - 2019-03-28
|
||||
* Moved `blocking` module to `actix-threadpool` crate
|
||||
## 0.2.4
|
||||
|
||||
- Avoid a copy of the Future when initializing the Box. #29
|
||||
|
||||
## 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
|
||||
## 0.2.3
|
||||
|
||||
- Allow to start System using existing CurrentThread Handle #22
|
||||
|
||||
## 0.2.0 - 2019-03-06
|
||||
* `run` method returns `io::Result<()>`
|
||||
* Removed `Handle`
|
||||
## 0.2.2
|
||||
|
||||
- Moved `blocking` module to `actix-threadpool` crate
|
||||
|
||||
## 0.1.0 - 2018-12-09
|
||||
* Initial release
|
||||
## 0.2.1
|
||||
|
||||
- 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
|
||||
|
||||
- `run` method returns `io::Result<()>`
|
||||
- Removed `Handle`
|
||||
|
||||
## 0.1.0
|
||||
|
||||
- Initial release
|
||||
|
@ -1,21 +1,18 @@
|
||||
[package]
|
||||
name = "actix-rt"
|
||||
version = "2.3.0"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
]
|
||||
version = "2.10.0"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>", "Rob Ede <robjtede@icloud.com>"]
|
||||
description = "Tokio-based single-threaded async runtime for the Actix ecosystem"
|
||||
keywords = ["async", "futures", "io", "runtime"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-net.git"
|
||||
repository = "https://github.com/actix/actix-net"
|
||||
categories = ["network-programming", "asynchronous"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "actix_rt"
|
||||
path = "src/lib.rs"
|
||||
[package.metadata.cargo_check_external_types]
|
||||
allowed_external_types = ["actix_macros::*", "tokio::*"]
|
||||
|
||||
[features]
|
||||
default = ["macros"]
|
||||
@ -26,11 +23,14 @@ io-uring = ["tokio-uring"]
|
||||
actix-macros = { version = "0.2.3", optional = true }
|
||||
|
||||
futures-core = { version = "0.3", default-features = false }
|
||||
tokio = { version = "1.5.1", features = ["rt", "net", "parking_lot", "signal", "sync", "time"] }
|
||||
tokio = { version = "1.23.1", features = ["rt", "net", "parking_lot", "signal", "sync", "time"] }
|
||||
|
||||
# runtime for `io-uring` feature
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
tokio-uring = { version = "0.1", optional = true }
|
||||
tokio-uring = { version = "0.5", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.5.1", features = ["full"] }
|
||||
hyper = { version = "0.14", default-features = false, features = ["server", "tcp", "http1"] }
|
||||
tokio = { version = "1.23.1", features = ["full"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
@ -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.3.0)
|
||||
[](https://docs.rs/actix-rt/2.10.0)
|
||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
||||

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

|
||||
[](https://discord.gg/WghFtEH6Hb)
|
||||
|
||||
|
@ -1,28 +0,0 @@
|
||||
use hyper::service::{make_service_fn, service_fn};
|
||||
use hyper::{Body, Request, Response, Server};
|
||||
use std::convert::Infallible;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
async fn handle(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
|
||||
Ok(Response::new(Body::from("Hello World")))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
actix_rt::System::with_tokio_rt(|| {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
})
|
||||
.block_on(async {
|
||||
let make_service =
|
||||
make_service_fn(|_conn| async { Ok::<_, Infallible>(service_fn(handle)) });
|
||||
|
||||
let server =
|
||||
Server::bind(&SocketAddr::from(([127, 0, 0, 1], 3000))).serve(make_service);
|
||||
|
||||
if let Err(e) = server.await {
|
||||
eprintln!("server error: {}", e);
|
||||
}
|
||||
})
|
||||
}
|
@ -16,7 +16,7 @@ use crate::system::{System, SystemCommand};
|
||||
pub(crate) static COUNT: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
thread_local!(
|
||||
static HANDLE: RefCell<Option<ArbiterHandle>> = RefCell::new(None);
|
||||
static HANDLE: RefCell<Option<ArbiterHandle>> = const { RefCell::new(None) };
|
||||
);
|
||||
|
||||
pub(crate) enum ArbiterCommand {
|
||||
@ -99,8 +99,7 @@ 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.")
|
||||
})
|
||||
}
|
||||
|
||||
@ -108,10 +107,9 @@ 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,
|
||||
F: FnOnce() -> tokio::runtime::Runtime + Send + 'static,
|
||||
{
|
||||
let sys = System::current();
|
||||
let system_id = sys.id();
|
||||
@ -150,9 +148,7 @@ 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();
|
||||
|
||||
@ -202,9 +198,7 @@ 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();
|
||||
|
||||
@ -261,6 +255,7 @@ 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,
|
||||
@ -276,6 +271,7 @@ 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,
|
||||
@ -302,7 +298,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!(Pin::new(&mut self.rx).poll_recv(cx)) {
|
||||
match ready!(self.rx.poll_recv(cx)) {
|
||||
// channel closed; no more messages can be received
|
||||
None => return Poll::Ready(()),
|
||||
|
||||
|
@ -34,12 +34,14 @@
|
||||
//! ```
|
||||
//!
|
||||
//! # `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)]
|
||||
#![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")]
|
||||
|
||||
@ -48,13 +50,10 @@ 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,11 +61,14 @@ mod arbiter;
|
||||
mod runtime;
|
||||
mod system;
|
||||
|
||||
pub use self::arbiter::{Arbiter, ArbiterHandle};
|
||||
pub use self::runtime::Runtime;
|
||||
pub use self::system::{System, SystemRunner};
|
||||
|
||||
pub use tokio::pin;
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
pub use self::{
|
||||
arbiter::{Arbiter, ArbiterHandle},
|
||||
runtime::Runtime,
|
||||
system::{System, SystemRunner},
|
||||
};
|
||||
|
||||
pub mod signal {
|
||||
//! Asynchronous signal handling (Tokio re-exports).
|
||||
@ -88,13 +90,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)]
|
||||
@ -153,10 +155,9 @@ pub mod net {
|
||||
pub mod time {
|
||||
//! Utilities for tracking time (Tokio re-exports).
|
||||
|
||||
pub use tokio::time::Instant;
|
||||
pub use tokio::time::{interval, interval_at, Interval};
|
||||
pub use tokio::time::{sleep, sleep_until, Sleep};
|
||||
pub use tokio::time::{timeout, Timeout};
|
||||
pub use tokio::time::{
|
||||
interval, interval_at, sleep, sleep_until, timeout, Instant, Interval, Sleep, Timeout,
|
||||
};
|
||||
}
|
||||
|
||||
pub mod task {
|
||||
@ -195,6 +196,7 @@ pub mod task {
|
||||
/// assert!(handle.await.unwrap_err().is_cancelled());
|
||||
/// # });
|
||||
/// ```
|
||||
#[track_caller]
|
||||
#[inline]
|
||||
pub fn spawn<Fut>(f: Fut) -> JoinHandle<Fut::Output>
|
||||
where
|
||||
|
@ -53,6 +53,7 @@ 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,
|
||||
@ -60,6 +61,62 @@ impl Runtime {
|
||||
self.local.spawn_local(future)
|
||||
}
|
||||
|
||||
/// Retrieves a reference to the underlying Tokio runtime associated with this instance.
|
||||
///
|
||||
/// The Tokio runtime is responsible for executing asynchronous tasks and managing
|
||||
/// the event loop for an asynchronous Rust program. This method allows accessing
|
||||
/// the runtime to interact with its features directly.
|
||||
///
|
||||
/// In a typical use case, you might need to share the same runtime between different
|
||||
/// modules of your project. For example, a module might require a `tokio::runtime::Handle`
|
||||
/// to spawn tasks on the same runtime, or the runtime itself to configure more complex
|
||||
/// behaviours.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use actix_rt::Runtime;
|
||||
///
|
||||
/// mod module_a {
|
||||
/// pub fn do_something(handle: tokio::runtime::Handle) {
|
||||
/// handle.spawn(async {
|
||||
/// // Some asynchronous task here
|
||||
/// });
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// mod module_b {
|
||||
/// pub fn do_something_else(rt: &tokio::runtime::Runtime) {
|
||||
/// rt.spawn(async {
|
||||
/// // Another asynchronous task here
|
||||
/// });
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let actix_runtime = actix_rt::Runtime::new().unwrap();
|
||||
/// let tokio_runtime = actix_runtime.tokio_runtime();
|
||||
///
|
||||
/// let handle = tokio_runtime.handle().clone();
|
||||
///
|
||||
/// module_a::do_something(handle);
|
||||
/// module_b::do_something_else(tokio_runtime);
|
||||
/// ```
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// An immutable reference to the `tokio::runtime::Runtime` instance associated with this
|
||||
/// `Runtime` instance.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// While this method provides an immutable reference to the Tokio runtime, which is safe to share across threads,
|
||||
/// be aware that spawning blocking tasks on the Tokio runtime could potentially impact the execution
|
||||
/// of the Actix runtime. This is because Tokio is responsible for driving the Actix system,
|
||||
/// and blocking tasks could delay or deadlock other tasks in run loop.
|
||||
pub fn tokio_runtime(&self) -> &tokio::runtime::Runtime {
|
||||
&self.rt
|
||||
}
|
||||
|
||||
/// Runs the provided future, blocking the current thread until the future completes.
|
||||
///
|
||||
/// This function can be used to synchronously block the current thread until the provided
|
||||
@ -73,6 +130,7 @@ 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,
|
||||
|
@ -16,7 +16,7 @@ use crate::{arbiter::ArbiterHandle, Arbiter};
|
||||
static SYSTEM_COUNT: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
thread_local!(
|
||||
static CURRENT: RefCell<Option<System>> = RefCell::new(None);
|
||||
static CURRENT: RefCell<Option<System>> = const { RefCell::new(None) };
|
||||
);
|
||||
|
||||
/// A manager for a per-thread distributed async runtime.
|
||||
@ -46,10 +46,9 @@ 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,
|
||||
F: FnOnce() -> tokio::runtime::Runtime,
|
||||
{
|
||||
let (stop_tx, stop_rx) = oneshot::channel();
|
||||
let (sys_tx, sys_rx) = mpsc::unbounded_channel();
|
||||
@ -67,11 +66,7 @@ impl System {
|
||||
let sys_ctrl = SystemController::new(sys_rx, stop_tx);
|
||||
rt.spawn(sys_ctrl);
|
||||
|
||||
SystemRunner {
|
||||
rt,
|
||||
stop_rx,
|
||||
system,
|
||||
}
|
||||
SystemRunner { rt, stop_rx }
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,9 +87,9 @@ impl System {
|
||||
#[doc(hidden)]
|
||||
pub fn with_tokio_rt<F>(_: F) -> SystemRunner
|
||||
where
|
||||
F: Fn() -> tokio::runtime::Runtime,
|
||||
F: FnOnce() -> tokio::runtime::Runtime,
|
||||
{
|
||||
unimplemented!("System::with_tokio_rt is not implemented yet")
|
||||
unimplemented!("System::with_tokio_rt is not implemented for io-uring feature yet")
|
||||
}
|
||||
}
|
||||
|
||||
@ -175,49 +170,83 @@ impl System {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "io-uring"))]
|
||||
/// 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."]
|
||||
#[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 SystemRunner { rt, stop_rx, .. } = self;
|
||||
let exit_code = self.run_with_code()?;
|
||||
|
||||
// run loop
|
||||
match rt.block_on(stop_rx) {
|
||||
Ok(code) => {
|
||||
if code != 0 {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("Non-zero exit code: {}", code),
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Err(e) => Err(io::Error::new(io::ErrorKind::Other, e)),
|
||||
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))
|
||||
}
|
||||
|
||||
/// Retrieves a reference to the underlying [Actix runtime](crate::Runtime) associated with this
|
||||
/// `SystemRunner` instance.
|
||||
///
|
||||
/// The Actix runtime is responsible for managing the event loop for an Actix system and
|
||||
/// executing asynchronous tasks. This method provides access to the runtime, allowing direct
|
||||
/// interaction with its features.
|
||||
///
|
||||
/// In a typical use case, you might need to share the same runtime between different
|
||||
/// parts of your project. For example, some components might require a [`Runtime`] to spawn
|
||||
/// tasks on the same runtime.
|
||||
///
|
||||
/// Read more in the documentation for [`Runtime`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// let system_runner = actix_rt::System::new();
|
||||
/// let actix_runtime = system_runner.runtime();
|
||||
///
|
||||
/// // Use the runtime to spawn an async task or perform other operations
|
||||
/// ```
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// While this method provides an immutable reference to the Actix runtime, which is safe to
|
||||
/// share across threads, be aware that spawning blocking tasks on the Actix runtime could
|
||||
/// potentially impact system performance. This is because the Actix runtime is responsible for
|
||||
/// driving the system, and blocking tasks could delay other tasks in the run loop.
|
||||
///
|
||||
/// [`Runtime`]: crate::Runtime
|
||||
pub fn runtime(&self) -> &crate::runtime::Runtime {
|
||||
&self.rt
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "io-uring")]
|
||||
/// 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."]
|
||||
#[derive(Debug)]
|
||||
pub struct SystemRunner;
|
||||
@ -226,7 +255,12 @@ 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 yet")
|
||||
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");
|
||||
}
|
||||
|
||||
/// Runs the provided future, blocking the current thread until the future completes.
|
||||
@ -290,7 +324,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!(Pin::new(&mut self.cmd_rx).poll_recv(cx)) {
|
||||
match ready!(self.cmd_rx.poll_recv(cx)) {
|
||||
// channel closed; no more messages can be received
|
||||
None => return Poll::Ready(()),
|
||||
|
||||
|
@ -1,10 +1,11 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use std::{
|
||||
future::Future,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use actix_rt::{task::JoinError, Arbiter, System};
|
||||
|
||||
#[cfg(not(feature = "io-uring"))]
|
||||
use {
|
||||
std::{sync::mpsc::channel, thread},
|
||||
@ -24,6 +25,15 @@ 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);
|
||||
@ -99,8 +109,8 @@ fn wait_for_spawns() {
|
||||
|
||||
let handle = rt.spawn(async {
|
||||
println!("running on the runtime");
|
||||
// assertion panic is caught at task boundary
|
||||
assert_eq!(1, 2);
|
||||
// panic is caught at task boundary
|
||||
panic!("intentional test panic");
|
||||
});
|
||||
|
||||
assert!(rt.block_on(handle).is_err());
|
||||
@ -350,7 +360,7 @@ fn tokio_uring_arbiter() {
|
||||
let f = tokio_uring::fs::File::create("test.txt").await.unwrap();
|
||||
let buf = b"Hello World!";
|
||||
|
||||
let (res, _) = f.write_at(&buf[..], 0).await;
|
||||
let (res, _) = f.write_all_at(&buf[..], 0).await;
|
||||
assert!(res.is_ok());
|
||||
|
||||
f.sync_all().await.unwrap();
|
||||
|
@ -1,198 +1,232 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
* 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.
|
||||
## Unreleased
|
||||
|
||||
[#407]: https://github.com/actix/actix-net/pull/407
|
||||
[#408]: https://github.com/actix/actix-net/pull/408
|
||||
## 2.5.1
|
||||
|
||||
- Fix panic in test server.
|
||||
- Minimum supported Rust version (MSRV) is now 1.71.
|
||||
|
||||
## 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.5.0
|
||||
|
||||
[#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
|
||||
- Update `mio` dependency to `1`.
|
||||
|
||||
## 2.4.0
|
||||
|
||||
## 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]
|
||||
- Update `tokio-uring` dependency to `0.5`.
|
||||
- Minimum supported Rust version (MSRV) is now 1.70.
|
||||
|
||||
[#333]: https://github.com/actix/actix-net/pull/333
|
||||
## 2.3.0
|
||||
|
||||
- Add support for MultiPath TCP (MPTCP) with `MpTcp` enum and `ServerBuilder::mptcp()` method.
|
||||
- Minimum supported Rust version (MSRV) is now 1.65.
|
||||
|
||||
## 2.0.0-beta.4 - 2021-04-01
|
||||
* Prevent panic when `shutdown_timeout` is very large. [f9262db]
|
||||
## 2.2.0
|
||||
|
||||
[f9262db]: https://github.com/actix/actix-net/commit/f9262db
|
||||
- Minimum supported Rust version (MSRV) is now 1.59.
|
||||
- Update `tokio-uring` dependency to `0.4`.
|
||||
|
||||
## 2.1.1
|
||||
|
||||
## 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]
|
||||
- No significant changes since `2.1.0`.
|
||||
|
||||
[#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.1.0
|
||||
|
||||
- Update `tokio-uring` dependency to `0.3`.
|
||||
- Logs emitted now use the `tracing` crate with `log` compatibility.
|
||||
- Wait for accept thread to stop before sending completion signal.
|
||||
|
||||
## 2.0.0-beta.2 - 2021-01-03
|
||||
* Merge `actix-testing` to `actix-server` as `test_server` mod. [#242]
|
||||
## 2.0.0
|
||||
|
||||
[#242]: https://github.com/actix/actix-net/pull/242
|
||||
- No significant changes since `2.0.0-rc.4`.
|
||||
|
||||
## 2.0.0-rc.4
|
||||
|
||||
## 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]
|
||||
- Update `tokio-uring` dependency to `0.2`.
|
||||
|
||||
[#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
|
||||
## 2.0.0-rc.3
|
||||
|
||||
- No significant changes since `2.0.0-rc.2`.
|
||||
|
||||
## 1.0.4 - 2020-09-12
|
||||
* Update actix-codec to 0.3.0.
|
||||
* Workers must be greater than 0. [#167]
|
||||
## 2.0.0-rc.2
|
||||
|
||||
[#167]: https://github.com/actix/actix-net/pull/167
|
||||
- Simplify `TestServer`.
|
||||
|
||||
## 2.0.0-rc.1
|
||||
|
||||
## 1.0.3 - 2020-05-19
|
||||
* Replace deprecated `net2` crate with `socket2` [#140]
|
||||
- Hide implementation details of `Server`.
|
||||
- `Server` now runs only after awaiting it.
|
||||
|
||||
[#140]: https://github.com/actix/actix-net/pull/140
|
||||
## 2.0.0-beta.9
|
||||
|
||||
- Restore `Arbiter` support lost in `beta.8`.
|
||||
|
||||
## 1.0.2 - 2020-02-26
|
||||
* Avoid error by calling `reregister()` on Windows [#103]
|
||||
## 2.0.0-beta.8
|
||||
|
||||
[#103]: https://github.com/actix/actix-net/pull/103
|
||||
- Fix non-unix signal handler.
|
||||
|
||||
## 2.0.0-beta.7
|
||||
|
||||
## 1.0.1 - 2019-12-29
|
||||
* Rename `.start()` method to `.run()`
|
||||
- Server can be started in regular Tokio runtime.
|
||||
- Expose new `Server` type whose `Future` impl resolves when server stops.
|
||||
- Rename `Server` to `ServerHandle`.
|
||||
- Add `Server::handle` to obtain handle to server.
|
||||
- Rename `ServerBuilder::{maxconn => max_concurrent_connections}`.
|
||||
- Deprecate crate-level `new` shortcut for server builder.
|
||||
- Minimum supported Rust version (MSRV) is now 1.52.
|
||||
|
||||
## 2.0.0-beta.6
|
||||
|
||||
## 1.0.0 - 2019-12-11
|
||||
* Use actix-net releases
|
||||
- Add experimental (semver-exempt) `io-uring` feature for enabling async file I/O on linux.
|
||||
- Server no long listens to `SIGHUP` signal. Previously, the received was not used but did block subsequent exit signals from working.
|
||||
- Remove `config` module. `ServiceConfig`, `ServiceRuntime` public types are removed due to this change.
|
||||
- Remove `ServerBuilder::configure`.
|
||||
|
||||
## 2.0.0-beta.5
|
||||
|
||||
## 1.0.0-alpha.4 - 2019-12-08
|
||||
* Use actix-service 1.0.0-alpha.4
|
||||
- Server shutdown notifies all workers to exit regardless if shutdown is graceful. This causes all workers to shutdown immediately in force shutdown case.
|
||||
|
||||
## 2.0.0-beta.4
|
||||
|
||||
## 1.0.0-alpha.3 - 2019-12-07
|
||||
* Migrate to tokio 0.2
|
||||
* Fix compilation on non-unix platforms
|
||||
* Better handling server configuration
|
||||
- Prevent panic when `shutdown_timeout` is very large. [f9262db]
|
||||
|
||||
## 2.0.0-beta.3
|
||||
|
||||
## 1.0.0-alpha.2 - 2019-12-02
|
||||
* Simplify server service (remove actix-server-config)
|
||||
* Allow to wait on `Server` until server stops
|
||||
- Hidden `ServerBuilder::start` method has been removed. Use `ServerBuilder::run`.
|
||||
- Add retry for EINTR signal (`io::Interrupted`) in `Accept`'s poll loop.
|
||||
- Add `ServerBuilder::worker_max_blocking_threads` to customize blocking thread pool size.
|
||||
- Update `actix-rt` to `2.0.0`.
|
||||
|
||||
## 2.0.0-beta.2
|
||||
|
||||
## 0.8.0-alpha.1 - 2019-11-22
|
||||
* Migrate to `std::future`
|
||||
- Merge `actix-testing` to `actix-server` as `test_server` mod.
|
||||
|
||||
## 2.0.0-beta.1
|
||||
|
||||
## 0.7.0 - 2019-10-04
|
||||
* Update `rustls` to 0.16
|
||||
* Minimum required Rust version upped to 1.37.0
|
||||
- Added explicit info log message on accept queue pause.
|
||||
- Prevent double registration of sockets when back-pressure is resolved.
|
||||
- Update `mio` dependency to `0.7.3`.
|
||||
- Remove `socket2` dependency.
|
||||
- `ServerBuilder::backlog` now accepts `u32` instead of `i32`.
|
||||
- Remove `AcceptNotify` type and pass `WakerQueue` to `Worker` to wake up `Accept`'s `Poll`.
|
||||
- Convert `mio::net::TcpStream` to `actix_rt::net::TcpStream`(`UnixStream` for uds) using `FromRawFd` and `IntoRawFd`(`FromRawSocket` and `IntoRawSocket` on windows).
|
||||
- Remove `AsyncRead` and `AsyncWrite` trait bound for `socket::FromStream` trait.
|
||||
|
||||
## 1.0.4
|
||||
|
||||
## 0.6.1 - 2019-09-25
|
||||
* Add UDS listening support to `ServerBuilder`
|
||||
- Update actix-codec to 0.3.0.
|
||||
- Workers must be greater than 0.
|
||||
|
||||
## 1.0.3
|
||||
|
||||
## 0.6.0 - 2019-07-18
|
||||
* Support Unix domain sockets #3
|
||||
- Replace deprecated `net2` crate with `socket2`.
|
||||
|
||||
## 1.0.2
|
||||
|
||||
## 0.5.1 - 2019-05-18
|
||||
* ServerBuilder::shutdown_timeout() accepts u64
|
||||
- Avoid error by calling `reregister()` on Windows.
|
||||
|
||||
## 1.0.1
|
||||
|
||||
## 0.5.0 - 2019-05-12
|
||||
* Add `Debug` impl for `SslError`
|
||||
* Derive debug for `Server` and `ServerCommand`
|
||||
* Upgrade to actix-service 0.4
|
||||
- Rename `.start()` method to `.run()`
|
||||
|
||||
## 1.0.0
|
||||
|
||||
## 0.4.3 - 2019-04-16
|
||||
* Re-export `IoStream` trait
|
||||
* Depend on `ssl` and `rust-tls` features from actix-server-config
|
||||
- Use actix-net releases
|
||||
|
||||
## 1.0.0-alpha.4
|
||||
|
||||
## 0.4.2 - 2019-03-30
|
||||
* Fix SIGINT force shutdown
|
||||
- Use actix-service 1.0.0-alpha.4
|
||||
|
||||
## 1.0.0-alpha.3
|
||||
|
||||
## 0.4.1 - 2019-03-14
|
||||
* `SystemRuntime::on_start()` - allow to run future before server service initialization
|
||||
- Migrate to tokio 0.2
|
||||
- Fix compilation on non-unix platforms
|
||||
- Better handling server configuration
|
||||
|
||||
## 1.0.0-alpha.2
|
||||
|
||||
## 0.4.0 - 2019-03-12
|
||||
* Use `ServerConfig` for service factory
|
||||
* Wrap tcp socket to `Io` type
|
||||
* Upgrade actix-service
|
||||
- Simplify server service (remove actix-server-config)
|
||||
- Allow to wait on `Server` until server stops
|
||||
|
||||
## 0.8.0-alpha.1
|
||||
|
||||
## 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`
|
||||
- Migrate to `std::future`
|
||||
|
||||
## 0.7.0
|
||||
|
||||
## 0.3.0 - 2019-03-02
|
||||
* Use new `NewService` trait
|
||||
- Update `rustls` to 0.16
|
||||
- Minimum required Rust version upped to 1.37.0
|
||||
|
||||
## 0.6.1
|
||||
|
||||
## 0.2.1 - 2019-02-09
|
||||
* Drop service response
|
||||
- Add UDS listening support to `ServerBuilder`
|
||||
|
||||
## 0.6.0
|
||||
|
||||
## 0.2.0 - 2019-02-01
|
||||
* Migrate to actix-service 0.2
|
||||
* Updated rustls dependency
|
||||
- Support Unix domain sockets #3
|
||||
|
||||
## 0.5.1
|
||||
|
||||
## 0.1.3 - 2018-12-21
|
||||
* Fix max concurrent connections handling
|
||||
- ServerBuilder::shutdown_timeout() accepts u64
|
||||
|
||||
## 0.5.0
|
||||
|
||||
## 0.1.2 - 2018-12-12
|
||||
* rename ServiceConfig::rt() to ServiceConfig::apply()
|
||||
* Fix back-pressure for concurrent ssl handshakes
|
||||
- Add `Debug` impl for `SslError`
|
||||
- Derive debug for `Server` and `ServerCommand`
|
||||
- Upgrade to actix-service 0.4
|
||||
|
||||
## 0.4.3
|
||||
|
||||
## 0.1.1 - 2018-12-11
|
||||
* Fix signal handling on windows
|
||||
- Re-export `IoStream` trait
|
||||
- Depend on `ssl` and `rust-tls` features from actix-server-config
|
||||
|
||||
## 0.4.2
|
||||
|
||||
## 0.1.0 - 2018-12-09
|
||||
* Move server to separate crate
|
||||
- Fix SIGINT force shutdown
|
||||
|
||||
## 0.4.1
|
||||
|
||||
- `SystemRuntime::on_start()` - allow to run future before server service initialization
|
||||
|
||||
## 0.4.0
|
||||
|
||||
- Use `ServerConfig` for service factory
|
||||
- Wrap tcp socket to `Io` type
|
||||
- Upgrade actix-service
|
||||
|
||||
## 0.3.1
|
||||
|
||||
- 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
|
||||
|
||||
- Use new `NewService` trait
|
||||
|
||||
## 0.2.1
|
||||
|
||||
- Drop service response
|
||||
|
||||
## 0.2.0
|
||||
|
||||
- Migrate to actix-service 0.2
|
||||
- Updated rustls dependency
|
||||
|
||||
## 0.1.3
|
||||
|
||||
- Fix max concurrent connections handling
|
||||
|
||||
## 0.1.2
|
||||
|
||||
- rename ServiceConfig::rt() to ServiceConfig::apply()
|
||||
- Fix back-pressure for concurrent ssl handshakes
|
||||
|
||||
## 0.1.1
|
||||
|
||||
- Fix signal handling on windows
|
||||
|
||||
## 0.1.0
|
||||
|
||||
- Move server to separate crate
|
||||
|
55
actix-server/Cargo.toml
Executable file → Normal file
55
actix-server/Cargo.toml
Executable file → Normal file
@ -1,41 +1,50 @@
|
||||
[package]
|
||||
name = "actix-server"
|
||||
version = "2.0.0-beta.6"
|
||||
version = "2.5.1"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"fakeshadow <24548779@qq.com>",
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
"Ali MJ Al-Nasrawy <alimjalnasrawy@gmail.com>",
|
||||
]
|
||||
description = "General purpose TCP server built for the Actix ecosystem"
|
||||
keywords = ["network", "framework", "async", "futures"]
|
||||
repository = "https://github.com/actix/actix-net.git"
|
||||
keywords = ["network", "tcp", "server", "framework", "async"]
|
||||
categories = ["network-programming", "asynchronous"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-net/tree/master/actix-server"
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "actix_server"
|
||||
path = "src/lib.rs"
|
||||
[package.metadata.cargo_check_external_types]
|
||||
allowed_external_types = ["tokio::*"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
io-uring = ["actix-rt/io-uring"]
|
||||
io-uring = ["tokio-uring", "actix-rt/io-uring"]
|
||||
|
||||
[dependencies]
|
||||
actix-rt = { version = "2.0.0", default-features = false }
|
||||
actix-service = "2.0.0"
|
||||
actix-utils = "3.0.0"
|
||||
actix-rt = { version = "2.10", default-features = false }
|
||||
actix-service = "2"
|
||||
actix-utils = "3"
|
||||
futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] }
|
||||
futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] }
|
||||
mio = { version = "1", features = ["os-poll", "net"] }
|
||||
socket2 = "0.5"
|
||||
tokio = { version = "1.23.1", features = ["sync"] }
|
||||
tracing = { version = "0.1.30", default-features = false, features = ["log"] }
|
||||
|
||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||
log = "0.4"
|
||||
mio = { version = "0.7.6", features = ["os-poll", "net"] }
|
||||
num_cpus = "1.13"
|
||||
tokio = { version = "1.5.1", features = ["sync"] }
|
||||
# runtime for `io-uring` feature
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
tokio-uring = { version = "0.5", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-codec = "0.4.0"
|
||||
actix-rt = "2.0.0"
|
||||
actix-codec = "0.5"
|
||||
actix-rt = "2.8"
|
||||
|
||||
bytes = "1"
|
||||
env_logger = "0.9"
|
||||
futures-util = { version = "0.3.7", default-features = false, features = ["sink"] }
|
||||
tokio = { version = "1.5.1", features = ["io-util", "rt-multi-thread", "macros"] }
|
||||
futures-util = { version = "0.3.17", default-features = false, features = ["sink", "async-await-macro"] }
|
||||
pretty_env_logger = "0.5"
|
||||
tokio = { version = "1.23.1", features = ["io-util", "rt-multi-thread", "macros", "fs"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
21
actix-server/README.md
Normal file
21
actix-server/README.md
Normal file
@ -0,0 +1,21 @@
|
||||
# actix-server
|
||||
|
||||
> General purpose TCP server built for the Actix ecosystem.
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
[](https://crates.io/crates/actix-server)
|
||||
[](https://docs.rs/actix-server/2.5.1)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
||||

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

|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
## Resources
|
||||
|
||||
- [Library Documentation](https://docs.rs/actix-server)
|
||||
- [Examples](/actix-server/examples)
|
98
actix-server/examples/file-reader.rs
Normal file
98
actix-server/examples/file-reader.rs
Normal file
@ -0,0 +1,98 @@
|
||||
//! 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.
|
||||
|
||||
#![allow(missing_docs)]
|
||||
|
||||
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<()> {
|
||||
pretty_env_logger::formatted_timed_builder()
|
||||
.parse_env(pretty_env_logger::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,20 +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::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
|
||||
pretty_env_logger::formatted_timed_builder()
|
||||
.parse_env(pretty_env_logger::env_logger::Env::default().default_filter_or("info"));
|
||||
|
||||
let count = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let addr = ("127.0.0.1", 8080);
|
||||
info!("starting server on port: {}", &addr.0);
|
||||
tracing::info!("starting server on port: {}", &addr.0);
|
||||
|
||||
// Bind socket address and start worker(s). By default, the server uses the number of available
|
||||
// logical CPU cores as the worker count. For this reason, the closure passed to bind needs
|
||||
// to return a service *factory*; so it can be created once per worker.
|
||||
// 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("echo", addr, move || {
|
||||
let count = Arc::clone(&count);
|
||||
@ -58,14 +58,14 @@ async fn run() -> io::Result<()> {
|
||||
|
||||
// more bytes to process
|
||||
Ok(bytes_read) => {
|
||||
info!("[{}] read {} bytes", num, bytes_read);
|
||||
tracing::info!("[{}] read {} bytes", num, bytes_read);
|
||||
stream.write_all(&buf[size..]).await.unwrap();
|
||||
size += bytes_read;
|
||||
}
|
||||
|
||||
// stream error; bail from loop with error
|
||||
Err(err) => {
|
||||
error!("Stream Error: {:?}", err);
|
||||
tracing::error!("stream error: {:?}", err);
|
||||
return Err(());
|
||||
}
|
||||
}
|
||||
@ -75,14 +75,14 @@ async fn run() -> io::Result<()> {
|
||||
Ok((buf.freeze(), size))
|
||||
}
|
||||
})
|
||||
.map_err(|err| error!("Service Error: {:?}", err))
|
||||
.map_err(|err| tracing::error!("service error: {:?}", err))
|
||||
.and_then(move |(_, size)| {
|
||||
let num = num2.load(Ordering::SeqCst);
|
||||
info!("[{}] total bytes read: {}", num, size);
|
||||
tracing::info!("[{}] total bytes read: {}", num, size);
|
||||
ok(size)
|
||||
})
|
||||
})?
|
||||
.workers(1)
|
||||
.workers(2)
|
||||
.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>)> {
|
||||
) -> io::Result<(WakerQueue, Vec<WorkerHandleServer>, thread::JoinHandle<()>)> {
|
||||
let handle_server = ServerHandle::new(builder.cmd_tx.clone());
|
||||
|
||||
// construct poll instance and its waker
|
||||
@ -73,12 +73,12 @@ impl Accept {
|
||||
handle_server,
|
||||
)?;
|
||||
|
||||
thread::Builder::new()
|
||||
let accept_handle = 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))
|
||||
Ok((waker_queue, handles_server, accept_handle))
|
||||
}
|
||||
|
||||
fn new_with_sockets(
|
||||
@ -127,10 +127,10 @@ impl Accept {
|
||||
let mut events = mio::Events::with_capacity(256);
|
||||
|
||||
loop {
|
||||
if let Err(e) = self.poll.poll(&mut events, None) {
|
||||
match e.kind() {
|
||||
if let Err(err) = self.poll.poll(&mut events, self.timeout) {
|
||||
match err.kind() {
|
||||
io::ErrorKind::Interrupted => {}
|
||||
_ => panic!("Poll error: {}", e),
|
||||
_ => panic!("Poll error: {}", err),
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,11 +161,13 @@ 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 interest can be added
|
||||
// until the current task is done.
|
||||
// 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.
|
||||
let mut guard = self.waker_queue.guard();
|
||||
|
||||
#[allow(clippy::significant_drop_in_scrutinee)]
|
||||
match guard.pop_front() {
|
||||
// worker notify it becomes available.
|
||||
// Worker notified it became available.
|
||||
Some(WakerInterest::WorkerAvailable(idx)) => {
|
||||
drop(guard);
|
||||
|
||||
@ -176,7 +178,7 @@ impl Accept {
|
||||
}
|
||||
}
|
||||
|
||||
// a new worker thread is made and it's handle would be added to Accept
|
||||
// A new worker thread has been created so store its handle.
|
||||
Some(WakerInterest::Worker(handle)) => {
|
||||
drop(guard);
|
||||
|
||||
@ -297,16 +299,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(e) => error!("Can not register server socket {}", e),
|
||||
Ok(_) => debug!("resume accepting connections on {}", info.lst.local_addr()),
|
||||
Err(err) => error!("can not register server socket {}", err),
|
||||
}
|
||||
}
|
||||
|
||||
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(e) => {
|
||||
error!("Can not deregister server socket {}", e)
|
||||
Ok(_) => debug!("paused accepting connections on {}", info.lst.local_addr()),
|
||||
Err(err) => {
|
||||
error!("can not deregister server socket {}", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -350,7 +352,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(());
|
||||
@ -396,10 +398,10 @@ impl Accept {
|
||||
let conn = Conn { io, token };
|
||||
self.accept_one(conn);
|
||||
}
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => return,
|
||||
Err(ref e) if connection_error(e) => continue,
|
||||
Err(e) => {
|
||||
error!("Error accepting connection: {}", e);
|
||||
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);
|
||||
|
||||
// deregister listener temporary
|
||||
self.deregister_logged(info);
|
||||
|
@ -1,19 +1,34 @@
|
||||
use std::{io, time::Duration};
|
||||
use std::{io, num::NonZeroUsize, time::Duration};
|
||||
|
||||
use actix_rt::net::TcpStream;
|
||||
use log::{info, trace};
|
||||
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
|
||||
|
||||
use crate::{
|
||||
server::ServerCommand,
|
||||
service::{InternalServiceFactory, ServiceFactory, StreamNewService},
|
||||
socket::{
|
||||
MioListener, MioTcpListener, MioTcpSocket, StdSocketAddr, StdTcpListener, ToSocketAddrs,
|
||||
},
|
||||
service::{InternalServiceFactory, ServerServiceFactory, StreamNewService},
|
||||
socket::{create_mio_tcp_listener, MioListener, MioTcpListener, StdTcpListener, ToSocketAddrs},
|
||||
worker::ServerWorkerConfig,
|
||||
Server,
|
||||
};
|
||||
|
||||
/// Multipath TCP (MPTCP) preference.
|
||||
///
|
||||
/// Currently only useful on Linux.
|
||||
///
|
||||
#[cfg_attr(target_os = "linux", doc = "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,
|
||||
@ -21,6 +36,7 @@ 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>,
|
||||
@ -40,11 +56,12 @@ impl ServerBuilder {
|
||||
let (cmd_tx, cmd_rx) = unbounded_channel();
|
||||
|
||||
ServerBuilder {
|
||||
threads: num_cpus::get(),
|
||||
threads: std::thread::available_parallelism().map_or(2, NonZeroUsize::get),
|
||||
token: 0,
|
||||
factories: Vec::new(),
|
||||
sockets: Vec::new(),
|
||||
backlog: 2048,
|
||||
mptcp: MpTcp::Disabled,
|
||||
exit: false,
|
||||
listen_os_signals: true,
|
||||
cmd_tx,
|
||||
@ -53,10 +70,19 @@ impl ServerBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
/// Set number of workers to start.
|
||||
/// Sets number of workers to start.
|
||||
///
|
||||
/// By default server uses number of available logical CPU as workers count. Workers must be
|
||||
/// greater than 0.
|
||||
/// See [`bind()`](Self::bind()) for more details on how worker count affects the number of
|
||||
/// server factory instantiations.
|
||||
///
|
||||
/// The default worker count is the determined by [`std::thread::available_parallelism()`]. See
|
||||
/// its documentation to determine what behavior you should expect when server is run.
|
||||
///
|
||||
/// `num` must be greater than 0.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `num` is 0.
|
||||
pub fn workers(mut self, num: usize) -> Self {
|
||||
assert_ne!(num, 0, "workers must be greater than 0");
|
||||
self.threads = num;
|
||||
@ -95,6 +121,24 @@ 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
|
||||
@ -112,13 +156,15 @@ impl ServerBuilder {
|
||||
self.max_concurrent_connections(num)
|
||||
}
|
||||
|
||||
/// Stop Actix system.
|
||||
/// Sets flag to stop Actix `System` after server shutdown.
|
||||
///
|
||||
/// This has no effect when server is running in a Tokio-only runtime.
|
||||
pub fn system_exit(mut self) -> Self {
|
||||
self.exit = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Disable OS signal handling.
|
||||
/// Disables OS signal handling.
|
||||
pub fn disable_signals(mut self) -> Self {
|
||||
self.listen_os_signals = false;
|
||||
self
|
||||
@ -136,24 +182,49 @@ impl ServerBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Add new service to the server.
|
||||
pub fn bind<F, U, N: AsRef<str>>(mut self, name: N, addr: U, factory: F) -> io::Result<Self>
|
||||
/// Adds new service to the server.
|
||||
///
|
||||
/// Note that, if a DNS lookup is required, resolving hostnames is a blocking operation.
|
||||
///
|
||||
/// # Worker Count
|
||||
///
|
||||
/// The `factory` will be instantiated multiple times in most scenarios. The number of
|
||||
/// instantiations is number of [`workers`](Self::workers()) × number of sockets resolved by
|
||||
/// `addrs`.
|
||||
///
|
||||
/// For example, if you've manually set [`workers`](Self::workers()) to 2, and use `127.0.0.1`
|
||||
/// as the bind `addrs`, then `factory` will be instantiated twice. However, using `localhost`
|
||||
/// as the bind `addrs` can often resolve to both `127.0.0.1` (IPv4) _and_ `::1` (IPv6), causing
|
||||
/// the `factory` to be instantiated 4 times (2 workers × 2 bind addresses).
|
||||
///
|
||||
/// Using a bind address of `0.0.0.0`, which signals to use all interfaces, may also multiple
|
||||
/// the number of instantiations in a similar way.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an `io::Error` if:
|
||||
/// - `addrs` cannot be resolved into one or more socket addresses;
|
||||
/// - all the resolved socket addresses are already bound.
|
||||
pub fn bind<F, U, N>(mut self, name: N, addrs: U, factory: F) -> io::Result<Self>
|
||||
where
|
||||
F: ServiceFactory<TcpStream>,
|
||||
F: ServerServiceFactory<TcpStream>,
|
||||
U: ToSocketAddrs,
|
||||
N: AsRef<str>,
|
||||
{
|
||||
let sockets = bind_addr(addr, self.backlog)?;
|
||||
let sockets = bind_addr(addrs, self.backlog, &self.mptcp)?;
|
||||
|
||||
trace!("binding server to: {:?}", &sockets);
|
||||
tracing::trace!("binding server to: {sockets:?}");
|
||||
|
||||
for lst in sockets {
|
||||
let token = self.next_token();
|
||||
|
||||
self.factories.push(StreamNewService::create(
|
||||
name.as_ref().to_string(),
|
||||
token,
|
||||
factory.clone(),
|
||||
lst.local_addr()?,
|
||||
));
|
||||
|
||||
self.sockets
|
||||
.push((token, name.as_ref().to_string(), MioListener::Tcp(lst)));
|
||||
}
|
||||
@ -161,7 +232,12 @@ impl ServerBuilder {
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Add new service to the server.
|
||||
/// Adds service to the server using a socket listener already bound.
|
||||
///
|
||||
/// # Worker Count
|
||||
///
|
||||
/// The `factory` will be instantiated multiple times in most scenarios. The number of
|
||||
/// instantiations is: number of [`workers`](Self::workers()).
|
||||
pub fn listen<F, N: AsRef<str>>(
|
||||
mut self,
|
||||
name: N,
|
||||
@ -169,7 +245,7 @@ impl ServerBuilder {
|
||||
factory: F,
|
||||
) -> io::Result<Self>
|
||||
where
|
||||
F: ServiceFactory<TcpStream>,
|
||||
F: ServerServiceFactory<TcpStream>,
|
||||
{
|
||||
lst.set_nonblocking(true)?;
|
||||
let addr = lst.local_addr()?;
|
||||
@ -193,7 +269,7 @@ impl ServerBuilder {
|
||||
if self.sockets.is_empty() {
|
||||
panic!("Server should have at least one bound socket");
|
||||
} else {
|
||||
info!("Starting {} workers", self.threads);
|
||||
tracing::info!("starting {} workers", self.threads);
|
||||
Server::new(self)
|
||||
}
|
||||
}
|
||||
@ -207,19 +283,24 @@ impl ServerBuilder {
|
||||
|
||||
#[cfg(unix)]
|
||||
impl ServerBuilder {
|
||||
/// Add new unix domain service to the server.
|
||||
/// Adds new service to the server using a UDS (unix domain socket) address.
|
||||
///
|
||||
/// # Worker Count
|
||||
///
|
||||
/// The `factory` will be instantiated multiple times in most scenarios. The number of
|
||||
/// instantiations is: number of [`workers`](Self::workers()).
|
||||
pub fn bind_uds<F, U, N>(self, name: N, addr: U, factory: F) -> io::Result<Self>
|
||||
where
|
||||
F: ServiceFactory<actix_rt::net::UnixStream>,
|
||||
F: ServerServiceFactory<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(e) = std::fs::remove_file(addr.as_ref()) {
|
||||
if let Err(err) = std::fs::remove_file(addr.as_ref()) {
|
||||
// NotFound is expected and not an issue. Anything else is.
|
||||
if e.kind() != std::io::ErrorKind::NotFound {
|
||||
return Err(e);
|
||||
if err.kind() != std::io::ErrorKind::NotFound {
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
|
||||
@ -227,9 +308,14 @@ impl ServerBuilder {
|
||||
self.listen_uds(name, lst, factory)
|
||||
}
|
||||
|
||||
/// Add new unix domain service to the server.
|
||||
/// Adds new service to the server using a UDS (unix domain socket) listener already bound.
|
||||
///
|
||||
/// Useful when running as a systemd service and a socket FD is acquired externally.
|
||||
///
|
||||
/// # Worker Count
|
||||
///
|
||||
/// The `factory` will be instantiated multiple times in most scenarios. The number of
|
||||
/// instantiations is: number of [`workers`](Self::workers()).
|
||||
pub fn listen_uds<F, N: AsRef<str>>(
|
||||
mut self,
|
||||
name: N,
|
||||
@ -237,20 +323,25 @@ impl ServerBuilder {
|
||||
factory: F,
|
||||
) -> io::Result<Self>
|
||||
where
|
||||
F: ServiceFactory<actix_rt::net::UnixStream>,
|
||||
F: ServerServiceFactory<actix_rt::net::UnixStream>,
|
||||
{
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
|
||||
lst.set_nonblocking(true)?;
|
||||
|
||||
let token = self.next_token();
|
||||
let addr = StdSocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
|
||||
let addr = crate::socket::StdSocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
|
||||
|
||||
self.factories.push(StreamNewService::create(
|
||||
name.as_ref().to_string(),
|
||||
token,
|
||||
factory,
|
||||
addr,
|
||||
));
|
||||
|
||||
self.sockets
|
||||
.push((token, name.as_ref().to_string(), MioListener::from(lst)));
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
@ -258,23 +349,25 @@ impl ServerBuilder {
|
||||
pub(super) fn bind_addr<S: ToSocketAddrs>(
|
||||
addr: S,
|
||||
backlog: u32,
|
||||
mptcp: &MpTcp,
|
||||
) -> io::Result<Vec<MioTcpListener>> {
|
||||
let mut err = None;
|
||||
let mut opt_err = None;
|
||||
let mut success = false;
|
||||
let mut sockets = Vec::new();
|
||||
|
||||
for addr in addr.to_socket_addrs()? {
|
||||
match create_tcp_listener(addr, backlog) {
|
||||
match create_mio_tcp_listener(addr, backlog, mptcp) {
|
||||
Ok(lst) => {
|
||||
success = true;
|
||||
sockets.push(lst);
|
||||
}
|
||||
Err(e) => err = Some(e),
|
||||
Err(err) => opt_err = Some(err),
|
||||
}
|
||||
}
|
||||
|
||||
if success {
|
||||
Ok(sockets)
|
||||
} else if let Some(err) = err.take() {
|
||||
} else if let Some(err) = opt_err.take() {
|
||||
Err(err)
|
||||
} else {
|
||||
Err(io::Error::new(
|
||||
@ -283,14 +376,3 @@ pub(super) fn bind_addr<S: ToSocketAddrs>(
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn create_tcp_listener(addr: StdSocketAddr, backlog: u32) -> io::Result<MioTcpListener> {
|
||||
let socket = match addr {
|
||||
StdSocketAddr::V4(_) => MioTcpSocket::new_v4()?,
|
||||
StdSocketAddr::V6(_) => MioTcpSocket::new_v6()?,
|
||||
};
|
||||
|
||||
socket.set_reuseaddr(true)?;
|
||||
socket.bind(addr)?;
|
||||
socket.listen(backlog)
|
||||
}
|
||||
|
@ -42,10 +42,13 @@ 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, LocalBoxFuture};
|
||||
use futures_core::future::BoxFuture;
|
||||
|
||||
// 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,69 +61,12 @@ 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 super::*;
|
||||
|
||||
use actix_utils::future::ready;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_join_all() {
|
||||
let futs = vec![ready(Ok(1)), ready(Err(3)), ready(Ok(9))];
|
||||
@ -132,13 +75,4 @@ 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,6 +1,5 @@
|
||||
//! General purpose TCP server.
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
|
||||
@ -17,14 +16,15 @@ 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,23 +3,22 @@ use std::{
|
||||
io, mem,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use actix_rt::{time::sleep, System};
|
||||
use futures_core::future::BoxFuture;
|
||||
use log::{error, info};
|
||||
use tokio::sync::{
|
||||
mpsc::{UnboundedReceiver, UnboundedSender},
|
||||
oneshot,
|
||||
};
|
||||
use futures_core::{future::BoxFuture, Stream};
|
||||
use futures_util::stream::StreamExt as _;
|
||||
use tokio::sync::{mpsc::UnboundedReceiver, oneshot};
|
||||
use tracing::{error, info};
|
||||
|
||||
use crate::{
|
||||
accept::Accept,
|
||||
builder::ServerBuilder,
|
||||
join_all::join_all,
|
||||
service::InternalServiceFactory,
|
||||
signals::{Signal, Signals},
|
||||
signals::{SignalKind, Signals},
|
||||
waker_queue::{WakerInterest, WakerQueue},
|
||||
worker::{ServerWorker, ServerWorkerConfig, WorkerHandleServer},
|
||||
ServerHandle,
|
||||
@ -27,22 +26,31 @@ use crate::{
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ServerCommand {
|
||||
/// TODO
|
||||
/// Worker failed to accept connection, indicating a probable panic.
|
||||
///
|
||||
/// Contains index of faulted worker.
|
||||
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<()>),
|
||||
|
||||
/// TODO
|
||||
/// Stop accepting connections and begin shutdown procedure.
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
||||
@ -54,11 +62,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 to process stop commands and listen for OS signals. It will resolve
|
||||
/// when the server has fully shut down.
|
||||
/// The [Server] must be awaited or polled in order to start running. It will resolve when the
|
||||
/// server has fully shut down.
|
||||
///
|
||||
/// # Shutdown Signals
|
||||
/// On UNIX systems, `SIGQUIT` will start a graceful shutdown and `SIGTERM` or `SIGINT` will start a
|
||||
/// On UNIX systems, `SIGTERM` will start a graceful shutdown and `SIGQUIT` 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.
|
||||
@ -113,10 +121,10 @@ pub(crate) enum ServerCommand {
|
||||
/// .await
|
||||
/// }
|
||||
/// ```
|
||||
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||
pub enum Server {
|
||||
Server(ServerInner),
|
||||
Error(Option<io::Error>),
|
||||
#[must_use = "Server does nothing unless you `.await` or poll it"]
|
||||
pub struct Server {
|
||||
handle: ServerHandle,
|
||||
fut: BoxFuture<'static, io::Result<()>>,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
@ -125,176 +133,157 @@ impl Server {
|
||||
ServerBuilder::default()
|
||||
}
|
||||
|
||||
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_tokio = tokio::runtime::Handle::try_current().is_ok();
|
||||
let is_actix = actix_rt::System::try_current().is_some();
|
||||
|
||||
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: {}"#,
|
||||
name,
|
||||
builder.threads,
|
||||
lst.local_addr()
|
||||
);
|
||||
}
|
||||
|
||||
match Accept::start(sockets, &builder) {
|
||||
Ok((waker_queue, worker_handles)) => {
|
||||
// construct OS signals listener future
|
||||
let signals = (builder.listen_os_signals).then(Signals::new);
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
Err(err) => Self::Error(Some(err)),
|
||||
pub(crate) fn new(builder: ServerBuilder) -> Self {
|
||||
Server {
|
||||
handle: ServerHandle::new(builder.cmd_tx.clone()),
|
||||
fut: Box::pin(ServerInner::run(builder)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a handle for ServerFuture that can be used to change state of actix server.
|
||||
/// 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 {
|
||||
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()
|
||||
);
|
||||
}
|
||||
}
|
||||
self.handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[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>>,
|
||||
exit: bool,
|
||||
cmd_tx: UnboundedSender<ServerCommand>,
|
||||
cmd_rx: UnboundedReceiver<ServerCommand>,
|
||||
signals: Option<Signals>,
|
||||
waker_queue: WakerQueue,
|
||||
stop_task: Option<BoxFuture<'static, ()>>,
|
||||
system_stop: bool,
|
||||
stopping: bool,
|
||||
}
|
||||
|
||||
impl ServerInner {
|
||||
fn handle_cmd(&mut self, item: ServerCommand) -> Option<BoxFuture<'static, ()>> {
|
||||
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)> {
|
||||
// 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();
|
||||
|
||||
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"),
|
||||
}
|
||||
|
||||
for (_, name, lst) in &builder.sockets {
|
||||
info!(
|
||||
r#"starting service: "{}", workers: {}, listening on: {}"#,
|
||||
name,
|
||||
builder.threads,
|
||||
lst.local_addr()
|
||||
);
|
||||
}
|
||||
|
||||
let sockets = mem::take(&mut builder.sockets)
|
||||
.into_iter()
|
||||
.map(|t| (t.0, t.2))
|
||||
.collect();
|
||||
|
||||
let (waker_queue, worker_handles, accept_handle) = Accept::start(sockets, &builder)?;
|
||||
|
||||
let mux = ServerEventMultiplexer {
|
||||
signal_fut: (builder.listen_os_signals).then(Signals::new),
|
||||
cmd_rx: builder.cmd_rx,
|
||||
};
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
async fn handle_cmd(&mut self, item: ServerCommand) {
|
||||
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,
|
||||
} => {
|
||||
let exit = self.exit;
|
||||
self.stopping = true;
|
||||
|
||||
// stop accept thread
|
||||
// Signal accept thread to stop.
|
||||
// Signal is non-blocking; we wait for thread to stop later.
|
||||
self.waker_queue.wake(WakerInterest::Stop);
|
||||
|
||||
// stop workers
|
||||
// send stop signal to workers
|
||||
let workers_stop = self
|
||||
.worker_handles
|
||||
.iter()
|
||||
.map(|worker| worker.stop(graceful))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Some(Box::pin(async move {
|
||||
if graceful {
|
||||
// wait for all workers to shut down
|
||||
let _ = join_all(workers_stop).await;
|
||||
}
|
||||
if graceful {
|
||||
// wait for all workers to shut down
|
||||
let _ = join_all(workers_stop).await;
|
||||
}
|
||||
|
||||
if let Some(tx) = completion {
|
||||
let _ = tx.send(());
|
||||
}
|
||||
// wait for accept thread stop
|
||||
self.accept_handle
|
||||
.take()
|
||||
.unwrap()
|
||||
.join()
|
||||
.expect("Accept thread must not panic in any case");
|
||||
|
||||
if exit {
|
||||
sleep(Duration::from_millis(300)).await;
|
||||
System::try_current().as_ref().map(System::stop);
|
||||
}
|
||||
}))
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@ -320,40 +309,60 @@ impl ServerInner {
|
||||
|
||||
Err(err) => error!("can not restart worker {}: {}", idx, err),
|
||||
};
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_signal(&mut self, signal: Signal) -> Option<BoxFuture<'static, ()>> {
|
||||
fn map_signal(signal: SignalKind) -> ServerCommand {
|
||||
match signal {
|
||||
Signal::Int => {
|
||||
SignalKind::Int => {
|
||||
info!("SIGINT received; starting forced shutdown");
|
||||
self.exit = true;
|
||||
self.handle_cmd(ServerCommand::Stop {
|
||||
ServerCommand::Stop {
|
||||
graceful: false,
|
||||
completion: None,
|
||||
})
|
||||
force_system_stop: true,
|
||||
}
|
||||
}
|
||||
|
||||
Signal::Term => {
|
||||
SignalKind::Term => {
|
||||
info!("SIGTERM received; starting graceful shutdown");
|
||||
self.exit = true;
|
||||
self.handle_cmd(ServerCommand::Stop {
|
||||
ServerCommand::Stop {
|
||||
graceful: true,
|
||||
completion: None,
|
||||
})
|
||||
force_system_stop: true,
|
||||
}
|
||||
}
|
||||
|
||||
Signal::Quit => {
|
||||
SignalKind::Quit => {
|
||||
info!("SIGQUIT received; starting forced shutdown");
|
||||
self.exit = true;
|
||||
self.handle_cmd(ServerCommand::Stop {
|
||||
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,16 +1,21 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::net::SocketAddr;
|
||||
use std::task::{Context, Poll};
|
||||
use std::{
|
||||
marker::PhantomData,
|
||||
net::SocketAddr,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use actix_service::{Service, ServiceFactory as BaseServiceFactory};
|
||||
use actix_utils::future::{ready, Ready};
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use log::error;
|
||||
use tracing::error;
|
||||
|
||||
use crate::socket::{FromStream, MioStream};
|
||||
use crate::worker::WorkerCounterGuard;
|
||||
use crate::{
|
||||
socket::{FromStream, MioStream},
|
||||
worker::WorkerCounterGuard,
|
||||
};
|
||||
|
||||
pub trait ServiceFactory<Stream: FromStream>: Send + Clone + 'static {
|
||||
#[doc(hidden)]
|
||||
pub trait ServerServiceFactory<Stream: FromStream>: Send + Clone + 'static {
|
||||
type Factory: BaseServiceFactory<Stream, Config = ()>;
|
||||
|
||||
fn create(&self) -> Self::Factory;
|
||||
@ -72,15 +77,15 @@ where
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Can not convert to an async tcp stream: {}", e);
|
||||
Err(err) => {
|
||||
error!("can not convert to an async TCP stream: {err}");
|
||||
Err(())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct StreamNewService<F: ServiceFactory<Io>, Io: FromStream> {
|
||||
pub(crate) struct StreamNewService<F: ServerServiceFactory<Io>, Io: FromStream> {
|
||||
name: String,
|
||||
inner: F,
|
||||
token: usize,
|
||||
@ -90,7 +95,7 @@ pub(crate) struct StreamNewService<F: ServiceFactory<Io>, Io: FromStream> {
|
||||
|
||||
impl<F, Io> StreamNewService<F, Io>
|
||||
where
|
||||
F: ServiceFactory<Io>,
|
||||
F: ServerServiceFactory<Io>,
|
||||
Io: FromStream + Send + 'static,
|
||||
{
|
||||
pub(crate) fn create(
|
||||
@ -111,7 +116,7 @@ where
|
||||
|
||||
impl<F, Io> InternalServiceFactory for StreamNewService<F, Io>
|
||||
where
|
||||
F: ServiceFactory<Io>,
|
||||
F: ServerServiceFactory<Io>,
|
||||
Io: FromStream + Send + 'static,
|
||||
{
|
||||
fn name(&self, _: usize) -> &str {
|
||||
@ -143,7 +148,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, T, I> ServiceFactory<I> for F
|
||||
impl<F, T, I> ServerServiceFactory<I> for F
|
||||
where
|
||||
F: Fn() -> T + Send + Clone + 'static,
|
||||
T: BaseServiceFactory<I, Config = ()>,
|
||||
|
@ -5,13 +5,13 @@ use std::{
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use log::trace;
|
||||
use tracing::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 Signal {
|
||||
pub(crate) enum SignalKind {
|
||||
/// `SIGINT`
|
||||
Int,
|
||||
|
||||
@ -22,12 +22,12 @@ pub(crate) enum Signal {
|
||||
Quit,
|
||||
}
|
||||
|
||||
impl fmt::Display for Signal {
|
||||
impl fmt::Display for SignalKind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(match self {
|
||||
Signal::Int => "SIGINT",
|
||||
Signal::Term => "SIGTERM",
|
||||
Signal::Quit => "SIGQUIT",
|
||||
SignalKind::Int => "SIGINT",
|
||||
SignalKind::Term => "SIGTERM",
|
||||
SignalKind::Quit => "SIGQUIT",
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -35,10 +35,10 @@ impl fmt::Display for Signal {
|
||||
/// Process signal listener.
|
||||
pub(crate) struct Signals {
|
||||
#[cfg(not(unix))]
|
||||
signals: futures_core::future::LocalBoxFuture<'static, std::io::Result<()>>,
|
||||
signals: futures_core::future::BoxFuture<'static, std::io::Result<()>>,
|
||||
|
||||
#[cfg(unix)]
|
||||
signals: Vec<(Signal, actix_rt::signal::unix::Signal)>,
|
||||
signals: Vec<(SignalKind, actix_rt::signal::unix::Signal)>,
|
||||
}
|
||||
|
||||
impl Signals {
|
||||
@ -58,9 +58,9 @@ impl Signals {
|
||||
use actix_rt::signal::unix;
|
||||
|
||||
let sig_map = [
|
||||
(unix::SignalKind::interrupt(), Signal::Int),
|
||||
(unix::SignalKind::terminate(), Signal::Term),
|
||||
(unix::SignalKind::quit(), Signal::Quit),
|
||||
(unix::SignalKind::interrupt(), SignalKind::Int),
|
||||
(unix::SignalKind::terminate(), SignalKind::Term),
|
||||
(unix::SignalKind::quit(), SignalKind::Quit),
|
||||
];
|
||||
|
||||
let signals = sig_map
|
||||
@ -69,8 +69,8 @@ impl Signals {
|
||||
unix::signal(*kind)
|
||||
.map(|tokio_sig| (*sig, tokio_sig))
|
||||
.map_err(|e| {
|
||||
log::error!(
|
||||
"Can not initialize stream handler for {:?} err: {}",
|
||||
tracing::error!(
|
||||
"can not initialize stream handler for {:?} err: {}",
|
||||
sig,
|
||||
e
|
||||
)
|
||||
@ -85,19 +85,18 @@ impl Signals {
|
||||
}
|
||||
|
||||
impl Future for Signals {
|
||||
type Output = Signal;
|
||||
type Output = SignalKind;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
self.signals.as_mut().poll(cx).map(|_| Signal::Int)
|
||||
self.signals.as_mut().poll(cx).map(|_| SignalKind::Int)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
for (sig, fut) in self.signals.iter_mut() {
|
||||
// TODO: match on if let Some ?
|
||||
if Pin::new(fut).poll_recv(cx).is_ready() {
|
||||
if fut.poll_recv(cx).is_ready() {
|
||||
trace!("{} received", sig);
|
||||
return Poll::Ready(*sig);
|
||||
}
|
||||
|
@ -1,18 +1,17 @@
|
||||
pub(crate) use std::net::{
|
||||
SocketAddr as StdSocketAddr, TcpListener as StdTcpListener, ToSocketAddrs,
|
||||
};
|
||||
|
||||
pub(crate) use mio::net::{TcpListener as MioTcpListener, TcpSocket as MioTcpSocket};
|
||||
#[cfg(unix)]
|
||||
pub(crate) use {
|
||||
mio::net::UnixListener as MioUnixListener,
|
||||
std::os::unix::net::UnixListener as StdUnixListener,
|
||||
};
|
||||
|
||||
use std::{fmt, io};
|
||||
|
||||
use actix_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),
|
||||
@ -107,7 +106,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(all(unix))]
|
||||
#[cfg(unix)]
|
||||
MioListener::Uds(ref lst) => write!(f, "{:?}", lst),
|
||||
}
|
||||
}
|
||||
@ -127,7 +126,7 @@ pub(crate) enum SocketAddr {
|
||||
Unknown,
|
||||
Tcp(StdSocketAddr),
|
||||
#[cfg(unix)]
|
||||
Uds(mio::net::SocketAddr),
|
||||
Uds(std::os::unix::net::SocketAddr),
|
||||
}
|
||||
|
||||
impl fmt::Display for SocketAddr {
|
||||
@ -159,24 +158,25 @@ pub enum MioStream {
|
||||
Uds(mio::net::UnixStream),
|
||||
}
|
||||
|
||||
/// helper trait for converting mio stream to tokio stream.
|
||||
/// Helper trait for converting a Mio stream into a Tokio stream.
|
||||
pub trait FromStream: Sized {
|
||||
/// Creates stream from a `mio` stream.
|
||||
fn from_mio(sock: MioStream) -> io::Result<Self>;
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
mod win_impl {
|
||||
use super::*;
|
||||
|
||||
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
|
||||
use super::*;
|
||||
|
||||
// TODO: 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 a in place conversion from mio stream to tokio stream.
|
||||
// SAFETY: This is an in-place conversion from Mio stream to Tokio stream.
|
||||
TcpStream::from_std(unsafe { FromRawSocket::from_raw_socket(raw) })
|
||||
}
|
||||
}
|
||||
@ -186,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;
|
||||
|
||||
// FIXME: This is a workaround and we need an efficient way to convert between mio and tokio stream
|
||||
use super::*;
|
||||
|
||||
// HACK: 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 a in place conversion from mio stream to tokio stream.
|
||||
// SAFETY: This is an in-place conversion from Mio stream to Tokio stream.
|
||||
TcpStream::from_std(unsafe { FromRawFd::from_raw_fd(raw) })
|
||||
}
|
||||
MioStream::Uds(_) => {
|
||||
@ -208,14 +208,14 @@ mod unix_impl {
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: This is a workaround and we need an efficient way to convert between mio and tokio stream
|
||||
// HACK: 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 a in place conversion from mio stream to tokio stream.
|
||||
// SAFETY: This is an in-place conversion from Mio stream to Tokio stream.
|
||||
UnixStream::from_std(unsafe { FromRawFd::from_raw_fd(raw) })
|
||||
}
|
||||
}
|
||||
@ -223,6 +223,42 @@ 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),
|
||||
};
|
||||
|
||||
socket.set_reuse_address(true)?;
|
||||
socket.set_nonblocking(true)?;
|
||||
socket.bind(&addr.into())?;
|
||||
socket.listen(backlog as i32)?;
|
||||
|
||||
Ok(MioTcpListener::from_std(StdTcpListener::from(socket)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@ -234,11 +270,8 @@ mod tests {
|
||||
assert_eq!(format!("{}", addr), "127.0.0.1:8080");
|
||||
|
||||
let addr: StdSocketAddr = "127.0.0.1:0".parse().unwrap();
|
||||
let socket = MioTcpSocket::new_v4().unwrap();
|
||||
socket.set_reuseaddr(true).unwrap();
|
||||
socket.bind(addr).unwrap();
|
||||
let tcp = socket.listen(128).unwrap();
|
||||
let lst = MioListener::Tcp(tcp);
|
||||
let lst = create_mio_tcp_listener(addr, 128, &MpTcp::Disabled).unwrap();
|
||||
let lst = MioListener::Tcp(lst);
|
||||
assert!(format!("{:?}", lst).contains("TcpListener"));
|
||||
assert!(format!("{}", lst).contains("127.0.0.1"));
|
||||
}
|
||||
|
@ -1,9 +1,8 @@
|
||||
use std::sync::mpsc;
|
||||
use std::{io, net, thread};
|
||||
use std::{io, net, sync::mpsc, thread};
|
||||
|
||||
use actix_rt::{net::TcpStream, System};
|
||||
|
||||
use crate::{Server, ServerBuilder, ServerHandle, ServiceFactory};
|
||||
use crate::{Server, ServerBuilder, ServerHandle, ServerServiceFactory};
|
||||
|
||||
/// A testing server.
|
||||
///
|
||||
@ -17,7 +16,7 @@ use crate::{Server, ServerBuilder, ServerHandle, ServiceFactory};
|
||||
///
|
||||
/// #[actix_rt::main]
|
||||
/// async fn main() {
|
||||
/// let srv = TestServer::with(|| fn_service(
|
||||
/// let srv = TestServer::start(|| fn_service(
|
||||
/// |sock| async move {
|
||||
/// println!("New connection: {:?}", sock);
|
||||
/// Ok::<_, ()>(())
|
||||
@ -29,8 +28,8 @@ use crate::{Server, ServerBuilder, ServerHandle, ServiceFactory};
|
||||
/// ```
|
||||
pub struct TestServer;
|
||||
|
||||
/// Test server runtime
|
||||
pub struct TestServerRuntime {
|
||||
/// Test server handle.
|
||||
pub struct TestServerHandle {
|
||||
addr: net::SocketAddr,
|
||||
host: String,
|
||||
port: u16,
|
||||
@ -39,46 +38,26 @@ pub struct TestServerRuntime {
|
||||
}
|
||||
|
||||
impl TestServer {
|
||||
/// Start new server with server builder.
|
||||
pub fn start<F>(mut factory: F) -> TestServerRuntime
|
||||
where
|
||||
F: FnMut(ServerBuilder) -> ServerBuilder + Send + 'static,
|
||||
{
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
// run server in separate thread
|
||||
let thread_handle = thread::spawn(move || {
|
||||
System::new().block_on(async {
|
||||
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 `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 test server with application factory.
|
||||
pub fn with<F: ServiceFactory<TcpStream>>(factory: F) -> TestServerRuntime {
|
||||
/// Start new `TestServer` using application factory and server builder.
|
||||
pub fn start_with_builder(
|
||||
server_builder: ServerBuilder,
|
||||
factory: impl ServerServiceFactory<TcpStream>,
|
||||
) -> TestServerHandle {
|
||||
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();
|
||||
let lst = net::TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let local_addr = lst.local_addr().unwrap();
|
||||
|
||||
sys.block_on(async {
|
||||
let server = Server::build()
|
||||
.listen("test", tcp, factory)
|
||||
System::new().block_on(async {
|
||||
let server = server_builder
|
||||
.listen("test", lst, factory)
|
||||
.unwrap()
|
||||
.workers(1)
|
||||
.disable_signals()
|
||||
@ -94,7 +73,7 @@ impl TestServer {
|
||||
let host = format!("{}", addr.ip());
|
||||
let port = addr.port();
|
||||
|
||||
TestServerRuntime {
|
||||
TestServerHandle {
|
||||
addr,
|
||||
host,
|
||||
port,
|
||||
@ -105,16 +84,22 @@ 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 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()
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
impl TestServerRuntime {
|
||||
impl TestServerHandle {
|
||||
/// Test server host.
|
||||
pub fn host(&self) -> &str {
|
||||
&self.host
|
||||
@ -132,18 +117,39 @@ impl TestServerRuntime {
|
||||
|
||||
/// Stop server.
|
||||
fn stop(&mut self) {
|
||||
let _ = self.server_handle.stop(false);
|
||||
drop(self.server_handle.stop(false));
|
||||
self.thread_handle.take().unwrap().join().unwrap().unwrap();
|
||||
}
|
||||
|
||||
/// Connect to server, returning a Tokio `TcpStream`.
|
||||
pub fn connect(&self) -> std::io::Result<TcpStream> {
|
||||
TcpStream::from_std(net::TcpStream::connect(self.addr)?)
|
||||
pub fn connect(&self) -> io::Result<TcpStream> {
|
||||
let stream = net::TcpStream::connect(self.addr)?;
|
||||
stream.set_nonblocking(true)?;
|
||||
TcpStream::from_std(stream)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TestServerRuntime {
|
||||
impl Drop for TestServerHandle {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
use std::{
|
||||
future::Future,
|
||||
io, mem,
|
||||
num::NonZeroUsize,
|
||||
pin::Pin,
|
||||
rc::Rc,
|
||||
sync::{
|
||||
@ -17,14 +18,13 @@ 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},
|
||||
@ -202,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.
|
||||
rx: UnboundedReceiver<Conn>,
|
||||
rx2: UnboundedReceiver<Stop>,
|
||||
conn_rx: UnboundedReceiver<Conn>,
|
||||
stop_rx: UnboundedReceiver<Stop>,
|
||||
counter: WorkerCounter,
|
||||
services: Box<[WorkerService]>,
|
||||
factories: Box<[Box<dyn InternalServiceFactory>]>,
|
||||
@ -212,7 +212,7 @@ pub(crate) struct ServerWorker {
|
||||
}
|
||||
|
||||
struct WorkerService {
|
||||
factory: usize,
|
||||
factory_idx: usize,
|
||||
status: WorkerServiceStatus,
|
||||
service: BoxedServerService,
|
||||
}
|
||||
@ -234,6 +234,12 @@ 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 {
|
||||
@ -244,8 +250,11 @@ 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(), 1);
|
||||
let parallelism = std::thread::available_parallelism().map_or(2, NonZeroUsize::get);
|
||||
|
||||
// 512 is the default max blocking thread count of a Tokio runtime.
|
||||
let max_blocking_threads = std::cmp::max(512 / parallelism, 1);
|
||||
|
||||
Self {
|
||||
shutdown_timeout: Duration::from_secs(30),
|
||||
max_blocking_threads,
|
||||
@ -277,110 +286,201 @@ impl ServerWorker {
|
||||
) -> io::Result<(WorkerHandleAccept, WorkerHandleServer)> {
|
||||
trace!("starting server worker {}", idx);
|
||||
|
||||
let (tx1, rx) = unbounded_channel();
|
||||
let (tx2, rx2) = unbounded_channel();
|
||||
let (tx1, conn_rx) = unbounded_channel();
|
||||
let (tx2, stop_rx) = unbounded_channel();
|
||||
|
||||
let counter = Counter::new(config.max_concurrent_connections);
|
||||
|
||||
let counter_clone = counter.clone();
|
||||
// every worker runs in it's own arbiter.
|
||||
// use a custom tokio runtime builder to change the settings of runtime.
|
||||
#[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()
|
||||
};
|
||||
let pair = handle_pair(idx, tx1, tx2, counter.clone());
|
||||
|
||||
// get actix system context if it is set
|
||||
let sys = System::try_current();
|
||||
let actix_system = System::try_current();
|
||||
|
||||
// get tokio runtime handle if it is set
|
||||
let tokio_handle = tokio::runtime::Handle::try_current().ok();
|
||||
|
||||
// service factories initialization channel
|
||||
let (factory_tx, factory_rx) = std::sync::mpsc::sync_channel(1);
|
||||
let (factory_tx, factory_rx) = std::sync::mpsc::sync_channel::<io::Result<()>>(1);
|
||||
|
||||
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);
|
||||
}
|
||||
// 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 rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.max_blocking_threads(config.max_blocking_threads)
|
||||
.build()
|
||||
.unwrap();
|
||||
// every worker runs in it's own thread and tokio runtime.
|
||||
// use a custom tokio runtime builder to change the settings of runtime.
|
||||
|
||||
rt.block_on(tokio::task::LocalSet::new().run_until(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<_>>();
|
||||
match (actix_system, tokio_handle) {
|
||||
(None, None) => {
|
||||
panic!("No runtime detected. Start a Tokio (or Actix) runtime.");
|
||||
}
|
||||
|
||||
// a second spawn to run !Send future tasks.
|
||||
spawn(async move {
|
||||
let res = join_all_local(fut)
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<Result<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();
|
||||
|
||||
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(),
|
||||
// local set for running service init futures and worker services
|
||||
let ls = tokio::task::LocalSet::new();
|
||||
|
||||
Err(e) => {
|
||||
error!("Can not start worker: {:?}", e);
|
||||
Arbiter::try_current().as_ref().map(ArbiterHandle::stop);
|
||||
// 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();
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
factory_tx.send(()).unwrap();
|
||||
let worker_services = wrap_worker_services(services);
|
||||
|
||||
// a third spawn to make sure ServerWorker runs as non boxed future.
|
||||
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.
|
||||
spawn(ServerWorker {
|
||||
rx,
|
||||
rx2,
|
||||
services,
|
||||
counter: WorkerCounter::new(idx, waker_queue, counter_clone),
|
||||
conn_rx,
|
||||
stop_rx,
|
||||
services: worker_services.into_boxed_slice(),
|
||||
counter: WorkerCounter::new(idx, waker_queue, counter),
|
||||
factories: factories.into_boxed_slice(),
|
||||
state: Default::default(),
|
||||
shutdown_timeout: config.shutdown_timeout,
|
||||
})
|
||||
.await
|
||||
.expect("task 3 panic");
|
||||
})
|
||||
.await
|
||||
.expect("task 2 panic");
|
||||
}))
|
||||
})
|
||||
.expect("worker thread error/panic");
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// wait for service factories initialization
|
||||
factory_rx.recv().unwrap();
|
||||
factory_rx.recv().unwrap()?;
|
||||
|
||||
Ok(handle_pair(idx, tx1, tx2, counter))
|
||||
Ok(pair)
|
||||
}
|
||||
|
||||
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,
|
||||
@ -412,8 +512,8 @@ impl ServerWorker {
|
||||
Poll::Ready(Ok(_)) => {
|
||||
if srv.status == WorkerServiceStatus::Unavailable {
|
||||
trace!(
|
||||
"Service {:?} is available",
|
||||
self.factories[srv.factory].name(idx)
|
||||
"service {:?} is available",
|
||||
self.factories[srv.factory_idx].name(idx)
|
||||
);
|
||||
srv.status = WorkerServiceStatus::Available;
|
||||
}
|
||||
@ -423,19 +523,19 @@ impl ServerWorker {
|
||||
|
||||
if srv.status == WorkerServiceStatus::Available {
|
||||
trace!(
|
||||
"Service {:?} is unavailable",
|
||||
self.factories[srv.factory].name(idx)
|
||||
"service {:?} is unavailable",
|
||||
self.factories[srv.factory_idx].name(idx)
|
||||
);
|
||||
srv.status = WorkerServiceStatus::Unavailable;
|
||||
}
|
||||
}
|
||||
Poll::Ready(Err(_)) => {
|
||||
error!(
|
||||
"Service {:?} readiness check returned error, restarting",
|
||||
self.factories[srv.factory].name(idx)
|
||||
"service {:?} readiness check returned error, restarting",
|
||||
self.factories[srv.factory_idx].name(idx)
|
||||
);
|
||||
srv.status = WorkerServiceStatus::Failed;
|
||||
return Err((idx, srv.factory));
|
||||
return Err((idx, srv.factory_idx));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -478,7 +578,6 @@ impl Default for WorkerState {
|
||||
|
||||
impl Drop for ServerWorker {
|
||||
fn drop(&mut self) {
|
||||
trace!("stopping ServerWorker Arbiter");
|
||||
Arbiter::try_current().as_ref().map(ArbiterHandle::stop);
|
||||
}
|
||||
}
|
||||
@ -490,15 +589,14 @@ impl Future for ServerWorker {
|
||||
let this = self.as_mut().get_mut();
|
||||
|
||||
// `StopWorker` message handler
|
||||
if let Poll::Ready(Some(Stop { graceful, tx })) = Pin::new(&mut this.rx2).poll_recv(cx)
|
||||
{
|
||||
if let Poll::Ready(Some(Stop { graceful, tx })) = this.stop_rx.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 {
|
||||
@ -507,7 +605,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);
|
||||
@ -527,12 +625,13 @@ impl Future for ServerWorker {
|
||||
self.poll(cx)
|
||||
}
|
||||
},
|
||||
|
||||
WorkerState::Restarting(ref mut restart) => {
|
||||
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)
|
||||
@ -542,7 +641,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)
|
||||
);
|
||||
|
||||
@ -551,7 +650,16 @@ impl Future for ServerWorker {
|
||||
|
||||
self.poll(cx)
|
||||
}
|
||||
|
||||
WorkerState::Shutdown(ref mut shutdown) => {
|
||||
// drop all pending connections in rx channel.
|
||||
while let Poll::Ready(Some(conn)) = this.conn_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();
|
||||
drop((conn, guard));
|
||||
}
|
||||
|
||||
// wait for 1 second
|
||||
ready!(shutdown.timer.as_mut().poll(cx));
|
||||
|
||||
@ -576,12 +684,13 @@ impl Future for ServerWorker {
|
||||
shutdown.timer.as_mut().poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
// actively poll stream and handle worker command
|
||||
WorkerState::Available => loop {
|
||||
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);
|
||||
}
|
||||
@ -592,10 +701,13 @@ impl Future for ServerWorker {
|
||||
}
|
||||
|
||||
// handle incoming io stream
|
||||
match ready!(Pin::new(&mut this.rx).poll_recv(cx)) {
|
||||
match ready!(this.conn_rx.poll_recv(cx)) {
|
||||
Some(msg) => {
|
||||
let guard = this.counter.guard();
|
||||
let _ = this.services[msg.token].service.call((guard, msg.io));
|
||||
let _ = this.services[msg.token]
|
||||
.service
|
||||
.call((guard, msg.io))
|
||||
.into_inner();
|
||||
}
|
||||
None => return Poll::Ready(()),
|
||||
};
|
||||
@ -603,3 +715,17 @@ 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,18 +1,21 @@
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::{mpsc, Arc};
|
||||
use std::{net, thread, time::Duration};
|
||||
#![allow(clippy::let_underscore_future, missing_docs)]
|
||||
|
||||
use std::{
|
||||
net,
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
mpsc, Arc,
|
||||
},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use actix_rt::{net::TcpStream, time::sleep};
|
||||
use actix_server::Server;
|
||||
use actix_server::{Server, TestServer};
|
||||
use actix_service::fn_service;
|
||||
|
||||
fn unused_addr() -> net::SocketAddr {
|
||||
let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
|
||||
let socket = mio::net::TcpSocket::new_v4().unwrap();
|
||||
socket.bind(addr).unwrap();
|
||||
socket.set_reuseaddr(true).unwrap();
|
||||
let tcp = socket.listen(32).unwrap();
|
||||
tcp.local_addr().unwrap()
|
||||
TestServer::unused_addr()
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -25,23 +28,24 @@ fn test_bind() {
|
||||
let srv = Server::build()
|
||||
.workers(1)
|
||||
.disable_signals()
|
||||
.shutdown_timeout(3600)
|
||||
.bind("test", addr, move || {
|
||||
fn_service(|_| async { Ok::<_, ()>(()) })
|
||||
})?
|
||||
.run();
|
||||
|
||||
let _ = tx.send((srv.handle(), actix_rt::System::current()));
|
||||
|
||||
tx.send(srv.handle()).unwrap();
|
||||
srv.await
|
||||
})
|
||||
});
|
||||
let (srv, sys) = rx.recv().unwrap();
|
||||
|
||||
let srv = rx.recv().unwrap();
|
||||
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
assert!(net::TcpStream::connect(addr).is_ok());
|
||||
|
||||
net::TcpStream::connect(addr).unwrap();
|
||||
|
||||
let _ = srv.stop(true);
|
||||
sys.stop();
|
||||
h.join().unwrap().unwrap();
|
||||
}
|
||||
|
||||
@ -49,31 +53,66 @@ 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()
|
||||
.disable_signals()
|
||||
.workers(1)
|
||||
.disable_signals()
|
||||
.shutdown_timeout(3600)
|
||||
.listen("test", lst, move || {
|
||||
fn_service(|_| async { Ok::<_, ()>(()) })
|
||||
})?
|
||||
.run();
|
||||
|
||||
let _ = tx.send((srv.handle(), actix_rt::System::current()));
|
||||
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();
|
||||
|
||||
srv.await
|
||||
})
|
||||
});
|
||||
|
||||
let (srv, sys) = rx.recv().unwrap();
|
||||
let srv = 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();
|
||||
}
|
||||
|
||||
@ -149,9 +188,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;
|
||||
@ -217,6 +256,7 @@ 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};
|
||||
@ -280,12 +320,12 @@ async fn test_service_restart() {
|
||||
.workers(1)
|
||||
.run();
|
||||
|
||||
let _ = tx.send((srv.handle(), actix_rt::System::current()));
|
||||
let _ = tx.send(srv.handle());
|
||||
srv.await
|
||||
})
|
||||
});
|
||||
|
||||
let (srv, sys) = rx.recv().unwrap();
|
||||
let srv = rx.recv().unwrap();
|
||||
|
||||
for _ in 0..5 {
|
||||
TcpStream::connect(addr1)
|
||||
@ -308,7 +348,6 @@ async fn test_service_restart() {
|
||||
assert!(num2_clone.load(Ordering::SeqCst) > 5);
|
||||
|
||||
let _ = srv.stop(false);
|
||||
sys.stop();
|
||||
h.join().unwrap().unwrap();
|
||||
}
|
||||
|
||||
@ -385,13 +424,13 @@ async fn worker_restart() {
|
||||
.workers(2)
|
||||
.run();
|
||||
|
||||
let _ = tx.send((srv.handle(), actix_rt::System::current()));
|
||||
let _ = tx.send(srv.handle());
|
||||
|
||||
srv.await
|
||||
})
|
||||
});
|
||||
|
||||
let (srv, sys) = rx.recv().unwrap();
|
||||
let srv = rx.recv().unwrap();
|
||||
|
||||
sleep(Duration::from_secs(3)).await;
|
||||
|
||||
@ -449,6 +488,50 @@ 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();
|
||||
}
|
77
actix-server/tests/testing_server.rs
Normal file
77
actix-server/tests/testing_server.rs
Normal file
@ -0,0 +1,77 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
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
|
||||
let stream = net::TcpStream::connect(alt_addr).unwrap();
|
||||
stream.set_nonblocking(true).unwrap();
|
||||
TcpStream::from_std(stream).unwrap();
|
||||
}
|
@ -1,284 +1,193 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
## Unreleased
|
||||
|
||||
## 2.0.3
|
||||
|
||||
## 2.0.1 - 2021-10-11
|
||||
* Documentation fix.
|
||||
- Minimum supported Rust version (MSRV) is now 1.71.
|
||||
|
||||
## 2.0.2
|
||||
|
||||
## 2.0.0 - 2021-04-16
|
||||
* Removed pipeline and related structs/functions. [#335]
|
||||
- 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
|
||||
|
||||
- Documentation fix. [#388]
|
||||
|
||||
[#388]: https://github.com/actix/actix-net/pull/388
|
||||
|
||||
## 2.0.0
|
||||
|
||||
- Removed pipeline and related structs/functions. [#335]
|
||||
|
||||
[#335]: https://github.com/actix/actix-net/pull/335
|
||||
|
||||
## 2.0.0-beta.5
|
||||
|
||||
## 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]
|
||||
- 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
|
||||
|
||||
## 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]
|
||||
- `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
|
||||
|
||||
## 2.0.0-beta.3 - 2021-01-09
|
||||
* The `forward_ready!` macro converts errors. [#246]
|
||||
- The `forward_ready!` macro converts errors. [#246]
|
||||
|
||||
[#246]: https://github.com/actix/actix-net/pull/246
|
||||
|
||||
## 2.0.0-beta.2
|
||||
|
||||
## 2.0.0-beta.2 - 2021-01-03
|
||||
* Remove redundant type parameter from `map_config`.
|
||||
- Remove redundant type parameter from `map_config`.
|
||||
|
||||
## 2.0.0-beta.1
|
||||
|
||||
## 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
|
||||
|
||||
## 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
|
||||
|
||||
* 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
|
||||
|
||||
### Fixed
|
||||
- Revert 1.0.3 change
|
||||
|
||||
* Fixed unsoundness in .and_then()/.then() service combinators
|
||||
## 1.0.3
|
||||
|
||||
## [1.0.4] - 2020-01-15
|
||||
- Fixed unsoundness in `AndThenService` impl.
|
||||
|
||||
### Fixed
|
||||
## 1.0.2
|
||||
|
||||
* Revert 1.0.3 change
|
||||
- Add `into_service` helper function.
|
||||
|
||||
## [1.0.3] - 2020-01-15
|
||||
## 1.0.1
|
||||
|
||||
### Fixed
|
||||
- `map_config()` and `unit_config()` now accept `IntoServiceFactory` type.
|
||||
|
||||
* Fixed unsoundness in `AndThenService` impl
|
||||
## 1.0.0
|
||||
|
||||
## [1.0.2] - 2020-01-08
|
||||
- Add Clone impl for Apply service
|
||||
|
||||
### Added
|
||||
## 1.0.0-alpha.4
|
||||
|
||||
* 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
|
||||
|
||||
## [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
|
||||
|
||||
* `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
|
||||
|
||||
## [1.0.0] - 2019-12-11
|
||||
- Migrated to `std::future`
|
||||
- `NewService` renamed to `ServiceFactory`
|
||||
- Added `pipeline` and `pipeline_factory` function
|
||||
|
||||
### Added
|
||||
## 0.4.2
|
||||
|
||||
* Add Clone impl for Apply service
|
||||
- Check service readiness for `new_apply_cfg` combinator
|
||||
|
||||
## 0.4.1
|
||||
|
||||
## [1.0.0-alpha.4] - 2019-12-08
|
||||
- Add `new_apply_cfg` function
|
||||
|
||||
### Changed
|
||||
## 0.4.0
|
||||
|
||||
* 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
|
||||
|
||||
* Renamed `factory_fn_cfg` to `fn_factory_with_config`
|
||||
- Poll boxed service call result immediately
|
||||
|
||||
## 0.3.5
|
||||
|
||||
## [1.0.0-alpha.3] - 2019-12-06
|
||||
- Add `impl<S: Service> Service for Rc<RefCell<S>>`.
|
||||
|
||||
### Changed
|
||||
## 0.3.4
|
||||
|
||||
* 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
|
||||
|
||||
* 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
|
||||
|
||||
- Change `NewService::Future` and `Transform::Future` to the `IntoFuture` trait.
|
||||
- Export `AndThenTransform` type
|
||||
|
||||
## [1.0.0-alpha.2] - 2019-12-02
|
||||
## 0.3.1
|
||||
|
||||
### Changed
|
||||
- Simplify Transform trait
|
||||
|
||||
* Use owned config value for service factory
|
||||
## 0.3.0
|
||||
|
||||
* 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
|
||||
|
||||
## [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
|
||||
|
||||
* Migraded to `std::future`
|
||||
- Generalize `.apply` combinator with Transform trait
|
||||
|
||||
* `NewService` renamed to `ServiceFactory`
|
||||
|
||||
* Added `pipeline` and `pipeline_factory` function
|
||||
|
||||
|
||||
## [0.4.2] - 2019-08-27
|
||||
|
||||
### Fixed
|
||||
|
||||
* Check service readiness for `new_apply_cfg` combinator
|
||||
|
||||
|
||||
## [0.4.1] - 2019-06-06
|
||||
|
||||
### Added
|
||||
|
||||
* Add `new_apply_cfg` function
|
||||
|
||||
## [0.4.0] - 2019-05-12
|
||||
|
||||
### Changed
|
||||
|
||||
* Use associated type for `NewService` config
|
||||
|
||||
* Change `apply_cfg` function
|
||||
|
||||
* Renamed helper functions
|
||||
|
||||
### Added
|
||||
|
||||
* Add `NewService::map_config` and `NewService::unit_config` combinators
|
||||
|
||||
|
||||
## [0.3.6] - 2019-04-07
|
||||
|
||||
### Changed
|
||||
|
||||
* Poll boxed service call result immediately
|
||||
|
||||
|
||||
## [0.3.5] - 2019-03-29
|
||||
|
||||
### Added
|
||||
|
||||
* Add `impl<S: Service> Service for Rc<RefCell<S>>`
|
||||
|
||||
|
||||
## [0.3.4] - 2019-03-12
|
||||
|
||||
### Added
|
||||
|
||||
* Add `Transform::from_err()` combinator
|
||||
|
||||
* Add `apply_fn` helper
|
||||
|
||||
* Add `apply_fn_factory` helper
|
||||
|
||||
* Add `apply_transform` helper
|
||||
|
||||
* Add `apply_cfg` helper
|
||||
|
||||
|
||||
## [0.3.3] - 2019-03-09
|
||||
|
||||
### Added
|
||||
|
||||
* Add `ApplyTransform` new service for transform and new service.
|
||||
|
||||
* Add `NewService::apply_cfg()` combinator, allows to use
|
||||
nested `NewService` with different config parameter.
|
||||
|
||||
### Changed
|
||||
|
||||
* Revert IntoFuture change
|
||||
|
||||
|
||||
## [0.3.2] - 2019-03-04
|
||||
|
||||
### Changed
|
||||
|
||||
* Change `NewService::Future` and `Transform::Future` to the `IntoFuture` trait.
|
||||
|
||||
* Export `AndThenTransform` type
|
||||
|
||||
|
||||
## [0.3.1] - 2019-03-04
|
||||
|
||||
### Changed
|
||||
|
||||
* Simplify Transform trait
|
||||
|
||||
|
||||
## [0.3.0] - 2019-03-02
|
||||
|
||||
## Added
|
||||
|
||||
* Added boxed NewService and Service.
|
||||
|
||||
## Changed
|
||||
|
||||
* Added `Config` parameter to `NewService` trait.
|
||||
|
||||
* Added `Config` parameter to `NewTransform` trait.
|
||||
|
||||
|
||||
## [0.2.2] - 2019-02-19
|
||||
|
||||
### Added
|
||||
|
||||
* Added `NewService` impl for `Rc<S> where S: NewService`
|
||||
|
||||
* Added `NewService` impl for `Arc<S> where S: NewService`
|
||||
|
||||
|
||||
## [0.2.1] - 2019-02-03
|
||||
|
||||
### Changed
|
||||
|
||||
* Generalize `.apply` combinator with Transform trait
|
||||
|
||||
|
||||
## [0.2.0] - 2019-02-01
|
||||
|
||||
### Changed
|
||||
|
||||
* Use associated type instead of generic for Service definition.
|
||||
|
||||
* Before:
|
||||
## 0.2.0
|
||||
|
||||
- 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;
|
||||
@ -287,51 +196,31 @@
|
||||
}
|
||||
```
|
||||
|
||||
## 0.1.6
|
||||
|
||||
## [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>`
|
||||
|
||||
### Changed
|
||||
## 0.1.5
|
||||
|
||||
* Use `FnMut` instead of `Fn` for .apply() and .map() combinators and `FnService` type
|
||||
- Make `Out::Error` convertible from `T::Error` for apply combinator
|
||||
|
||||
* Change `.apply()` error semantic, new service's error is `From<Self::Error>`
|
||||
## 0.1.4
|
||||
|
||||
- Use `FnMut` instead of `Fn` for `FnService`
|
||||
|
||||
## [0.1.5] - 2019-01-13
|
||||
## 0.1.3
|
||||
|
||||
### Changed
|
||||
- Split service combinators to separate trait
|
||||
|
||||
* Make `Out::Error` convertable from `T::Error` for apply combinator
|
||||
## 0.1.2
|
||||
|
||||
- Release future early for `.and_then()` and `.then()` combinators
|
||||
|
||||
## [0.1.4] - 2019-01-11
|
||||
## 0.1.1
|
||||
|
||||
### Changed
|
||||
- Added Service impl for `Box<S: Service>`
|
||||
|
||||
* Use `FnMut` instead of `Fn` for `FnService`
|
||||
## 0.1.0
|
||||
|
||||
|
||||
## [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
|
||||
- Initial import
|
||||
|
@ -1,28 +1,23 @@
|
||||
[package]
|
||||
name = "actix-service"
|
||||
version = "2.0.1"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
"fakeshadow <24548779@qq.com>",
|
||||
]
|
||||
version = "2.0.3"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>", "Rob Ede <robjtede@icloud.com>"]
|
||||
description = "Service trait and combinators for representing asynchronous request/response operations."
|
||||
keywords = ["network", "framework", "async", "futures", "service"]
|
||||
categories = ["network-programming", "asynchronous", "no-std"]
|
||||
repository = "https://github.com/actix/actix-net"
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
name = "actix_service"
|
||||
path = "src/lib.rs"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
futures-core = { version = "0.3.7", default-features = false }
|
||||
paste = "1"
|
||||
futures-core = { version = "0.3.17", default-features = false }
|
||||
pin-project-lite = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "2.0.0"
|
||||
actix-utils = "3.0.0"
|
||||
futures-util = { version = "0.3.7", default-features = false }
|
||||
actix-rt = "2"
|
||||
actix-utils = "3"
|
||||
futures-util = { version = "0.3.17", default-features = false }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
@ -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.1)
|
||||
[](https://docs.rs/actix-service/2.0.3)
|
||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
||||

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

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

|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use std::{future::Future, sync::mpsc, time::Duration};
|
||||
|
||||
async fn oracle<F, Fut>(f: F) -> (u32, u32)
|
||||
|
@ -121,12 +121,7 @@ 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>,
|
||||
@ -136,12 +131,7 @@ 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 {
|
||||
@ -156,12 +146,7 @@ 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;
|
||||
@ -184,12 +169,7 @@ 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 {
|
||||
@ -334,9 +314,8 @@ 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<(Req, In, Res, Err)>,
|
||||
_phantom: PhantomData<fn(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<(Req, In, Res, Err)>,
|
||||
_phantom: PhantomData<fn(Req) -> (In, Res, Err)>,
|
||||
}
|
||||
|
||||
impl<SF, F, Fut, Req, In, Res, Err> ApplyFactory<SF, F, Req, In, Res, Err>
|
||||
@ -140,8 +140,7 @@ 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,
|
||||
@ -171,7 +170,7 @@ pin_project! {
|
||||
#[pin]
|
||||
fut: SF::Future,
|
||||
wrap_fn: Option<F>,
|
||||
_phantom: PhantomData<(Req, Res)>,
|
||||
_phantom: PhantomData<fn(Req) -> Res>,
|
||||
}
|
||||
}
|
||||
|
||||
@ -209,15 +208,13 @@ where
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use core::task::Poll;
|
||||
|
||||
use futures_util::future::lazy;
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
ok,
|
||||
pipeline::{pipeline, pipeline_factory},
|
||||
Ready, Service, ServiceFactory,
|
||||
Ready,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -198,8 +198,7 @@ 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>,
|
||||
|
@ -3,36 +3,38 @@
|
||||
use alloc::{boxed::Box, rc::Rc};
|
||||
use core::{future::Future, pin::Pin};
|
||||
|
||||
use paste::paste;
|
||||
|
||||
use crate::{Service, ServiceFactory};
|
||||
|
||||
/// A boxed future with no send bound or lifetime parameters.
|
||||
pub type BoxFuture<T> = Pin<Box<dyn Future<Output = T>>>;
|
||||
|
||||
macro_rules! service_object {
|
||||
($name: ident, $type: tt, $fn_name: ident) => {
|
||||
paste! {
|
||||
#[doc = "Type alias for service trait object using `" $type "`."]
|
||||
pub type $name<Req, Res, Err> = $type<
|
||||
dyn Service<Req, Response = Res, Error = Err, Future = BoxFuture<Result<Res, Err>>>,
|
||||
>;
|
||||
/// Type alias for service trait object using [`Box`].
|
||||
pub type BoxService<Req, Res, Err> =
|
||||
Box<dyn Service<Req, Response = Res, Error = Err, Future = BoxFuture<Result<Res, Err>>>>;
|
||||
|
||||
#[doc = "Wraps service as a trait object using [`" $name "`]."]
|
||||
pub fn $fn_name<S, Req>(service: S) -> $name<Req, S::Response, S::Error>
|
||||
where
|
||||
S: Service<Req> + 'static,
|
||||
Req: 'static,
|
||||
S::Future: 'static,
|
||||
{
|
||||
$type::new(ServiceWrapper::new(service))
|
||||
}
|
||||
}
|
||||
};
|
||||
/// Wraps service as a trait object using [`BoxService`].
|
||||
pub fn service<S, Req>(service: S) -> BoxService<Req, S::Response, S::Error>
|
||||
where
|
||||
S: Service<Req> + 'static,
|
||||
Req: 'static,
|
||||
S::Future: 'static,
|
||||
{
|
||||
Box::new(ServiceWrapper::new(service))
|
||||
}
|
||||
|
||||
service_object!(BoxService, Box, service);
|
||||
service_object!(RcService, Rc, rc_service);
|
||||
/// Type alias for service trait object using [`Rc`].
|
||||
pub type RcService<Req, Res, Err> =
|
||||
Rc<dyn Service<Req, Response = Res, Error = Err, Future = BoxFuture<Result<Res, Err>>>>;
|
||||
|
||||
/// Wraps service as a trait object using [`RcService`].
|
||||
pub fn rc_service<S, Req>(service: S) -> RcService<Req, S::Response, S::Error>
|
||||
where
|
||||
S: Service<Req> + 'static,
|
||||
Req: 'static,
|
||||
S::Future: 'static,
|
||||
{
|
||||
Rc::new(ServiceWrapper::new(service))
|
||||
}
|
||||
|
||||
struct ServiceWrapper<S> {
|
||||
inner: S,
|
||||
@ -91,8 +93,7 @@ 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,
|
||||
|
@ -44,7 +44,7 @@ pub trait ServiceExt<Req>: Service<Req> {
|
||||
/// Call another service after call to this one has resolved successfully.
|
||||
///
|
||||
/// This function can be used to chain two services together and ensure that the second service
|
||||
/// isn't called until call to the fist service have finished. Result of the call to the first
|
||||
/// isn't called until call to the first service have finished. Result of the call to the first
|
||||
/// service is used as an input parameter for the second service's call.
|
||||
///
|
||||
/// Note that this function consumes the receiving service and returns a wrapped version of it.
|
||||
|
@ -3,9 +3,7 @@ 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>>,
|
||||
@ -48,9 +46,7 @@ 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>>,
|
||||
@ -105,7 +101,7 @@ where
|
||||
Fut: Future<Output = Result<Res, Err>>,
|
||||
{
|
||||
f: F,
|
||||
_t: PhantomData<Req>,
|
||||
_t: PhantomData<fn(Req)>,
|
||||
}
|
||||
|
||||
impl<F, Fut, Req, Res, Err> FnService<F, Fut, Req, Res, Err>
|
||||
@ -160,7 +156,7 @@ where
|
||||
Fut: Future<Output = Result<Res, Err>>,
|
||||
{
|
||||
f: F,
|
||||
_t: PhantomData<(Req, Cfg)>,
|
||||
_t: PhantomData<fn(Req, Cfg)>,
|
||||
}
|
||||
|
||||
impl<F, Fut, Req, Res, Err, Cfg> FnServiceFactory<F, Fut, Req, Res, Err, Cfg>
|
||||
@ -237,7 +233,7 @@ where
|
||||
Srv: Service<Req>,
|
||||
{
|
||||
f: F,
|
||||
_t: PhantomData<(Fut, Cfg, Req, Srv, Err)>,
|
||||
_t: PhantomData<fn(Cfg, Req) -> (Fut, Srv, Err)>,
|
||||
}
|
||||
|
||||
impl<F, Fut, Cfg, Srv, Req, Err> FnServiceConfig<F, Fut, Cfg, Srv, Req, Err>
|
||||
@ -265,8 +261,7 @@ 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>>,
|
||||
@ -293,7 +288,7 @@ where
|
||||
Fut: Future<Output = Result<Srv, Err>>,
|
||||
{
|
||||
f: F,
|
||||
_t: PhantomData<(Cfg, Req)>,
|
||||
_t: PhantomData<fn(Cfg, Req)>,
|
||||
}
|
||||
|
||||
impl<F, Cfg, Srv, Req, Fut, Err> FnServiceNoConfig<F, Cfg, Srv, Req, Fut, Err>
|
||||
@ -356,7 +351,6 @@ mod tests {
|
||||
use futures_util::future::lazy;
|
||||
|
||||
use super::*;
|
||||
use crate::{ok, Service, ServiceFactory};
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_fn_service() {
|
||||
@ -391,4 +385,40 @@ 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,6 @@
|
||||
//! See [`Service`] docs for information on this crate's foundational trait.
|
||||
|
||||
#![no_std]
|
||||
#![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,15 +31,16 @@ 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 serivice types, use [`forward_ready!`] for simple cases or write a bespoke
|
||||
/// For wrapper or other service types, use [`forward_ready!`] for simple cases or write a bespoke
|
||||
/// `poll_ready` implementation.
|
||||
///
|
||||
/// [`poll_ready`]: crate::Service::poll_ready
|
||||
@ -25,6 +25,8 @@
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`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<(Req, Res)>,
|
||||
_t: PhantomData<fn(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(e)) => Poll::Ready(Err(e)),
|
||||
Poll::Ready(Err(err)) => Poll::Ready(Err(err)),
|
||||
Poll::Pending => Poll::Pending,
|
||||
}
|
||||
}
|
||||
@ -107,7 +107,7 @@ where
|
||||
pub struct MapServiceFactory<A, F, Req, Res> {
|
||||
a: A,
|
||||
f: F,
|
||||
r: PhantomData<(Res, Req)>,
|
||||
r: PhantomData<fn(Req) -> Res>,
|
||||
}
|
||||
|
||||
impl<A, F, Req, Res> MapServiceFactory<A, F, Req, Res> {
|
||||
@ -202,9 +202,7 @@ mod tests {
|
||||
use futures_util::future::lazy;
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
ok, IntoServiceFactory, Ready, Service, ServiceExt, ServiceFactory, ServiceFactoryExt,
|
||||
};
|
||||
use crate::{ok, IntoServiceFactory, Ready, ServiceExt, ServiceFactoryExt};
|
||||
|
||||
struct Srv;
|
||||
|
||||
|
@ -28,7 +28,7 @@ where
|
||||
pub struct MapConfig<SF, Req, F, Cfg> {
|
||||
factory: SF,
|
||||
cfg_mapper: F,
|
||||
e: PhantomData<(Cfg, Req)>,
|
||||
e: PhantomData<fn(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<(Cfg, Req)>,
|
||||
_phantom: PhantomData<fn(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<(E, Req)>,
|
||||
_t: PhantomData<fn(Req) -> E>,
|
||||
}
|
||||
|
||||
impl<S, Req, F, E> MapErr<S, Req, F, E> {
|
||||
@ -111,7 +111,7 @@ where
|
||||
{
|
||||
a: SF,
|
||||
f: F,
|
||||
e: PhantomData<(E, Req)>,
|
||||
e: PhantomData<fn(Req) -> E>,
|
||||
}
|
||||
|
||||
impl<SF, Req, F, E> MapErrServiceFactory<SF, Req, F, E>
|
||||
@ -205,10 +205,7 @@ mod tests {
|
||||
use futures_util::future::lazy;
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
err, ok, IntoServiceFactory, Ready, Service, ServiceExt, ServiceFactory,
|
||||
ServiceFactoryExt,
|
||||
};
|
||||
use crate::{err, ok, IntoServiceFactory, Ready, ServiceExt, ServiceFactoryExt};
|
||||
|
||||
struct Srv;
|
||||
|
||||
|
@ -13,7 +13,7 @@ use super::ServiceFactory;
|
||||
pub struct MapInitErr<A, F, Req, Err> {
|
||||
a: A,
|
||||
f: F,
|
||||
e: PhantomData<(Req, Err)>,
|
||||
e: PhantomData<fn(Req) -> Err>,
|
||||
}
|
||||
|
||||
impl<A, F, Req, Err> MapInitErr<A, F, Req, Err>
|
||||
|
@ -6,12 +6,14 @@ use core::{
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use crate::and_then::{AndThenService, AndThenServiceFactory};
|
||||
use crate::map::{Map, MapServiceFactory};
|
||||
use crate::map_err::{MapErr, MapErrServiceFactory};
|
||||
use crate::map_init_err::MapInitErr;
|
||||
use crate::then::{ThenService, ThenServiceFactory};
|
||||
use crate::{IntoService, IntoServiceFactory, Service, ServiceFactory};
|
||||
use crate::{
|
||||
and_then::{AndThenService, AndThenServiceFactory},
|
||||
map::{Map, MapServiceFactory},
|
||||
map_err::{MapErr, MapErrServiceFactory},
|
||||
map_init_err::MapInitErr,
|
||||
then::{ThenService, ThenServiceFactory},
|
||||
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>
|
||||
@ -40,7 +42,7 @@ where
|
||||
/// Pipeline service - pipeline allows to compose multiple service into one service.
|
||||
pub(crate) struct Pipeline<S, Req> {
|
||||
service: S,
|
||||
_phantom: PhantomData<Req>,
|
||||
_phantom: PhantomData<fn(Req)>,
|
||||
}
|
||||
|
||||
impl<S, Req> Pipeline<S, Req>
|
||||
@ -50,7 +52,7 @@ where
|
||||
/// Call another service after call to this one has resolved successfully.
|
||||
///
|
||||
/// This function can be used to chain two services together and ensure that
|
||||
/// the second service isn't called until call to the fist service have
|
||||
/// the second service isn't called until call to the first service have
|
||||
/// finished. Result of the call to the first service is used as an
|
||||
/// input parameter for the second service's call.
|
||||
///
|
||||
@ -162,7 +164,7 @@ impl<S: Service<Req>, Req> Service<Req> for Pipeline<S, Req> {
|
||||
/// Pipeline factory
|
||||
pub(crate) struct PipelineFactory<SF, Req> {
|
||||
factory: SF,
|
||||
_phantom: PhantomData<Req>,
|
||||
_phantom: PhantomData<fn(Req)>,
|
||||
}
|
||||
|
||||
impl<SF, Req> PipelineFactory<SF, Req>
|
||||
@ -252,10 +254,7 @@ 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,
|
||||
|
@ -226,9 +226,9 @@ mod tests {
|
||||
use actix_utils::future::{ready, Ready};
|
||||
|
||||
use super::*;
|
||||
use crate::Service;
|
||||
|
||||
// pseudo-doctest for Transform trait
|
||||
#[allow(unused)]
|
||||
pub struct TimeoutTransform {
|
||||
timeout: Duration,
|
||||
}
|
||||
@ -250,6 +250,7 @@ mod tests {
|
||||
}
|
||||
|
||||
// pseudo-doctest for Transform trait
|
||||
#[allow(unused)]
|
||||
pub struct Timeout<S> {
|
||||
service: S,
|
||||
_timeout: Duration,
|
||||
|
@ -14,7 +14,7 @@ use super::Transform;
|
||||
pub struct TransformMapInitErr<T, S, Req, F, E> {
|
||||
transform: T,
|
||||
mapper: F,
|
||||
_phantom: PhantomData<(S, Req, E)>,
|
||||
_phantom: PhantomData<fn(Req) -> (S, E)>,
|
||||
}
|
||||
|
||||
impl<T, S, F, E, Req> TransformMapInitErr<T, S, Req, F, E> {
|
||||
|
@ -1,106 +1,215 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
## Unreleased
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.71.
|
||||
|
||||
## 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]
|
||||
## 3.4.0
|
||||
|
||||
- Add `rustls-0_23`, `rustls-0_23-webpki-roots`, and `rustls-0_23-native-roots` crate features.
|
||||
- Minimum supported Rust version (MSRV) is now 1.70.
|
||||
|
||||
## 3.3.0
|
||||
|
||||
- Add `rustls-0_22` crate feature which excludes any root certificate methods or re-exports.
|
||||
|
||||
## 3.2.0
|
||||
|
||||
- Support Rustls v0.22.
|
||||
- Add `{accept, connect}::rustls_0_22` modules.
|
||||
- Add `rustls-0_21-native-roots` and `rustls-0_20-native-roots` crate features which utilize the `rustls-native-certs` crate to enable a `native_roots_cert_store()` functions in each rustls-based `connect` module.
|
||||
- Implement `Host` for `http::Uri` (`http` crate version `1`).
|
||||
|
||||
## 3.1.1
|
||||
|
||||
- Fix `rustls` v0.21 version requirement.
|
||||
|
||||
## 3.1.0
|
||||
|
||||
- Support Rustls v0.21.
|
||||
- Add `{accept, connect}::rustls_0_21` modules.
|
||||
- Add `{accept, connect}::rustls_0_20` alias for `{accept, connect}::rustls` modules.
|
||||
- Minimum supported Rust version (MSRV) is now 1.65.
|
||||
|
||||
## 3.0.4
|
||||
|
||||
- Logs emitted now use the `tracing` crate with `log` compatibility. [#451]
|
||||
|
||||
[#451]: https://github.com/actix/actix-net/pull/451
|
||||
|
||||
## 3.0.3
|
||||
|
||||
- No significant changes since `3.0.2`.
|
||||
|
||||
## 3.0.2
|
||||
|
||||
- Expose `connect::Connection::new`. [#439]
|
||||
|
||||
[#439]: https://github.com/actix/actix-net/pull/439
|
||||
|
||||
## 3.0.1
|
||||
|
||||
- No significant changes since `3.0.0`.
|
||||
|
||||
## 3.0.0
|
||||
|
||||
- No significant changes since `3.0.0-rc.2`.
|
||||
|
||||
## 3.0.0-rc.2
|
||||
|
||||
- Re-export `openssl::SslConnectorBuilder` in `connect::openssl::reexports`. [#429]
|
||||
|
||||
[#429]: https://github.com/actix/actix-net/pull/429
|
||||
|
||||
## 3.0.0-rc.1
|
||||
|
||||
### 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
|
||||
|
||||
- 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
|
||||
|
||||
- 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
|
||||
|
||||
- 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
|
||||
|
||||
## 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.
|
||||
- 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
|
||||
|
||||
## 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]
|
||||
- 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]
|
||||
|
||||
[#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
|
||||
|
||||
## 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]
|
||||
- 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
|
||||
|
||||
## 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]
|
||||
- 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
|
||||
|
||||
## 3.0.0-beta.2 - 2021-xx-xx
|
||||
* Depend on stable trust-dns packages. [#204]
|
||||
- Depend on stable trust-dns packages. [#204]
|
||||
|
||||
[#204]: https://github.com/actix/actix-net/pull/204
|
||||
|
||||
## 3.0.0-beta.1
|
||||
|
||||
## 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]
|
||||
- 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
|
||||
|
||||
## 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`.
|
||||
- `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
|
||||
|
||||
## 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]
|
||||
|
||||
## [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]
|
||||
|
||||
## [1.0.0] - 2019-12-11
|
||||
* 1.0.0 release
|
||||
- 1.0.0 release
|
||||
|
||||
## [1.0.0-alpha.3]
|
||||
|
||||
## [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]
|
||||
|
||||
## [1.0.0-alpha.1] - 2019-12-02
|
||||
* Split openssl acceptor from actix-server package
|
||||
- Split openssl acceptor from actix-server package
|
||||
|
134
actix-tls/Cargo.toml
Executable file → Normal file
134
actix-tls/Cargo.toml
Executable file → Normal file
@ -1,23 +1,30 @@
|
||||
[package]
|
||||
name = "actix-tls"
|
||||
version = "3.0.0-beta.7"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
version = "3.4.0"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>", "Rob Ede <robjtede@icloud.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"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
categories = ["network-programming", "asynchronous", "cryptography"]
|
||||
license.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["openssl", "rustls", "native-tls", "accept", "connect", "uri"]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[lib]
|
||||
name = "actix_tls"
|
||||
path = "src/lib.rs"
|
||||
[package.metadata.cargo_check_external_types]
|
||||
allowed_external_types = ["actix_service::*", "actix_utils::*", "futures_core::*", "tokio::*"]
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = [
|
||||
"rustls_021", # specified to force version with add_trust_anchors method
|
||||
"rustls_webpki_0101", # specified to force secure version
|
||||
]
|
||||
|
||||
[features]
|
||||
default = ["accept", "connect", "uri"]
|
||||
default = ["accept", "connect"]
|
||||
|
||||
# enable acceptor services
|
||||
accept = []
|
||||
@ -26,50 +33,103 @@ accept = []
|
||||
connect = []
|
||||
|
||||
# use openssl impls
|
||||
openssl = ["tls-openssl", "tokio-openssl"]
|
||||
openssl = ["dep:tls-openssl", "dep:tokio-openssl"]
|
||||
|
||||
# use rustls impls
|
||||
rustls = ["tokio-rustls", "webpki-roots"]
|
||||
# alias for backwards compat
|
||||
rustls = ["rustls-0_20"]
|
||||
|
||||
# use rustls v0.20 impls
|
||||
rustls-0_20 = ["rustls-0_20-webpki-roots"]
|
||||
rustls-0_20-webpki-roots = ["tokio-rustls-023", "webpki-roots-022"]
|
||||
rustls-0_20-native-roots = ["tokio-rustls-023", "dep:rustls-native-certs-06"]
|
||||
|
||||
# use rustls v0.21 impls
|
||||
rustls-0_21 = ["rustls-0_21-webpki-roots"]
|
||||
rustls-0_21-webpki-roots = ["tokio-rustls-024", "webpki-roots-025"]
|
||||
rustls-0_21-native-roots = ["tokio-rustls-024", "dep:rustls-native-certs-06"]
|
||||
|
||||
# use rustls v0.22 impls
|
||||
rustls-0_22 = ["dep:tokio-rustls-025", "dep:rustls-pki-types-1"]
|
||||
rustls-0_22-webpki-roots = ["rustls-0_22", "dep:webpki-roots-026"]
|
||||
rustls-0_22-native-roots = ["rustls-0_22", "dep:rustls-native-certs-07"]
|
||||
|
||||
# use rustls v0.23 impls
|
||||
rustls-0_23 = ["dep:tokio-rustls-026", "dep:rustls-pki-types-1"]
|
||||
rustls-0_23-webpki-roots = ["rustls-0_23", "dep:webpki-roots-026"]
|
||||
rustls-0_23-native-roots = ["rustls-0_23", "dep:rustls-native-certs-07"]
|
||||
|
||||
# use native-tls impls
|
||||
native-tls = ["tokio-native-tls"]
|
||||
native-tls = ["dep:tokio-native-tls"]
|
||||
|
||||
# support http::Uri as connect address
|
||||
uri = ["http"]
|
||||
uri = ["dep:http-0_2", "dep:http-1"]
|
||||
|
||||
[dependencies]
|
||||
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"
|
||||
actix-rt = { version = "2.2", default-features = false }
|
||||
actix-service = "2"
|
||||
actix-utils = "3"
|
||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||
http = { version = "0.2.3", optional = true }
|
||||
log = "0.4"
|
||||
tokio-util = { version = "0.6.3", default-features = false }
|
||||
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-0_2 = { package = "http", version = "0.2.3", optional = true }
|
||||
http-1 = { package = "http", version = "1", optional = true }
|
||||
|
||||
# openssl
|
||||
tls-openssl = { package = "openssl", version = "0.10.9", optional = true }
|
||||
tls-openssl = { package = "openssl", version = "0.10.55", optional = true }
|
||||
tokio-openssl = { version = "0.6", optional = true }
|
||||
|
||||
# rustls
|
||||
tokio-rustls = { version = "0.23", optional = true }
|
||||
webpki-roots = { version = "0.22", optional = true }
|
||||
# rustls PKI types
|
||||
rustls-pki-types-1 = { package = "rustls-pki-types", version = "1", optional = true }
|
||||
|
||||
# rustls v0.20
|
||||
tokio-rustls-023 = { package = "tokio-rustls", version = "0.23", optional = true }
|
||||
|
||||
# rustls v0.21
|
||||
tokio-rustls-024 = { package = "tokio-rustls", version = "0.24", optional = true }
|
||||
|
||||
# rustls v0.22
|
||||
tokio-rustls-025 = { package = "tokio-rustls", version = "0.25", optional = true }
|
||||
|
||||
# rustls v0.23
|
||||
tokio-rustls-026 = { package = "tokio-rustls", version = "0.26", default-features = false, optional = true }
|
||||
|
||||
# webpki-roots used with rustls features
|
||||
webpki-roots-022 = { package = "webpki-roots", version = "0.22", optional = true }
|
||||
webpki-roots-025 = { package = "webpki-roots", version = "0.25", optional = true }
|
||||
webpki-roots-026 = { package = "webpki-roots", version = "0.26", optional = true }
|
||||
|
||||
# native root certificates for rustls impls
|
||||
rustls-native-certs-06 = { package = "rustls-native-certs", version = "0.6", optional = true }
|
||||
rustls-native-certs-07 = { package = "rustls-native-certs", version = "0.7", optional = true }
|
||||
|
||||
# native-tls
|
||||
tokio-native-tls = { version = "0.3", optional = true }
|
||||
|
||||
[target.'cfg(any())'.dependencies]
|
||||
rustls-021 = { package = "rustls", version = "0.21.6", optional = true } # force version with add_trust_anchors method
|
||||
rustls-webpki-0101 = { package = "rustls-webpki", version = "0.101.4", optional = true } # force secure version
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "2.2.0"
|
||||
actix-server = "2.0.0-beta.6"
|
||||
actix-codec = "0.5"
|
||||
actix-rt = "2.2"
|
||||
actix-server = "2"
|
||||
bytes = "1"
|
||||
env_logger = "0.9"
|
||||
futures-util = { version = "0.3.7", default-features = false, features = ["sink"] }
|
||||
log = "0.4"
|
||||
rustls-pemfile = "0.2.1"
|
||||
trust-dns-resolver = "0.20.0"
|
||||
futures-util = { version = "0.3.17", default-features = false, features = ["sink"] }
|
||||
itertools = "0.14"
|
||||
pretty_env_logger = "0.5"
|
||||
rcgen = "0.13"
|
||||
rustls-pemfile = "2"
|
||||
tokio-rustls-026 = { package = "tokio-rustls", version = "0.26" }
|
||||
trust-dns-resolver = "0.23"
|
||||
|
||||
[[example]]
|
||||
name = "tcp-rustls"
|
||||
required-features = ["accept", "rustls"]
|
||||
name = "accept-rustls"
|
||||
required-features = ["accept", "rustls-0_23"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
21
actix-tls/README.md
Normal file
21
actix-tls/README.md
Normal file
@ -0,0 +1,21 @@
|
||||
# `actix-tls`
|
||||
|
||||
> TLS acceptor and connector services for the Actix ecosystem.
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
[](https://crates.io/crates/actix-tls)
|
||||
[](https://docs.rs/actix-tls/3.4.0)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
||||

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

|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
## Resources
|
||||
|
||||
- [Library Documentation](https://docs.rs/actix-tls)
|
||||
- [Examples](/actix-tls/examples)
|
@ -1,4 +1,4 @@
|
||||
//! TLS Acceptor Server
|
||||
//! No-Op TLS Acceptor Server
|
||||
//!
|
||||
//! Using either HTTPie (`http`) or cURL:
|
||||
//!
|
||||
@ -15,14 +15,12 @@
|
||||
//! http --verify=false https://127.0.0.1:8443
|
||||
//! ```
|
||||
|
||||
// this use only exists because of how we have organised the crate
|
||||
// it is not necessary for your actual code
|
||||
use tokio_rustls::rustls;
|
||||
|
||||
// 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` normally
|
||||
use std::{
|
||||
env,
|
||||
fs::File,
|
||||
io::{self, BufReader},
|
||||
path::PathBuf,
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc,
|
||||
@ -32,32 +30,40 @@ use std::{
|
||||
use actix_rt::net::TcpStream;
|
||||
use actix_server::Server;
|
||||
use actix_service::ServiceFactoryExt as _;
|
||||
use actix_tls::accept::rustls::{Acceptor as RustlsAcceptor, TlsStream};
|
||||
use actix_tls::accept::rustls_0_23::{Acceptor as RustlsAcceptor, TlsStream};
|
||||
use futures_util::future::ok;
|
||||
use log::info;
|
||||
use rustls::{server::ServerConfig, Certificate, PrivateKey};
|
||||
use itertools::Itertools as _;
|
||||
use rustls::server::ServerConfig;
|
||||
use rustls_pemfile::{certs, rsa_private_keys};
|
||||
use rustls_pki_types_1::PrivateKeyDer;
|
||||
use tokio_rustls_026::rustls;
|
||||
use tracing::info;
|
||||
|
||||
#[actix_rt::main]
|
||||
async fn main() -> io::Result<()> {
|
||||
env::set_var("RUST_LOG", "info");
|
||||
env_logger::init();
|
||||
pretty_env_logger::formatted_timed_builder()
|
||||
.parse_env(pretty_env_logger::env_logger::Env::default().default_filter_or("info"));
|
||||
|
||||
let root_path = env!("CARGO_MANIFEST_DIR")
|
||||
.parse::<PathBuf>()
|
||||
.unwrap()
|
||||
.join("examples");
|
||||
let cert_path = root_path.clone().join("cert.pem");
|
||||
let key_path = root_path.clone().join("key.pem");
|
||||
|
||||
// Load TLS key and cert files
|
||||
let cert_file = &mut BufReader::new(File::open("./examples/cert.pem").unwrap());
|
||||
let key_file = &mut BufReader::new(File::open("./examples/key.pem").unwrap());
|
||||
let cert_file = &mut BufReader::new(File::open(cert_path).unwrap());
|
||||
let key_file = &mut BufReader::new(File::open(key_path).unwrap());
|
||||
|
||||
let cert_chain = certs(cert_file)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(Certificate)
|
||||
.collect();
|
||||
let mut keys = rsa_private_keys(key_file).unwrap();
|
||||
let cert_chain = certs(cert_file);
|
||||
let mut keys = rsa_private_keys(key_file);
|
||||
|
||||
let tls_config = ServerConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_no_client_auth()
|
||||
.with_single_cert(cert_chain, PrivateKey(keys.remove(0)))
|
||||
.with_single_cert(
|
||||
cert_chain.try_collect::<_, Vec<_>, _>()?,
|
||||
PrivateKeyDer::Pkcs1(keys.next().unwrap()?),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let tls_acceptor = RustlsAcceptor::new(tls_config);
|
||||
@ -65,7 +71,7 @@ async fn main() -> io::Result<()> {
|
||||
let count = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let addr = ("127.0.0.1", 8443);
|
||||
info!("starting server on port: {}", &addr.0);
|
||||
info!("starting server at: {addr:?}");
|
||||
|
||||
Server::build()
|
||||
.bind("tls-example", addr, move || {
|
@ -1,25 +1,49 @@
|
||||
//! 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.
|
||||
//! TLS connection acceptor services.
|
||||
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
error::Error,
|
||||
fmt,
|
||||
sync::atomic::{AtomicUsize, Ordering},
|
||||
};
|
||||
|
||||
use actix_utils::counter::Counter;
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
pub mod openssl;
|
||||
|
||||
#[cfg(feature = "rustls")]
|
||||
pub mod rustls;
|
||||
#[cfg(feature = "rustls-0_20")]
|
||||
pub mod rustls_0_20;
|
||||
|
||||
#[doc(hidden)]
|
||||
#[cfg(feature = "rustls-0_20")]
|
||||
pub use rustls_0_20 as rustls;
|
||||
|
||||
#[cfg(feature = "rustls-0_21")]
|
||||
pub mod rustls_0_21;
|
||||
|
||||
#[cfg(feature = "rustls-0_22")]
|
||||
pub mod rustls_0_22;
|
||||
|
||||
#[cfg(feature = "rustls-0_23")]
|
||||
pub mod rustls_0_23;
|
||||
|
||||
#[cfg(feature = "native-tls")]
|
||||
pub mod native_tls;
|
||||
|
||||
pub(crate) static MAX_CONN: AtomicUsize = AtomicUsize::new(256);
|
||||
|
||||
#[cfg(any(
|
||||
feature = "openssl",
|
||||
feature = "rustls-0_20",
|
||||
feature = "rustls-0_21",
|
||||
feature = "rustls-0_22",
|
||||
feature = "rustls-0_23",
|
||||
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));
|
||||
}
|
||||
@ -34,9 +58,74 @@ pub fn max_concurrent_tls_connect(num: usize) {
|
||||
MAX_CONN.store(num, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// TLS error combined with service error.
|
||||
/// 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
|
||||
#[derive(Debug)]
|
||||
pub enum TlsError<E1, E2> {
|
||||
Tls(E1),
|
||||
Service(E2),
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -1,45 +1,43 @@
|
||||
//! `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_codec::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
use actix_rt::net::{ActixStream, Ready};
|
||||
use actix_rt::{
|
||||
net::{ActixStream, Ready},
|
||||
time::timeout,
|
||||
};
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use actix_utils::counter::Counter;
|
||||
use actix_utils::{
|
||||
counter::Counter,
|
||||
future::{ready, Ready as FutReady},
|
||||
};
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
use tokio_native_tls::{native_tls::Error, TlsAcceptor};
|
||||
|
||||
pub use tokio_native_tls::native_tls::Error;
|
||||
pub use tokio_native_tls::TlsAcceptor;
|
||||
use super::{TlsError, DEFAULT_TLS_HANDSHAKE_TIMEOUT, MAX_CONN_COUNTER};
|
||||
|
||||
use super::MAX_CONN_COUNTER;
|
||||
pub mod reexports {
|
||||
//! Re-exports from `native-tls` that are useful for acceptors.
|
||||
|
||||
/// 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)
|
||||
}
|
||||
pub use tokio_native_tls::{native_tls::Error, TlsAcceptor};
|
||||
}
|
||||
|
||||
impl<T: ActixStream> Deref for TlsStream<T> {
|
||||
type Target = tokio_native_tls::TlsStream<T>;
|
||||
/// Wraps a `native-tls` based async TLS stream in order to implement [`ActixStream`].
|
||||
pub struct TlsStream<IO>(tokio_native_tls::TlsStream<IO>);
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
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>);
|
||||
|
||||
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> {
|
||||
impl<IO: ActixStream> AsyncRead for TlsStream<IO> {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
@ -49,7 +47,7 @@ impl<T: ActixStream> AsyncRead for TlsStream<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ActixStream> AsyncWrite for TlsStream<T> {
|
||||
impl<IO: ActixStream> AsyncWrite for TlsStream<IO> {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
@ -75,32 +73,41 @@ impl<T: ActixStream> AsyncWrite for TlsStream<T> {
|
||||
}
|
||||
|
||||
fn is_write_vectored(&self) -> bool {
|
||||
(&**self).is_write_vectored()
|
||||
(**self).is_write_vectored()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ActixStream> ActixStream for TlsStream<T> {
|
||||
impl<IO: ActixStream> ActixStream for TlsStream<IO> {
|
||||
fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
|
||||
T::poll_read_ready((&**self).get_ref().get_ref().get_ref(), cx)
|
||||
IO::poll_read_ready((**self).get_ref().get_ref().get_ref(), cx)
|
||||
}
|
||||
|
||||
fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
|
||||
T::poll_write_ready((&**self).get_ref().get_ref().get_ref(), cx)
|
||||
IO::poll_write_ready((**self).get_ref().get_ref().get_ref(), cx)
|
||||
}
|
||||
}
|
||||
|
||||
/// Accept TLS connections via `native-tls` package.
|
||||
///
|
||||
/// `native-tls` feature enables this `Acceptor` type.
|
||||
/// Accept TLS connections via the `native-tls` crate.
|
||||
pub struct Acceptor {
|
||||
acceptor: TlsAcceptor,
|
||||
handshake_timeout: Duration,
|
||||
}
|
||||
|
||||
impl Acceptor {
|
||||
/// Create `native-tls` based `Acceptor` service factory.
|
||||
#[inline]
|
||||
/// Constructs `native-tls` based acceptor service factory.
|
||||
pub fn new(acceptor: TlsAcceptor) -> Self {
|
||||
Acceptor { acceptor }
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@ -109,39 +116,43 @@ impl Clone for Acceptor {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
acceptor: self.acceptor.clone(),
|
||||
handshake_timeout: self.handshake_timeout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ActixStream + 'static> ServiceFactory<T> for Acceptor {
|
||||
type Response = TlsStream<T>;
|
||||
type Error = Error;
|
||||
impl<IO: ActixStream + 'static> ServiceFactory<IO> for Acceptor {
|
||||
type Response = TlsStream<IO>;
|
||||
type Error = TlsError<Error, Infallible>;
|
||||
type Config = ();
|
||||
|
||||
type Service = NativeTlsAcceptorService;
|
||||
type Service = AcceptorService;
|
||||
type InitError = ();
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
||||
type Future = FutReady<Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
let res = MAX_CONN_COUNTER.with(|conns| {
|
||||
Ok(NativeTlsAcceptorService {
|
||||
Ok(AcceptorService {
|
||||
acceptor: self.acceptor.clone(),
|
||||
conns: conns.clone(),
|
||||
handshake_timeout: self.handshake_timeout,
|
||||
})
|
||||
});
|
||||
Box::pin(async { res })
|
||||
|
||||
ready(res)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NativeTlsAcceptorService {
|
||||
/// Native-TLS based acceptor service.
|
||||
pub struct AcceptorService {
|
||||
acceptor: TlsAcceptor,
|
||||
conns: Counter,
|
||||
handshake_timeout: Duration,
|
||||
}
|
||||
|
||||
impl<T: ActixStream + 'static> Service<T> for NativeTlsAcceptorService {
|
||||
type Response = TlsStream<T>;
|
||||
type Error = Error;
|
||||
type Future = LocalBoxFuture<'static, Result<TlsStream<T>, Error>>;
|
||||
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>>;
|
||||
|
||||
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
if self.conns.available(cx) {
|
||||
@ -151,13 +162,21 @@ impl<T: ActixStream + 'static> Service<T> for NativeTlsAcceptorService {
|
||||
}
|
||||
}
|
||||
|
||||
fn call(&self, io: T) -> Self::Future {
|
||||
fn call(&self, io: IO) -> Self::Future {
|
||||
let guard = self.conns.get();
|
||||
let acceptor = self.acceptor.clone();
|
||||
|
||||
let dur = self.handshake_timeout;
|
||||
|
||||
Box::pin(async move {
|
||||
let io = acceptor.accept(io).await;
|
||||
drop(guard);
|
||||
io.map(Into::into)
|
||||
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),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,47 +1,46 @@
|
||||
//! `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_codec::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
use actix_rt::net::{ActixStream, Ready};
|
||||
use actix_rt::{
|
||||
net::{ActixStream, Ready},
|
||||
time::{sleep, Sleep},
|
||||
};
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
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 actix_utils::{
|
||||
counter::{Counter, CounterGuard},
|
||||
future::{ready, Ready as FutReady},
|
||||
};
|
||||
use openssl::ssl::{Error, Ssl, SslAcceptor};
|
||||
use pin_project_lite::pin_project;
|
||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
|
||||
use super::MAX_CONN_COUNTER;
|
||||
use super::{TlsError, DEFAULT_TLS_HANDSHAKE_TIMEOUT, MAX_CONN_COUNTER};
|
||||
|
||||
/// Wrapper type for `tokio_openssl::SslStream` in order to impl `ActixStream` trait.
|
||||
pub struct TlsStream<T>(tokio_openssl::SslStream<T>);
|
||||
pub mod reexports {
|
||||
//! Re-exports from `openssl` that are useful for acceptors.
|
||||
|
||||
impl<T> From<tokio_openssl::SslStream<T>> for TlsStream<T> {
|
||||
fn from(stream: tokio_openssl::SslStream<T>) -> Self {
|
||||
Self(stream)
|
||||
}
|
||||
pub use openssl::ssl::{
|
||||
AlpnError, Error, HandshakeError, Ssl, SslAcceptor, SslAcceptorBuilder,
|
||||
};
|
||||
}
|
||||
|
||||
impl<T> Deref for TlsStream<T> {
|
||||
type Target = tokio_openssl::SslStream<T>;
|
||||
/// Wraps an `openssl` based async TLS stream in order to implement [`ActixStream`].
|
||||
pub struct TlsStream<IO>(tokio_openssl::SslStream<IO>);
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
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>);
|
||||
|
||||
impl<T> DerefMut for TlsStream<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ActixStream> AsyncRead for TlsStream<T> {
|
||||
impl<IO: ActixStream> AsyncRead for TlsStream<IO> {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
@ -51,7 +50,7 @@ impl<T: ActixStream> AsyncRead for TlsStream<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ActixStream> AsyncWrite for TlsStream<T> {
|
||||
impl<IO: ActixStream> AsyncWrite for TlsStream<IO> {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
@ -77,32 +76,42 @@ impl<T: ActixStream> AsyncWrite for TlsStream<T> {
|
||||
}
|
||||
|
||||
fn is_write_vectored(&self) -> bool {
|
||||
(&**self).is_write_vectored()
|
||||
(**self).is_write_vectored()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ActixStream> ActixStream for TlsStream<T> {
|
||||
impl<IO: ActixStream> ActixStream for TlsStream<IO> {
|
||||
fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
|
||||
T::poll_read_ready((&**self).get_ref(), cx)
|
||||
IO::poll_read_ready((**self).get_ref(), cx)
|
||||
}
|
||||
|
||||
fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
|
||||
T::poll_write_ready((&**self).get_ref(), cx)
|
||||
IO::poll_write_ready((**self).get_ref(), cx)
|
||||
}
|
||||
}
|
||||
|
||||
/// Accept TLS connections via `openssl` package.
|
||||
///
|
||||
/// `openssl` feature enables this `Acceptor` type.
|
||||
/// Accept TLS connections via the `openssl` crate.
|
||||
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 }
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,38 +120,43 @@ impl Clone for Acceptor {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
acceptor: self.acceptor.clone(),
|
||||
handshake_timeout: self.handshake_timeout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ActixStream> ServiceFactory<T> for Acceptor {
|
||||
type Response = TlsStream<T>;
|
||||
type Error = SslError;
|
||||
impl<IO: ActixStream> ServiceFactory<IO> for Acceptor {
|
||||
type Response = TlsStream<IO>;
|
||||
type Error = TlsError<Error, Infallible>;
|
||||
type Config = ();
|
||||
type Service = AcceptorService;
|
||||
type InitError = ();
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
||||
type Future = FutReady<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,
|
||||
})
|
||||
});
|
||||
Box::pin(async { res })
|
||||
|
||||
ready(res)
|
||||
}
|
||||
}
|
||||
|
||||
/// OpenSSL based acceptor service.
|
||||
pub struct AcceptorService {
|
||||
acceptor: SslAcceptor,
|
||||
conns: Counter,
|
||||
handshake_timeout: Duration,
|
||||
}
|
||||
|
||||
impl<T: ActixStream> Service<T> for AcceptorService {
|
||||
type Response = TlsStream<T>;
|
||||
type Error = SslError;
|
||||
type Future = AcceptorServiceResponse<T>;
|
||||
impl<IO: ActixStream> Service<IO> for AcceptorService {
|
||||
type Response = TlsStream<IO>;
|
||||
type Error = TlsError<Error, Infallible>;
|
||||
type Future = AcceptFut<IO>;
|
||||
|
||||
fn poll_ready(&self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
if self.conns.available(ctx) {
|
||||
@ -152,30 +166,43 @@ impl<T: ActixStream> Service<T> for AcceptorService {
|
||||
}
|
||||
}
|
||||
|
||||
fn call(&self, io: T) -> Self::Future {
|
||||
fn call(&self, io: IO) -> Self::Future {
|
||||
let ssl_ctx = self.acceptor.context();
|
||||
let ssl = Ssl::new(ssl_ctx).expect("Provided SSL acceptor was invalid.");
|
||||
AcceptorServiceResponse {
|
||||
|
||||
AcceptFut {
|
||||
_guard: self.conns.get(),
|
||||
timeout: sleep(self.handshake_timeout),
|
||||
stream: Some(tokio_openssl::SslStream::new(ssl, io).unwrap()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AcceptorServiceResponse<T: ActixStream> {
|
||||
stream: Option<tokio_openssl::SslStream<T>>,
|
||||
_guard: CounterGuard,
|
||||
}
|
||||
|
||||
impl<T: ActixStream> Future for AcceptorServiceResponse<T> {
|
||||
type Output = Result<TlsStream<T>, SslError>;
|
||||
|
||||
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()))
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
impl<IO: ActixStream> Future for AcceptFut<IO> {
|
||||
type Output = Result<TlsStream<IO>, TlsError<Error, Infallible>>;
|
||||
|
||||
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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,179 +0,0 @@
|
||||
use std::{
|
||||
future::Future,
|
||||
io::{self, IoSlice},
|
||||
ops::{Deref, DerefMut},
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
use actix_rt::net::{ActixStream, Ready};
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use actix_utils::counter::{Counter, CounterGuard};
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use tokio_rustls::{Accept, TlsAcceptor};
|
||||
|
||||
pub use tokio_rustls::rustls::ServerConfig;
|
||||
|
||||
use super::MAX_CONN_COUNTER;
|
||||
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for TlsStream<T> {
|
||||
type Target = tokio_rustls::server::TlsStream<T>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
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<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
Pin::new(&mut **self.get_mut()).poll_read(cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ActixStream> AsyncWrite for TlsStream<T> {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
Pin::new(&mut **self.get_mut()).poll_write(cx, buf)
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
Pin::new(&mut **self.get_mut()).poll_flush(cx)
|
||||
}
|
||||
|
||||
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
Pin::new(&mut **self.get_mut()).poll_shutdown(cx)
|
||||
}
|
||||
|
||||
fn poll_write_vectored(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
bufs: &[IoSlice<'_>],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
Pin::new(&mut **self.get_mut()).poll_write_vectored(cx, bufs)
|
||||
}
|
||||
|
||||
fn is_write_vectored(&self) -> bool {
|
||||
(&**self).is_write_vectored()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ActixStream> ActixStream for TlsStream<T> {
|
||||
fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
|
||||
T::poll_read_ready((&**self).get_ref().0, cx)
|
||||
}
|
||||
|
||||
fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
|
||||
T::poll_write_ready((&**self).get_ref().0, cx)
|
||||
}
|
||||
}
|
||||
|
||||
/// Accept TLS connections via `rustls` package.
|
||||
///
|
||||
/// `rustls` feature enables this `Acceptor` type.
|
||||
pub struct Acceptor {
|
||||
config: Arc<ServerConfig>,
|
||||
}
|
||||
|
||||
impl Acceptor {
|
||||
/// Create Rustls based `Acceptor` service factory.
|
||||
#[inline]
|
||||
pub fn new(config: ServerConfig) -> Self {
|
||||
Acceptor {
|
||||
config: Arc::new(config),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Acceptor {
|
||||
#[inline]
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
config: self.config.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ActixStream> ServiceFactory<T> for Acceptor {
|
||||
type Response = TlsStream<T>;
|
||||
type Error = io::Error;
|
||||
type Config = ();
|
||||
|
||||
type Service = AcceptorService;
|
||||
type 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(),
|
||||
})
|
||||
});
|
||||
Box::pin(async { res })
|
||||
}
|
||||
}
|
||||
|
||||
/// Rustls based `Acceptor` service
|
||||
pub struct AcceptorService {
|
||||
acceptor: TlsAcceptor,
|
||||
conns: Counter,
|
||||
}
|
||||
|
||||
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) {
|
||||
Poll::Ready(Ok(()))
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
fn call(&self, req: T) -> Self::Future {
|
||||
AcceptorServiceFut {
|
||||
_guard: self.conns.get(),
|
||||
fut: self.acceptor.accept(req),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AcceptorServiceFut<T: ActixStream> {
|
||||
fut: Accept<T>,
|
||||
_guard: CounterGuard,
|
||||
}
|
||||
|
||||
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 this = self.get_mut();
|
||||
Pin::new(&mut this.fut).poll(cx).map_ok(TlsStream)
|
||||
}
|
||||
}
|
198
actix-tls/src/accept/rustls_0_20.rs
Normal file
198
actix-tls/src/accept/rustls_0_20.rs
Normal file
@ -0,0 +1,198 @@
|
||||
//! `rustls` v0.20 based TLS connection acceptor service.
|
||||
//!
|
||||
//! See [`Acceptor`] for main service factory docs.
|
||||
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
future::Future,
|
||||
io::{self, IoSlice},
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use actix_rt::{
|
||||
net::{ActixStream, Ready},
|
||||
time::{sleep, Sleep},
|
||||
};
|
||||
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::{Accept, TlsAcceptor};
|
||||
use tokio_rustls_023 as tokio_rustls;
|
||||
|
||||
use super::{TlsError, DEFAULT_TLS_HANDSHAKE_TIMEOUT, MAX_CONN_COUNTER};
|
||||
|
||||
pub mod reexports {
|
||||
//! Re-exports from `rustls` that are useful for acceptors.
|
||||
|
||||
pub use tokio_rustls_023::rustls::ServerConfig;
|
||||
}
|
||||
|
||||
/// Wraps a `rustls` based async TLS stream in order to implement [`ActixStream`].
|
||||
pub struct TlsStream<IO>(tokio_rustls::server::TlsStream<IO>);
|
||||
|
||||
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>);
|
||||
|
||||
impl<IO: ActixStream> AsyncRead for TlsStream<IO> {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
Pin::new(&mut **self.get_mut()).poll_read(cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<IO: ActixStream> AsyncWrite for TlsStream<IO> {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
Pin::new(&mut **self.get_mut()).poll_write(cx, buf)
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
Pin::new(&mut **self.get_mut()).poll_flush(cx)
|
||||
}
|
||||
|
||||
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
Pin::new(&mut **self.get_mut()).poll_shutdown(cx)
|
||||
}
|
||||
|
||||
fn poll_write_vectored(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
bufs: &[IoSlice<'_>],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
Pin::new(&mut **self.get_mut()).poll_write_vectored(cx, bufs)
|
||||
}
|
||||
|
||||
fn is_write_vectored(&self) -> bool {
|
||||
(**self).is_write_vectored()
|
||||
}
|
||||
}
|
||||
|
||||
impl<IO: ActixStream> ActixStream for TlsStream<IO> {
|
||||
fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
|
||||
IO::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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Accept TLS connections via the `rustls` crate.
|
||||
pub struct Acceptor {
|
||||
config: Arc<reexports::ServerConfig>,
|
||||
handshake_timeout: Duration,
|
||||
}
|
||||
|
||||
impl Acceptor {
|
||||
/// Constructs `rustls` based acceptor service factory.
|
||||
pub fn new(config: reexports::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 {
|
||||
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>;
|
||||
type Config = ();
|
||||
type Service = AcceptorService;
|
||||
type InitError = ();
|
||||
type Future = FutReady<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)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>;
|
||||
|
||||
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
if self.conns.available(cx) {
|
||||
Poll::Ready(Ok(()))
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
fn call(&self, req: IO) -> Self::Future {
|
||||
AcceptFut {
|
||||
fut: self.acceptor.accept(req),
|
||||
timeout: sleep(self.handshake_timeout),
|
||||
_guard: self.conns.get(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
/// Accept future for Rustls service.
|
||||
#[doc(hidden)]
|
||||
pub struct AcceptFut<IO: ActixStream> {
|
||||
fut: Accept<IO>,
|
||||
#[pin]
|
||||
timeout: Sleep,
|
||||
_guard: CounterGuard,
|
||||
}
|
||||
}
|
||||
|
||||
impl<IO: ActixStream> Future for AcceptFut<IO> {
|
||||
type Output = Result<TlsStream<IO>, TlsError<io::Error, Infallible>>;
|
||||
|
||||
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)),
|
||||
}
|
||||
}
|
||||
}
|
198
actix-tls/src/accept/rustls_0_21.rs
Normal file
198
actix-tls/src/accept/rustls_0_21.rs
Normal file
@ -0,0 +1,198 @@
|
||||
//! `rustls` v0.21 based TLS connection acceptor service.
|
||||
//!
|
||||
//! See [`Acceptor`] for main service factory docs.
|
||||
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
future::Future,
|
||||
io::{self, IoSlice},
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use actix_rt::{
|
||||
net::{ActixStream, Ready},
|
||||
time::{sleep, Sleep},
|
||||
};
|
||||
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::{Accept, TlsAcceptor};
|
||||
use tokio_rustls_024 as tokio_rustls;
|
||||
|
||||
use super::{TlsError, DEFAULT_TLS_HANDSHAKE_TIMEOUT, MAX_CONN_COUNTER};
|
||||
|
||||
pub mod reexports {
|
||||
//! Re-exports from `rustls` that are useful for acceptors.
|
||||
|
||||
pub use tokio_rustls_024::rustls::ServerConfig;
|
||||
}
|
||||
|
||||
/// Wraps a `rustls` based async TLS stream in order to implement [`ActixStream`].
|
||||
pub struct TlsStream<IO>(tokio_rustls::server::TlsStream<IO>);
|
||||
|
||||
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>);
|
||||
|
||||
impl<IO: ActixStream> AsyncRead for TlsStream<IO> {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
Pin::new(&mut **self.get_mut()).poll_read(cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<IO: ActixStream> AsyncWrite for TlsStream<IO> {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
Pin::new(&mut **self.get_mut()).poll_write(cx, buf)
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
Pin::new(&mut **self.get_mut()).poll_flush(cx)
|
||||
}
|
||||
|
||||
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
Pin::new(&mut **self.get_mut()).poll_shutdown(cx)
|
||||
}
|
||||
|
||||
fn poll_write_vectored(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
bufs: &[IoSlice<'_>],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
Pin::new(&mut **self.get_mut()).poll_write_vectored(cx, bufs)
|
||||
}
|
||||
|
||||
fn is_write_vectored(&self) -> bool {
|
||||
(**self).is_write_vectored()
|
||||
}
|
||||
}
|
||||
|
||||
impl<IO: ActixStream> ActixStream for TlsStream<IO> {
|
||||
fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
|
||||
IO::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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Accept TLS connections via the `rustls` crate.
|
||||
pub struct Acceptor {
|
||||
config: Arc<reexports::ServerConfig>,
|
||||
handshake_timeout: Duration,
|
||||
}
|
||||
|
||||
impl Acceptor {
|
||||
/// Constructs `rustls` based acceptor service factory.
|
||||
pub fn new(config: reexports::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 {
|
||||
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>;
|
||||
type Config = ();
|
||||
type Service = AcceptorService;
|
||||
type InitError = ();
|
||||
type Future = FutReady<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)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>;
|
||||
|
||||
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
if self.conns.available(cx) {
|
||||
Poll::Ready(Ok(()))
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
fn call(&self, req: IO) -> Self::Future {
|
||||
AcceptFut {
|
||||
fut: self.acceptor.accept(req),
|
||||
timeout: sleep(self.handshake_timeout),
|
||||
_guard: self.conns.get(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
/// Accept future for Rustls service.
|
||||
#[doc(hidden)]
|
||||
pub struct AcceptFut<IO: ActixStream> {
|
||||
fut: Accept<IO>,
|
||||
#[pin]
|
||||
timeout: Sleep,
|
||||
_guard: CounterGuard,
|
||||
}
|
||||
}
|
||||
|
||||
impl<IO: ActixStream> Future for AcceptFut<IO> {
|
||||
type Output = Result<TlsStream<IO>, TlsError<io::Error, Infallible>>;
|
||||
|
||||
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)),
|
||||
}
|
||||
}
|
||||
}
|
198
actix-tls/src/accept/rustls_0_22.rs
Normal file
198
actix-tls/src/accept/rustls_0_22.rs
Normal file
@ -0,0 +1,198 @@
|
||||
//! `rustls` v0.22 based TLS connection acceptor service.
|
||||
//!
|
||||
//! See [`Acceptor`] for main service factory docs.
|
||||
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
future::Future,
|
||||
io::{self, IoSlice},
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use actix_rt::{
|
||||
net::{ActixStream, Ready},
|
||||
time::{sleep, Sleep},
|
||||
};
|
||||
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::{Accept, TlsAcceptor};
|
||||
use tokio_rustls_025 as tokio_rustls;
|
||||
|
||||
use super::{TlsError, DEFAULT_TLS_HANDSHAKE_TIMEOUT, MAX_CONN_COUNTER};
|
||||
|
||||
pub mod reexports {
|
||||
//! Re-exports from `rustls` that are useful for acceptors.
|
||||
|
||||
pub use tokio_rustls_025::rustls::ServerConfig;
|
||||
}
|
||||
|
||||
/// Wraps a `rustls` based async TLS stream in order to implement [`ActixStream`].
|
||||
pub struct TlsStream<IO>(tokio_rustls::server::TlsStream<IO>);
|
||||
|
||||
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>);
|
||||
|
||||
impl<IO: ActixStream> AsyncRead for TlsStream<IO> {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
Pin::new(&mut **self.get_mut()).poll_read(cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<IO: ActixStream> AsyncWrite for TlsStream<IO> {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
Pin::new(&mut **self.get_mut()).poll_write(cx, buf)
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
Pin::new(&mut **self.get_mut()).poll_flush(cx)
|
||||
}
|
||||
|
||||
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
Pin::new(&mut **self.get_mut()).poll_shutdown(cx)
|
||||
}
|
||||
|
||||
fn poll_write_vectored(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
bufs: &[IoSlice<'_>],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
Pin::new(&mut **self.get_mut()).poll_write_vectored(cx, bufs)
|
||||
}
|
||||
|
||||
fn is_write_vectored(&self) -> bool {
|
||||
(**self).is_write_vectored()
|
||||
}
|
||||
}
|
||||
|
||||
impl<IO: ActixStream> ActixStream for TlsStream<IO> {
|
||||
fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
|
||||
IO::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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Accept TLS connections via the `rustls` crate.
|
||||
pub struct Acceptor {
|
||||
config: Arc<reexports::ServerConfig>,
|
||||
handshake_timeout: Duration,
|
||||
}
|
||||
|
||||
impl Acceptor {
|
||||
/// Constructs `rustls` based acceptor service factory.
|
||||
pub fn new(config: reexports::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 {
|
||||
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>;
|
||||
type Config = ();
|
||||
type Service = AcceptorService;
|
||||
type InitError = ();
|
||||
type Future = FutReady<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)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>;
|
||||
|
||||
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
if self.conns.available(cx) {
|
||||
Poll::Ready(Ok(()))
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
fn call(&self, req: IO) -> Self::Future {
|
||||
AcceptFut {
|
||||
fut: self.acceptor.accept(req),
|
||||
timeout: sleep(self.handshake_timeout),
|
||||
_guard: self.conns.get(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
/// Accept future for Rustls service.
|
||||
#[doc(hidden)]
|
||||
pub struct AcceptFut<IO: ActixStream> {
|
||||
fut: Accept<IO>,
|
||||
#[pin]
|
||||
timeout: Sleep,
|
||||
_guard: CounterGuard,
|
||||
}
|
||||
}
|
||||
|
||||
impl<IO: ActixStream> Future for AcceptFut<IO> {
|
||||
type Output = Result<TlsStream<IO>, TlsError<io::Error, Infallible>>;
|
||||
|
||||
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)),
|
||||
}
|
||||
}
|
||||
}
|
198
actix-tls/src/accept/rustls_0_23.rs
Normal file
198
actix-tls/src/accept/rustls_0_23.rs
Normal file
@ -0,0 +1,198 @@
|
||||
//! `rustls` v0.23 based TLS connection acceptor service.
|
||||
//!
|
||||
//! See [`Acceptor`] for main service factory docs.
|
||||
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
future::Future,
|
||||
io::{self, IoSlice},
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use actix_rt::{
|
||||
net::{ActixStream, Ready},
|
||||
time::{sleep, Sleep},
|
||||
};
|
||||
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::{Accept, TlsAcceptor};
|
||||
use tokio_rustls_026 as tokio_rustls;
|
||||
|
||||
use super::{TlsError, DEFAULT_TLS_HANDSHAKE_TIMEOUT, MAX_CONN_COUNTER};
|
||||
|
||||
pub mod reexports {
|
||||
//! Re-exports from `rustls` that are useful for acceptors.
|
||||
|
||||
pub use tokio_rustls_026::rustls::ServerConfig;
|
||||
}
|
||||
|
||||
/// Wraps a `rustls` based async TLS stream in order to implement [`ActixStream`].
|
||||
pub struct TlsStream<IO>(tokio_rustls::server::TlsStream<IO>);
|
||||
|
||||
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>);
|
||||
|
||||
impl<IO: ActixStream> AsyncRead for TlsStream<IO> {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
Pin::new(&mut **self.get_mut()).poll_read(cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<IO: ActixStream> AsyncWrite for TlsStream<IO> {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
Pin::new(&mut **self.get_mut()).poll_write(cx, buf)
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
Pin::new(&mut **self.get_mut()).poll_flush(cx)
|
||||
}
|
||||
|
||||
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
Pin::new(&mut **self.get_mut()).poll_shutdown(cx)
|
||||
}
|
||||
|
||||
fn poll_write_vectored(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
bufs: &[IoSlice<'_>],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
Pin::new(&mut **self.get_mut()).poll_write_vectored(cx, bufs)
|
||||
}
|
||||
|
||||
fn is_write_vectored(&self) -> bool {
|
||||
(**self).is_write_vectored()
|
||||
}
|
||||
}
|
||||
|
||||
impl<IO: ActixStream> ActixStream for TlsStream<IO> {
|
||||
fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
|
||||
IO::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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Accept TLS connections via the `rustls` crate.
|
||||
pub struct Acceptor {
|
||||
config: Arc<reexports::ServerConfig>,
|
||||
handshake_timeout: Duration,
|
||||
}
|
||||
|
||||
impl Acceptor {
|
||||
/// Constructs `rustls` based acceptor service factory.
|
||||
pub fn new(config: reexports::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 {
|
||||
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>;
|
||||
type Config = ();
|
||||
type Service = AcceptorService;
|
||||
type InitError = ();
|
||||
type Future = FutReady<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)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>;
|
||||
|
||||
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
if self.conns.available(cx) {
|
||||
Poll::Ready(Ok(()))
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
fn call(&self, req: IO) -> Self::Future {
|
||||
AcceptFut {
|
||||
fut: self.acceptor.accept(req),
|
||||
timeout: sleep(self.handshake_timeout),
|
||||
_guard: self.conns.get(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
/// Accept future for Rustls service.
|
||||
#[doc(hidden)]
|
||||
pub struct AcceptFut<IO: ActixStream> {
|
||||
fut: Accept<IO>,
|
||||
#[pin]
|
||||
timeout: Sleep,
|
||||
_guard: CounterGuard,
|
||||
}
|
||||
}
|
||||
|
||||
impl<IO: ActixStream> Future for AcceptFut<IO> {
|
||||
type Output = Result<TlsStream<IO>, TlsError<io::Error, Infallible>>;
|
||||
|
||||
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)),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,350 +0,0 @@
|
||||
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))
|
||||
)
|
||||
}
|
||||
}
|
82
actix-tls/src/connect/connect_addrs.rs
Normal file
82
actix-tls/src/connect/connect_addrs.rs
Normal file
@ -0,0 +1,82 @@
|
||||
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<'_> {}
|
51
actix-tls/src/connect/connection.rs
Normal file
51
actix-tls/src/connect/connection.rs
Normal file
@ -0,0 +1,51 @@
|
||||
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()
|
||||
}
|
||||
}
|
234
actix-tls/src/connect/connector.rs
Executable file → Normal file
234
actix-tls/src/connect/connector.rs
Executable file → Normal file
@ -1,194 +1,128 @@
|
||||
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_rt::net::TcpStream;
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use futures_core::{future::LocalBoxFuture, ready};
|
||||
use log::{error, trace};
|
||||
use tokio_util::sync::ReusableBoxFuture;
|
||||
use actix_utils::future::{ok, Ready};
|
||||
use futures_core::ready;
|
||||
|
||||
use super::connect::{Address, Connect, ConnectAddrs, Connection};
|
||||
use super::error::ConnectError;
|
||||
use super::{
|
||||
error::ConnectError,
|
||||
resolver::{Resolver, ResolverService},
|
||||
tcp::{TcpConnector, TcpConnectorService},
|
||||
ConnectInfo, Connection, Host,
|
||||
};
|
||||
|
||||
/// TCP connector service factory
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct TcpConnectorFactory;
|
||||
/// 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,
|
||||
}
|
||||
|
||||
impl TcpConnectorFactory {
|
||||
/// Create TCP connector service
|
||||
pub fn service(&self) -> TcpConnector {
|
||||
TcpConnector
|
||||
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<T: Address> ServiceFactory<Connect<T>> for TcpConnectorFactory {
|
||||
type Response = Connection<T, TcpStream>;
|
||||
impl<R: Host> ServiceFactory<ConnectInfo<R>> for Connector {
|
||||
type Response = Connection<R, TcpStream>;
|
||||
type Error = ConnectError;
|
||||
type Config = ();
|
||||
type Service = TcpConnector;
|
||||
type Service = ConnectorService;
|
||||
type InitError = ();
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
||||
type Future = Ready<Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
let service = self.service();
|
||||
Box::pin(async move { Ok(service) })
|
||||
ok(self.service())
|
||||
}
|
||||
}
|
||||
|
||||
/// TCP connector service
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct TcpConnector;
|
||||
/// 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,
|
||||
}
|
||||
|
||||
impl<T: Address> Service<Connect<T>> for TcpConnector {
|
||||
type Response = Connection<T, TcpStream>;
|
||||
impl<R: Host> Service<ConnectInfo<R>> for ConnectorService {
|
||||
type Response = Connection<R, TcpStream>;
|
||||
type Error = ConnectError;
|
||||
type Future = TcpConnectorResponse<T>;
|
||||
type Future = ConnectServiceResponse<R>;
|
||||
|
||||
actix_service::always_ready!();
|
||||
|
||||
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)
|
||||
fn call(&self, req: ConnectInfo<R>) -> Self::Future {
|
||||
ConnectServiceResponse {
|
||||
fut: ConnectFut::Resolve(self.resolver.call(req)),
|
||||
tcp: self.tcp,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>),
|
||||
/// 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),
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
/// Container for the intermediate states of [`ConnectFut`].
|
||||
pub(crate) enum ConnectFutState<R: Host> {
|
||||
Resolved(ConnectInfo<R>),
|
||||
Connected(Connection<R, TcpStream>),
|
||||
}
|
||||
|
||||
trace!(
|
||||
"TCP connector: connecting to {} on port {}",
|
||||
req.hostname(),
|
||||
port
|
||||
);
|
||||
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)
|
||||
}
|
||||
|
||||
match addr {
|
||||
ConnectAddrs::None => unreachable!("none variant already checked"),
|
||||
|
||||
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)),
|
||||
}
|
||||
ConnectFut::Connect(ref mut fut) => {
|
||||
Pin::new(fut).poll(cx).map_ok(ConnectFutState::Connected)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Address> Future for TcpConnectorResponse<T> {
|
||||
type Output = Result<Connection<T, TcpStream>, ConnectError>;
|
||||
pub struct ConnectServiceResponse<R: Host> {
|
||||
fut: ConnectFut<R>,
|
||||
tcp: TcpConnectorService,
|
||||
}
|
||||
|
||||
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())),
|
||||
impl<R: Host> Future for ConnectServiceResponse<R> {
|
||||
type Output = Result<Connection<R, TcpStream>, ConnectError>;
|
||||
|
||||
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)));
|
||||
}
|
||||
}
|
||||
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));
|
||||
}
|
||||
},
|
||||
ConnectFutState::Connected(res) => return Poll::Ready(Ok(res)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,25 +1,44 @@
|
||||
use std::io;
|
||||
use std::{error::Error, fmt, io};
|
||||
|
||||
use derive_more::Display;
|
||||
|
||||
#[derive(Debug, Display)]
|
||||
/// Errors that can result from using a connector service.
|
||||
#[derive(Debug)]
|
||||
pub enum ConnectError {
|
||||
/// Failed to resolve the hostname
|
||||
#[display(fmt = "Failed resolving hostname: {}", _0)]
|
||||
/// Failed to resolve the hostname.
|
||||
Resolver(Box<dyn std::error::Error>),
|
||||
|
||||
/// No dns records
|
||||
#[display(fmt = "No dns records found for the input")]
|
||||
/// No DNS records.
|
||||
NoRecords,
|
||||
|
||||
/// Invalid input
|
||||
/// Invalid input.
|
||||
InvalidInput,
|
||||
|
||||
/// Unresolved host name
|
||||
#[display(fmt = "Connector received `Connect` method with unresolved host")]
|
||||
/// Unresolved host name.
|
||||
Unresolved,
|
||||
|
||||
/// Connection IO error
|
||||
#[display(fmt = "{}", _0)]
|
||||
/// Connection IO error.
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
71
actix-tls/src/connect/host.rs
Normal file
71
actix-tls/src/connect/host.rs
Normal file
@ -0,0 +1,71 @@
|
||||
//! 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);
|
||||
}
|
||||
}
|
251
actix-tls/src/connect/info.rs
Normal file
251
actix-tls/src/connect/info.rs
Normal file
@ -0,0 +1,251 @@
|
||||
//! 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);
|
||||
/// ```
|
||||
#[allow(clippy::implied_bounds_in_impls)]
|
||||
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);
|
||||
/// ```
|
||||
#[allow(clippy::implied_bounds_in_impls)]
|
||||
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,76 +1,66 @@
|
||||
//! TCP connector services for Actix ecosystem.
|
||||
//! TCP and TLS connector services.
|
||||
//!
|
||||
//! # Stages of the TCP connector service:
|
||||
//! - Resolve [`Address`] with given [`Resolver`] and collect list of socket addresses.
|
||||
//! - Establish TCP connection and return [`TcpStream`].
|
||||
//! 1. Resolve [`Host`] (if needed) with given [`Resolver`] and collect list of socket addresses.
|
||||
//! 1. Establish TCP connection and return [`TcpStream`].
|
||||
//!
|
||||
//! # Stages of TLS connector services:
|
||||
//! - 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
|
||||
//! 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`.
|
||||
//!
|
||||
//! [`TcpStream`]: actix_rt::net::TcpStream
|
||||
|
||||
#[allow(clippy::module_inception)]
|
||||
mod connect;
|
||||
mod connect_addrs;
|
||||
mod connection;
|
||||
mod connector;
|
||||
mod error;
|
||||
mod host;
|
||||
mod info;
|
||||
mod resolve;
|
||||
mod service;
|
||||
pub mod tls;
|
||||
#[doc(hidden)]
|
||||
pub use tls as ssl;
|
||||
mod resolver;
|
||||
pub mod tcp;
|
||||
|
||||
#[cfg(feature = "uri")]
|
||||
mod uri;
|
||||
|
||||
use actix_rt::net::TcpStream;
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
#[cfg(feature = "openssl")]
|
||||
pub mod openssl;
|
||||
|
||||
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(any(
|
||||
feature = "rustls-0_20-webpki-roots",
|
||||
feature = "rustls-0_20-native-roots",
|
||||
))]
|
||||
pub mod rustls_0_20;
|
||||
|
||||
/// 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()
|
||||
}
|
||||
#[doc(hidden)]
|
||||
#[cfg(any(
|
||||
feature = "rustls-0_20-webpki-roots",
|
||||
feature = "rustls-0_20-native-roots",
|
||||
))]
|
||||
pub use rustls_0_20 as rustls;
|
||||
|
||||
/// 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)
|
||||
}
|
||||
#[cfg(any(
|
||||
feature = "rustls-0_21-webpki-roots",
|
||||
feature = "rustls-0_21-native-roots",
|
||||
))]
|
||||
pub mod rustls_0_21;
|
||||
|
||||
/// 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)
|
||||
}
|
||||
#[cfg(feature = "rustls-0_22")]
|
||||
pub mod rustls_0_22;
|
||||
|
||||
/// 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)
|
||||
}
|
||||
#[cfg(feature = "rustls-0_23")]
|
||||
pub mod rustls_0_23;
|
||||
|
||||
#[cfg(feature = "native-tls")]
|
||||
pub mod native_tls;
|
||||
|
||||
pub use self::{
|
||||
connection::Connection,
|
||||
connector::{Connector, ConnectorService},
|
||||
error::ConnectError,
|
||||
host::Host,
|
||||
info::ConnectInfo,
|
||||
resolve::Resolve,
|
||||
resolver::{Resolver, ResolverService},
|
||||
};
|
||||
|
90
actix-tls/src/connect/native_tls.rs
Normal file
90
actix-tls/src/connect/native_tls.rs
Normal file
@ -0,0 +1,90 @@
|
||||
//! 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))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
151
actix-tls/src/connect/openssl.rs
Normal file
151
actix-tls/src/connect/openssl.rs
Normal file
@ -0,0 +1,151 @@
|
||||
//! 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
Executable file → Normal file
207
actix-tls/src/connect/resolve.rs
Executable file → Normal file
@ -1,61 +1,12 @@
|
||||
use std::{
|
||||
future::Future,
|
||||
io,
|
||||
net::SocketAddr,
|
||||
pin::Pin,
|
||||
rc::Rc,
|
||||
task::{Context, Poll},
|
||||
vec::IntoIter,
|
||||
};
|
||||
//! The [`Resolve`] trait.
|
||||
|
||||
use actix_rt::task::{spawn_blocking, JoinHandle};
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use futures_core::{future::LocalBoxFuture, ready};
|
||||
use log::trace;
|
||||
use std::{error::Error as StdError, net::SocketAddr};
|
||||
|
||||
use super::connect::{Address, Connect};
|
||||
use super::error::ConnectError;
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
|
||||
/// 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.
|
||||
/// Custom async DNS resolvers.
|
||||
///
|
||||
/// # Usage
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use std::net::SocketAddr;
|
||||
///
|
||||
@ -89,155 +40,23 @@ pub enum Resolver {
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let resolver = MyResolver {
|
||||
/// let my_resolver = MyResolver {
|
||||
/// trust_dns: TokioAsyncResolver::tokio_from_system_conf().unwrap(),
|
||||
/// };
|
||||
///
|
||||
/// // 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());
|
||||
/// // wrap custom resolver
|
||||
/// let resolver = Resolver::custom(my_resolver);
|
||||
///
|
||||
/// // resolver can be passed to connector factory where returned service factory
|
||||
/// // can be used to construct new connector services.
|
||||
/// let factory = actix_tls::connect::new_connector_factory::<&str>(resolver);
|
||||
/// // can be used to construct new connector services for use in clients
|
||||
/// let factory = actix_tls::connect::Connector::new(resolver);
|
||||
/// let connector = factory.service();
|
||||
/// ```
|
||||
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 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),
|
||||
}
|
||||
}
|
||||
) -> LocalBoxFuture<'a, Result<Vec<SocketAddr>, Box<dyn StdError>>>;
|
||||
}
|
||||
|
201
actix-tls/src/connect/resolver.rs
Normal file
201
actix-tls/src/connect/resolver.rs
Normal file
@ -0,0 +1,201 @@
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
179
actix-tls/src/connect/rustls_0_20.rs
Normal file
179
actix-tls/src/connect/rustls_0_20.rs
Normal file
@ -0,0 +1,179 @@
|
||||
//! Rustls based connector service.
|
||||
//!
|
||||
//! See [`TlsConnector`] for main connector service factory docs.
|
||||
|
||||
use std::{
|
||||
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, RootCertStore},
|
||||
Connect as RustlsConnect, TlsConnector as RustlsTlsConnector,
|
||||
};
|
||||
use tokio_rustls_023 as tokio_rustls;
|
||||
|
||||
use crate::connect::{Connection, Host};
|
||||
|
||||
pub mod reexports {
|
||||
//! Re-exports from the `rustls` v0.20 ecosystem that are useful for connectors.
|
||||
|
||||
pub use tokio_rustls_023::{client::TlsStream as AsyncTlsStream, rustls::ClientConfig};
|
||||
#[cfg(feature = "rustls-0_20-webpki-roots")]
|
||||
pub use webpki_roots_022::TLS_SERVER_ROOTS;
|
||||
}
|
||||
|
||||
/// Returns root certificates via `rustls-native-certs` crate as a rustls certificate store.
|
||||
///
|
||||
/// See [`rustls_native_certs::load_native_certs()`] for more info on behavior and errors.
|
||||
///
|
||||
/// [`rustls_native_certs::load_native_certs()`]: rustls_native_certs_06::load_native_certs()
|
||||
#[cfg(feature = "rustls-0_20-native-roots")]
|
||||
pub fn native_roots_cert_store() -> io::Result<RootCertStore> {
|
||||
let mut root_certs = RootCertStore::empty();
|
||||
|
||||
for cert in rustls_native_certs_06::load_native_certs()? {
|
||||
root_certs
|
||||
.add(&tokio_rustls_023::rustls::Certificate(cert.0))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
Ok(root_certs)
|
||||
}
|
||||
|
||||
/// Returns standard root certificates from `webpki-roots` crate as a rustls certificate store.
|
||||
#[cfg(feature = "rustls-0_20-webpki-roots")]
|
||||
pub fn webpki_roots_cert_store() -> RootCertStore {
|
||||
use tokio_rustls_023::rustls;
|
||||
|
||||
let mut root_certs = RootCertStore::empty();
|
||||
|
||||
for cert in webpki_roots_022::TLS_SERVER_ROOTS.0 {
|
||||
let cert = rustls::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 {
|
||||
tracing::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(Arc::clone(&self.connector))
|
||||
.connect(host, stream),
|
||||
connection: Some(connection),
|
||||
},
|
||||
Err(_) => ConnectFut::InvalidDns,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Connect future for Rustls service.
|
||||
#[doc(hidden)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
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 = io::Result<Connection<R, AsyncTlsStream<IO>>>;
|
||||
|
||||
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 v0.20 can only handle hostname-based connections. Enable the `rustls-0_21` \
|
||||
feature and use the Rustls v0.21 utilities to gain this feature.",
|
||||
))),
|
||||
|
||||
Self::Future {
|
||||
connect,
|
||||
connection,
|
||||
} => {
|
||||
let stream = ready!(Pin::new(connect).poll(cx))?;
|
||||
let connection = connection.take().unwrap();
|
||||
tracing::trace!("TLS handshake success: {:?}", connection.hostname());
|
||||
Poll::Ready(Ok(connection.replace_io(stream).1))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user