mirror of
https://github.com/fafhrd91/actix-net
synced 2025-04-06 23:08:52 +02:00
Compare commits
480 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
f8bea79547 | ||
|
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 | ||
|
2080f4c149 | ||
|
b2cef8fcdb | ||
|
15279eaf3d | ||
|
7d98247cb0 | ||
|
5b537c7b10 | ||
|
81d7295486 | ||
|
581e599209 | ||
|
1c8fcaebbc | ||
|
a1d15f2e08 | ||
|
70ea5322ab | ||
|
303666278a | ||
|
669e868370 | ||
|
47f278b17a | ||
|
ca77d8d835 | ||
|
00775884f8 | ||
|
4ff8a2cf68 | ||
|
5c555a9408 | ||
|
ca435b2575 | ||
|
9fa8d7fc5a | ||
|
b03fe7c5b6 | ||
|
6fed1c3e7d | ||
|
c3d697df97 | ||
|
80a362712f | ||
|
2b1edb95ea | ||
|
4644fa41cf | ||
|
98c37fe47d | ||
|
b9455d2ca9 | ||
|
0183b0f8cc | ||
|
b122a1ae1a | ||
|
4303058243 | ||
|
48b2e11509 | ||
|
5379a46a99 | ||
|
f8f1ac94bc | ||
|
82cd5b8290 | ||
|
c65e8524b2 | ||
|
a83dfaa162 | ||
|
e4ec956001 | ||
|
95cba659ff | ||
|
5687e81d9f | ||
|
a0fe2a9b2e | ||
|
ad22a93466 | ||
|
c2d5b2398a | ||
|
5b1ff30dd9 | ||
|
e1317bb3a0 | ||
|
dcea009158 | ||
|
13c18b8a51 | ||
|
06b17d6a43 | ||
|
605ec25143 | ||
|
3824493fd3 | ||
|
3be3e11aa5 | ||
|
6a5ea0342b | ||
|
23b1f63345 | ||
|
3aa037d07d | ||
|
cf21df14f2 | ||
|
a1bf8662c9 | ||
|
6f4d2220fa | ||
|
54b22f9fce | ||
|
983abec77d | ||
|
e4d4ae21ee | ||
|
8ad5f58d38 | ||
|
613b2be51f | ||
|
b2e9640952 | ||
|
76338a5822 | ||
|
978e4f25fb | ||
|
1c4e965366 | ||
|
2435520e67 | ||
|
19468feef8 | ||
|
bd48908792 | ||
|
20c2da17ed | ||
|
fdafc1dd65 | ||
|
7749dfe46a | ||
|
aeb81ad3fd | ||
|
47fba25d67 | ||
|
7a82288066 | ||
|
4e6d88d143 | ||
|
ef206f40fb | ||
|
8e98d9168c | ||
|
3c1f57706a | ||
|
d49ecf7203 | ||
|
e0fb67f646 | ||
|
ddce2d6d12 | ||
|
0a11cf5cba | ||
|
859f45868d | ||
|
d4829b046d | ||
|
5961eb892e | ||
|
995efcf427 | ||
|
f1573931dd | ||
|
3859e91799 | ||
|
8aade720ed | ||
|
8079c50ddb | ||
|
05689b86d9 | ||
|
fd3e5fba02 | ||
|
39d1f282f7 | ||
|
d8889c63ef | ||
|
fdac52aa11 | ||
|
6d66cfb06a | ||
|
fb27ffc525 | ||
|
b068ea16f8 | ||
|
4eebdf4070 | ||
|
b09e7cd417 | ||
|
2c5c9167a5 | ||
|
ee3a548a85 | ||
|
f21eaa954f | ||
|
8becb0db70 | ||
|
26a5af70cb | ||
|
0ee8d032b6 | ||
|
3cf1c548fd | ||
|
4544562e1b | ||
|
bb27bac216 | ||
|
f9262dbec0 | ||
|
12d3942b98 | ||
|
a3c9ebc7fa | ||
|
b7bfff2b32 | ||
|
0c73f13c8b | ||
|
945479e0c3 | ||
|
746cc2ab89 | ||
|
91ea8c5dad | ||
|
0a705b1023 | ||
|
9e2bcec226 | ||
|
382830a37e | ||
|
493a1a32c0 | ||
|
50a195e9ce | ||
|
06ddad0051 | ||
|
789e6a8a46 | ||
|
6e590fd042 | ||
|
fa8ded3a34 | ||
|
841c611233 | ||
|
81a2b6a425 | ||
|
a6e79453d0 | ||
|
17f711a9d6 | ||
|
c3be839a69 | ||
|
8d74cf387d | ||
|
7e483cc356 | ||
|
75d7ae3139 | ||
|
2cfe1d88ad |
15
.cargo/config.toml
Normal file
15
.cargo/config.toml
Normal file
@ -0,0 +1,15 @@
|
||||
[alias]
|
||||
lint = "clippy --workspace --tests --examples --bins -- -Dclippy::todo"
|
||||
lint-all = "clippy --workspace --all-features --tests --examples --bins -- -Dclippy::todo"
|
||||
|
||||
# just check the library (without dev deps)
|
||||
ci-check-min = "hack --workspace check --no-default-features"
|
||||
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 --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 --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
|
133
.github/workflows/ci.yml
vendored
Normal file
133
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,133 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
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 }
|
||||
version:
|
||||
- { name: msrv, version: "${{ needs.read_msrv.outputs.msrv }}" }
|
||||
- { name: stable, version: stable }
|
||||
|
||||
name: ${{ matrix.target.name }} / ${{ matrix.version.name }}
|
||||
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.name }})
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
|
||||
with:
|
||||
toolchain: ${{ matrix.version.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: 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'
|
||||
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
|
||||
|
||||
- name: CI cache clean
|
||||
run: cargo-ci-cache-clean
|
||||
|
||||
docs:
|
||||
name: Documentation
|
||||
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 just
|
||||
uses: taiki-e/install-action@v2.49.40
|
||||
with:
|
||||
tool: just
|
||||
|
||||
- name: doc tests
|
||||
run: just test-docs
|
34
.github/workflows/clippy-fmt.yml
vendored
34
.github/workflows/clippy-fmt.yml
vendored
@ -1,34 +0,0 @@
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
name: Clippy and rustfmt Check
|
||||
jobs:
|
||||
clippy_check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
components: rustfmt
|
||||
profile: minimal
|
||||
override: true
|
||||
- name: Check with rustfmt
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: clippy
|
||||
profile: minimal
|
||||
override: true
|
||||
- name: Check with Clippy
|
||||
uses: actions-rs/clippy-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --workspace --tests
|
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 }}
|
82
.github/workflows/linux.yml
vendored
82
.github/workflows/linux.yml
vendored
@ -1,82 +0,0 @@
|
||||
name: CI (Linux)
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- '1.0'
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
version:
|
||||
- 1.46.0
|
||||
- stable
|
||||
- nightly
|
||||
|
||||
name: ${{ matrix.version }} - x86_64-unknown-linux-gnu
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install ${{ matrix.version }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.version }}-x86_64-unknown-linux-gnu
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Generate Cargo.lock
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: generate-lockfile
|
||||
- name: Cache cargo dirs
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path:
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
~/.cargo/bin
|
||||
key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-cargo-trimmed-${{ hashFiles('**/Cargo.lock') }}
|
||||
- name: Cache cargo build
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: target
|
||||
key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-cargo-build-trimmed-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
- name: check build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --workspace --bins --examples --tests
|
||||
|
||||
- name: tests
|
||||
uses: actions-rs/cargo@v1
|
||||
timeout-minutes: 40
|
||||
with:
|
||||
command: test
|
||||
args: --workspace --exclude=actix-tls --no-fail-fast -- --nocapture
|
||||
|
||||
- name: Generate coverage file
|
||||
if: matrix.version == 'stable' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request')
|
||||
run: |
|
||||
cargo install cargo-tarpaulin
|
||||
cargo tarpaulin --out Xml --workspace
|
||||
|
||||
- name: Upload to Codecov
|
||||
if: matrix.version == 'stable' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request')
|
||||
uses: codecov/codecov-action@v1
|
||||
with:
|
||||
file: cobertura.xml
|
||||
|
||||
- name: Clear the cargo caches
|
||||
run: |
|
||||
rustup update stable
|
||||
rustup override set stable
|
||||
cargo install cargo-cache --no-default-features --features ci-autoclean
|
||||
cargo-cache
|
43
.github/workflows/macos.yml
vendored
43
.github/workflows/macos.yml
vendored
@ -1,43 +0,0 @@
|
||||
name: CI (macOS)
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- '1.0'
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
version:
|
||||
- stable
|
||||
- nightly
|
||||
|
||||
name: ${{ matrix.version }} - x86_64-apple-darwin
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install ${{ matrix.version }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.version }}-x86_64-apple-darwin
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: check build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --workspace --bins --examples --tests
|
||||
|
||||
- name: tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --workspace --exclude=actix-tls --no-fail-fast -- --nocapture
|
45
.github/workflows/windows-mingw.yml
vendored
45
.github/workflows/windows-mingw.yml
vendored
@ -1,45 +0,0 @@
|
||||
name: CI (Windows-mingw)
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- '1.0'
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
version:
|
||||
- stable
|
||||
- nightly
|
||||
|
||||
name: ${{ matrix.version }} - x86_64-pc-windows-gnu
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install ${{ matrix.version }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.version }}-x86_64-pc-windows-gnu
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Install MSYS2
|
||||
uses: msys2/setup-msys2@v2
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
msys2 -c 'pacman -Sy --noconfirm pacman'
|
||||
msys2 -c 'pacman --noconfirm -S base-devel pkg-config'
|
||||
|
||||
- name: check build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --workspace --bins --examples --tests
|
69
.github/workflows/windows.yml
vendored
69
.github/workflows/windows.yml
vendored
@ -1,69 +0,0 @@
|
||||
name: CI (Windows)
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- '1.0'
|
||||
|
||||
env:
|
||||
VCPKGRS_DYNAMIC: 1
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
version:
|
||||
- stable
|
||||
- nightly
|
||||
target:
|
||||
- x86_64-pc-windows-msvc
|
||||
- i686-pc-windows-msvc
|
||||
|
||||
name: ${{ matrix.version }} - ${{ matrix.target }}
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install ${{ matrix.version }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.version }}-${{ matrix.target }}
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Install OpenSSL (x64)
|
||||
if: matrix.target == 'x86_64-pc-windows-msvc'
|
||||
run: |
|
||||
vcpkg integrate install
|
||||
vcpkg install openssl:x64-windows
|
||||
Get-ChildItem C:\vcpkg\installed\x64-windows\bin
|
||||
Get-ChildItem C:\vcpkg\installed\x64-windows\lib
|
||||
Copy-Item C:\vcpkg\installed\x64-windows\bin\libcrypto-1_1-x64.dll C:\vcpkg\installed\x64-windows\bin\libcrypto.dll
|
||||
Copy-Item C:\vcpkg\installed\x64-windows\bin\libssl-1_1-x64.dll C:\vcpkg\installed\x64-windows\bin\libssl.dll
|
||||
|
||||
- name: Install OpenSSL (x86)
|
||||
if: matrix.target == 'i686-pc-windows-msvc'
|
||||
run: |
|
||||
vcpkg integrate install
|
||||
vcpkg install openssl:x86-windows
|
||||
Get-ChildItem C:\vcpkg\installed\x86-windows\bin
|
||||
Get-ChildItem C:\vcpkg\installed\x86-windows\lib
|
||||
Copy-Item C:\vcpkg\installed\x86-windows\bin\libcrypto-1_1.dll C:\vcpkg\installed\x86-windows\bin\libcrypto.dll
|
||||
Copy-Item C:\vcpkg\installed\x86-windows\bin\libssl-1_1.dll C:\vcpkg\installed\x86-windows\bin\libssl.dll
|
||||
|
||||
- name: check build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --workspace --bins --examples --tests
|
||||
|
||||
- name: tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --workspace --exclude=actix-tls --no-fail-fast -- --nocapture
|
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
23
Cargo.toml
23
Cargo.toml
@ -1,8 +1,8 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"actix-codec",
|
||||
"actix-macros",
|
||||
"actix-router",
|
||||
"actix-rt",
|
||||
"actix-server",
|
||||
"actix-service",
|
||||
@ -10,12 +10,18 @@ members = [
|
||||
"actix-tracing",
|
||||
"actix-utils",
|
||||
"bytestring",
|
||||
"local-channel",
|
||||
"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" }
|
||||
actix-router = { path = "actix-router" }
|
||||
actix-rt = { path = "actix-rt" }
|
||||
actix-server = { path = "actix-server" }
|
||||
actix-service = { path = "actix-service" }
|
||||
@ -23,3 +29,16 @@ actix-tls = { path = "actix-tls" }
|
||||
actix-tracing = { path = "actix-tracing" }
|
||||
actix-utils = { path = "actix-utils" }
|
||||
bytestring = { path = "bytestring" }
|
||||
local-channel = { path = "local-channel" }
|
||||
local-waker = { path = "local-waker" }
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
opt-level = 3
|
||||
codegen-units = 1
|
||||
|
||||
[workspace.lints.rust]
|
||||
rust_2018_idioms = "deny"
|
||||
nonstandard-style = "deny"
|
||||
future_incompatible = "deny"
|
||||
missing_docs = { level = "warn", priority = -1 }
|
||||
|
@ -186,7 +186,7 @@
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2017-NOW Nikolay Kim
|
||||
Copyright 2017-NOW Actix Team
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -1,4 +1,4 @@
|
||||
Copyright (c) 2017 Nikolay Kim
|
||||
Copyright (c) 2017-NOW Actix Team
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
|
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,59 +1,85 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
## Unreleased
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.71.
|
||||
|
||||
## 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]
|
||||
## 0.5.2
|
||||
|
||||
[#237]: https://github.com/actix/actix-net/pull/237
|
||||
- Minimum supported Rust version (MSRV) is now 1.65.
|
||||
|
||||
## 0.5.1
|
||||
|
||||
## 0.3.0 - 2020-08-23
|
||||
* No changes from beta 2.
|
||||
- Logs emitted now use the `tracing` crate with `log` compatibility.
|
||||
- Minimum supported Rust version (MSRV) is now 1.49.
|
||||
|
||||
## 0.5.0
|
||||
|
||||
## 0.3.0-beta.2 - 2020-08-19
|
||||
* Remove unused type parameter from `Framed::replace_codec`.
|
||||
- Updated `tokio-util` dependency to `0.7.0`.
|
||||
|
||||
## 0.4.2
|
||||
|
||||
## 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.
|
||||
- No significant changes since `0.4.1`.
|
||||
|
||||
## 0.4.1
|
||||
|
||||
## 0.2.0 - 2019-12-10
|
||||
* Use specific futures dependencies
|
||||
- Added `LinesCodec`.
|
||||
- `Framed::poll_ready` flushes when the buffer is full.
|
||||
|
||||
## 0.4.0
|
||||
|
||||
- No significant changes since v0.4.0-beta.1.
|
||||
|
||||
## 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,26 +1,36 @@
|
||||
[package]
|
||||
name = "actix-codec"
|
||||
version = "0.4.0-beta.1"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.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"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-net.git"
|
||||
documentation = "https://docs.rs/actix-codec"
|
||||
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"
|
||||
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.5", features = ["html_reports"] }
|
||||
tokio-test = "0.4.2"
|
||||
|
||||
[[bench]]
|
||||
name = "lines"
|
||||
harness = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
59
actix-codec/benches/lines.rs
Normal file
59
actix-codec/benches/lines.rs
Normal file
@ -0,0 +1,59 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use bytes::BytesMut;
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
const INPUT: &[u8] = include_bytes!("./lorem.txt");
|
||||
|
||||
fn bench_lines_codec(c: &mut Criterion) {
|
||||
let mut decode_group = c.benchmark_group("lines decode");
|
||||
|
||||
decode_group.bench_function("actix", |b| {
|
||||
b.iter(|| {
|
||||
use actix_codec::Decoder as _;
|
||||
|
||||
let mut codec = actix_codec::LinesCodec::default();
|
||||
let mut buf = BytesMut::from(INPUT);
|
||||
while let Ok(Some(_bytes)) = codec.decode_eof(&mut buf) {}
|
||||
});
|
||||
});
|
||||
|
||||
decode_group.bench_function("tokio", |b| {
|
||||
b.iter(|| {
|
||||
use tokio_util::codec::Decoder as _;
|
||||
|
||||
let mut codec = tokio_util::codec::LinesCodec::new();
|
||||
let mut buf = BytesMut::from(INPUT);
|
||||
while let Ok(Some(_bytes)) = codec.decode_eof(&mut buf) {}
|
||||
});
|
||||
});
|
||||
|
||||
decode_group.finish();
|
||||
|
||||
let mut encode_group = c.benchmark_group("lines encode");
|
||||
|
||||
encode_group.bench_function("actix", |b| {
|
||||
b.iter(|| {
|
||||
use actix_codec::Encoder as _;
|
||||
|
||||
let mut codec = actix_codec::LinesCodec::default();
|
||||
let mut buf = BytesMut::new();
|
||||
codec.encode("123", &mut buf).unwrap();
|
||||
});
|
||||
});
|
||||
|
||||
encode_group.bench_function("tokio", |b| {
|
||||
b.iter(|| {
|
||||
use tokio_util::codec::Encoder as _;
|
||||
|
||||
let mut codec = tokio_util::codec::LinesCodec::new();
|
||||
let mut buf = BytesMut::new();
|
||||
codec.encode("123", &mut buf).unwrap();
|
||||
});
|
||||
});
|
||||
|
||||
encode_group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(benches, bench_lines_codec);
|
||||
criterion_main!(benches);
|
5
actix-codec/benches/lorem.txt
Normal file
5
actix-codec/benches/lorem.txt
Normal file
@ -0,0 +1,5 @@
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In tortor quam, pulvinar sit amet vestibulum eget, tincidunt non urna. Sed eu sem in felis malesuada venenatis. Suspendisse volutpat aliquet nisi, in condimentum nibh convallis id. Quisque gravida felis scelerisque ipsum aliquam consequat. Praesent libero odio, malesuada vitae odio quis, aliquam aliquet enim. In fringilla ut turpis nec pharetra. Duis eu posuere metus. Sed a aliquet massa. Mauris non tempus mi, quis mattis libero. Vivamus ornare ex at semper cursus. Vestibulum sed facilisis erat, aliquet mollis est. In interdum, magna iaculis ultricies elementum, mi ante vestibulum mauris, nec viverra turpis lorem quis ante. Proin in auctor erat. Vivamus dictum congue massa, fermentum bibendum leo pretium quis. Integer dapibus sodales ligula, sit amet imperdiet felis suscipit eu. Phasellus non ornare enim.
|
||||
Nam feugiat neque sit amet hendrerit rhoncus. Nunc suscipit molestie vehicula. Aenean vulputate porttitor augue, sit amet molestie dolor volutpat vitae. Nulla vitae condimentum eros. Aliquam tristique purus at metus lacinia egestas. Cras euismod lorem eu orci lobortis, sed tincidunt nisl laoreet. Ut suscipit fermentum mi, et euismod tortor. Pellentesque vitae tempor quam, sed dignissim mi. Suspendisse luctus lacus vitae ligula blandit vehicula. Quisque interdum iaculis tincidunt. Nunc elementum mi vitae tempor placerat. Suspendisse potenti. Donec blandit laoreet ipsum, quis rhoncus velit vulputate sed.
|
||||
Aliquam suscipit lectus eros, at maximus dolor efficitur quis. Integer blandit tortor orci, nec mattis nunc eleifend ac. Mauris pharetra vel quam quis lacinia. Duis lobortis condimentum nunc ut facilisis. Praesent arcu nisi, porta sit amet viverra sit amet, pellentesque ut nisi. Nunc gravida tortor eu ligula tempus, in interdum magna pretium. Fusce eu ornare sapien. Nullam pellentesque cursus eros. Nam orci massa, faucibus eget leo eget, elementum vulputate erat. Fusce vehicula augue et dui hendrerit vulputate. Mauris neque lacus, porttitor ut condimentum id, efficitur ac neque. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Donec accumsan, lectus fermentum elementum tristique, ipsum tortor mollis ante, non lacinia nibh ex quis sapien.
|
||||
Donec pharetra, elit eget rutrum luctus, urna ligula facilisis lorem, sit amet rhoncus ante est eu mi. Vestibulum vestibulum ultricies interdum. Nulla tincidunt ante non hendrerit venenatis. Curabitur vestibulum turpis erat, id efficitur quam venenatis eu. Fusce nulla sem, dapibus vel quam feugiat, ornare fermentum ligula. Praesent tempus tincidunt mauris, non pellentesque felis varius in. Aenean eu arcu ligula. Morbi dapibus maximus nulla a pharetra. Fusce leo metus, luctus ut cursus non, sollicitudin non lectus. Integer pellentesque eleifend erat, vel gravida purus tempus a. Mauris id vestibulum quam. Nunc vitae ullamcorper metus, pharetra placerat enim. Fusce in ultrices nisl. Curabitur justo mauris, dignissim in aliquam sit amet, sollicitudin ut risus. Cras tempor rutrum justo, non tincidunt est maximus at.
|
||||
Aliquam ac velit tincidunt, ullamcorper velit sit amet, pulvinar nisi. Nullam rhoncus rhoncus egestas. Cras ac luctus nisi. Mauris sit amet risus at magna volutpat ultrices quis ac dui. Aliquam condimentum tellus purus, vel sagittis odio vulputate at. Sed ut finibus tellus. Aliquam tincidunt vehicula diam.
|
@ -1,11 +1,10 @@
|
||||
use bytes::{Buf, Bytes, BytesMut};
|
||||
use std::io;
|
||||
|
||||
use bytes::{Buf, Bytes, BytesMut};
|
||||
|
||||
use super::{Decoder, Encoder};
|
||||
|
||||
/// Bytes codec.
|
||||
///
|
||||
/// Reads/Writes chunks of bytes from a stream.
|
||||
/// Bytes codec. Reads/writes chunks of bytes from a stream.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct BytesCodec;
|
||||
|
||||
|
@ -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,22 +17,22 @@ 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! {
|
||||
/// A unified `Stream` and `Sink` interface to an underlying I/O object, using
|
||||
/// the `Encoder` and `Decoder` traits to encode and decode frames.
|
||||
pin_project! {
|
||||
/// A unified `Stream` and `Sink` interface to an underlying I/O object, using the `Encoder` and
|
||||
/// `Decoder` traits to encode and decode frames.
|
||||
///
|
||||
/// Raw I/O objects work with byte sequences, but higher-level code usually
|
||||
/// wants to batch these into meaningful chunks, called "frames". This
|
||||
/// method layers framing on top of an I/O object, by using the `Encoder`/`Decoder`
|
||||
/// traits to handle encoding and decoding of message frames. Note that
|
||||
/// the incoming and outgoing frame types may be distinct.
|
||||
/// Raw I/O objects work with byte sequences, but higher-level code usually wants to batch these
|
||||
/// into meaningful chunks, called "frames". This method layers framing on top of an I/O object,
|
||||
/// by using the `Encoder`/`Decoder` traits to handle encoding and decoding of message frames.
|
||||
/// Note that the incoming and outgoing frame types may be distinct.
|
||||
pub struct Framed<T, U> {
|
||||
#[pin]
|
||||
io: T,
|
||||
@ -44,10 +48,9 @@ where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
U: Decoder,
|
||||
{
|
||||
/// This function returns a *single* object that is both `Stream` and
|
||||
/// `Sink`; grouping this into a single object is often useful for layering
|
||||
/// things like gzip or TLS, which require both read and write access to the
|
||||
/// underlying object.
|
||||
/// This function returns a *single* object that is both `Stream` and `Sink`; grouping this into
|
||||
/// a single object is often useful for layering things like gzip or TLS, which require both
|
||||
/// read and write access to the underlying object.
|
||||
pub fn new(io: T, codec: U) -> Framed<T, U> {
|
||||
Framed {
|
||||
io,
|
||||
@ -70,21 +73,18 @@ impl<T, U> Framed<T, U> {
|
||||
&mut self.codec
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying I/O stream wrapped by
|
||||
/// `Frame`.
|
||||
/// Returns a reference to the underlying I/O stream wrapped by `Frame`.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying stream
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
/// Note that care should be taken to not tamper with the underlying stream of data coming in as
|
||||
/// it may corrupt the stream of frames otherwise being worked with.
|
||||
pub fn io_ref(&self) -> &T {
|
||||
&self.io
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying I/O stream.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying stream
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
/// Note that care should be taken to not tamper with the underlying stream of data coming in as
|
||||
/// it may corrupt the stream of frames otherwise being worked with.
|
||||
pub fn io_mut(&mut self) -> &mut T {
|
||||
&mut self.io
|
||||
}
|
||||
@ -157,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,
|
||||
@ -183,30 +183,29 @@ impl<T, U> Framed<T, U> {
|
||||
U: Decoder,
|
||||
{
|
||||
loop {
|
||||
let mut this = self.as_mut().project();
|
||||
// Repeatedly call `decode` or `decode_eof` as long as it is
|
||||
// "readable". Readable is defined as not having returned `None`. If
|
||||
// the upstream has returned EOF, and the decoder is no longer
|
||||
// readable, it can be assumed that the decoder will never become
|
||||
let this = self.as_mut().project();
|
||||
// Repeatedly call `decode` or `decode_eof` as long as it is "readable". Readable is
|
||||
// defined as not having returned `None`. If the upstream has returned EOF, and the
|
||||
// decoder is no longer readable, it can be assumed that the decoder will never become
|
||||
// readable again, at which point the stream is terminated.
|
||||
|
||||
if this.flags.contains(Flags::READABLE) {
|
||||
if this.flags.contains(Flags::EOF) {
|
||||
match this.codec.decode_eof(&mut this.read_buf) {
|
||||
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(&mut this.read_buf) {
|
||||
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
|
||||
}
|
||||
|
||||
@ -215,7 +214,7 @@ impl<T, U> Framed<T, U> {
|
||||
|
||||
debug_assert!(!this.flags.contains(Flags::EOF));
|
||||
|
||||
// Otherwise, try to read more data and try again. Make sure we've got room
|
||||
// Otherwise, try to read more data and try again. Make sure we've got room.
|
||||
let remaining = this.read_buf.capacity() - this.read_buf.len();
|
||||
if remaining < LW {
|
||||
this.read_buf.reserve(HW - remaining)
|
||||
@ -223,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,
|
||||
};
|
||||
|
||||
@ -235,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))?;
|
||||
|
||||
@ -266,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>,
|
||||
@ -306,11 +299,11 @@ where
|
||||
{
|
||||
type Error = U::Error;
|
||||
|
||||
fn poll_ready(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
if self.is_write_ready() {
|
||||
Poll::Ready(Ok(()))
|
||||
} else {
|
||||
Poll::Pending
|
||||
self.flush(cx)
|
||||
}
|
||||
}
|
||||
|
||||
@ -341,13 +334,12 @@ where
|
||||
}
|
||||
|
||||
impl<T, U> Framed<T, U> {
|
||||
/// This function returns a *single* object that is both `Stream` and
|
||||
/// `Sink`; grouping this into a single object is often useful for layering
|
||||
/// things like gzip or TLS, which require both read and write access to the
|
||||
/// underlying object.
|
||||
/// This function returns a *single* object that is both `Stream` and `Sink`; grouping this into
|
||||
/// a single object is often useful for layering things like gzip or TLS, which require both
|
||||
/// read and write access to the underlying object.
|
||||
///
|
||||
/// These objects take a stream, a read buffer and a write buffer. These
|
||||
/// fields can be obtained from an existing `Framed` with the `into_parts` method.
|
||||
/// These objects take a stream, a read buffer and a write buffer. These fields can be obtained
|
||||
/// from an existing `Framed` with the `into_parts` method.
|
||||
pub fn from_parts(parts: FramedParts<T, U>) -> Framed<T, U> {
|
||||
Framed {
|
||||
io: parts.io,
|
||||
@ -358,12 +350,11 @@ impl<T, U> Framed<T, U> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes the `Frame`, returning its underlying I/O stream, the buffer
|
||||
/// with unprocessed data, and the codec.
|
||||
/// Consumes the `Frame`, returning its underlying I/O stream, the buffer with unprocessed data,
|
||||
/// and the codec.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying stream
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
/// Note that care should be taken to not tamper with the underlying stream of data coming in as
|
||||
/// it may corrupt the stream of frames otherwise being worked with.
|
||||
pub fn into_parts(self) -> FramedParts<T, U> {
|
||||
FramedParts {
|
||||
io: self.io,
|
||||
@ -376,14 +367,15 @@ impl<T, U> Framed<T, U> {
|
||||
}
|
||||
|
||||
/// `FramedParts` contains an export of the data of a Framed transport.
|
||||
/// It can be used to construct a new `Framed` with a different codec.
|
||||
/// It contains all current buffers and the inner transport.
|
||||
///
|
||||
/// It can be used to construct a new `Framed` with a different codec. It contains all current
|
||||
/// buffers and the inner transport.
|
||||
#[derive(Debug)]
|
||||
pub struct FramedParts<T, U> {
|
||||
/// The inner transport used to read bytes to and write bytes to
|
||||
/// The inner transport used to read bytes to and write bytes to.
|
||||
pub io: T,
|
||||
|
||||
/// The codec
|
||||
/// The codec object.
|
||||
pub codec: U,
|
||||
|
||||
/// The buffer with read but unprocessed data.
|
||||
@ -396,7 +388,7 @@ pub struct FramedParts<T, U> {
|
||||
}
|
||||
|
||||
impl<T, U> FramedParts<T, U> {
|
||||
/// Create a new, default, `FramedParts`
|
||||
/// Creates a new default `FramedParts`.
|
||||
pub fn new(io: T, codec: U) -> FramedParts<T, U> {
|
||||
FramedParts {
|
||||
io,
|
||||
@ -407,7 +399,7 @@ impl<T, U> FramedParts<T, U> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `FramedParts` with read buffer
|
||||
/// Creates a new `FramedParts` with read buffer.
|
||||
pub fn with_read_buf(io: T, codec: U, read_buf: BytesMut) -> FramedParts<T, U> {
|
||||
FramedParts {
|
||||
io,
|
||||
|
@ -1,23 +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)]
|
||||
#![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 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,
|
||||
};
|
||||
|
158
actix-codec/src/lines.rs
Normal file
158
actix-codec/src/lines.rs
Normal file
@ -0,0 +1,158 @@
|
||||
use std::io;
|
||||
|
||||
use bytes::{Buf, BufMut, Bytes, BytesMut};
|
||||
use memchr::memchr;
|
||||
|
||||
use super::{Decoder, Encoder};
|
||||
|
||||
/// Lines codec. Reads/writes line delimited strings.
|
||||
///
|
||||
/// Will split input up by LF or CRLF delimiters. Carriage return characters at the end of lines are
|
||||
/// not preserved.
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
#[non_exhaustive]
|
||||
pub struct LinesCodec;
|
||||
|
||||
impl<T: AsRef<str>> Encoder<T> for LinesCodec {
|
||||
type Error = io::Error;
|
||||
|
||||
#[inline]
|
||||
fn encode(&mut self, item: T, dst: &mut BytesMut) -> Result<(), Self::Error> {
|
||||
let item = item.as_ref();
|
||||
dst.reserve(item.len() + 1);
|
||||
dst.put_slice(item.as_bytes());
|
||||
dst.put_u8(b'\n');
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Decoder for LinesCodec {
|
||||
type Item = String;
|
||||
type Error = io::Error;
|
||||
|
||||
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
||||
if src.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let len = match memchr(b'\n', src) {
|
||||
Some(n) => n,
|
||||
None => {
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
|
||||
// split up to new line char
|
||||
let mut buf = src.split_to(len);
|
||||
debug_assert_eq!(len, buf.len());
|
||||
|
||||
// remove new line char from source
|
||||
src.advance(1);
|
||||
|
||||
match buf.last() {
|
||||
// remove carriage returns at the end of buf
|
||||
Some(b'\r') => buf.truncate(len - 1),
|
||||
|
||||
// line is empty
|
||||
None => return Ok(Some(String::new())),
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
try_into_utf8(buf.freeze())
|
||||
}
|
||||
|
||||
fn decode_eof(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
||||
match self.decode(src)? {
|
||||
Some(frame) => Ok(Some(frame)),
|
||||
None if src.is_empty() => Ok(None),
|
||||
None => {
|
||||
let buf = match src.last() {
|
||||
// if last line ends in a CR then take everything up to it
|
||||
Some(b'\r') => src.split_to(src.len() - 1),
|
||||
|
||||
// take all bytes from source
|
||||
_ => src.split(),
|
||||
};
|
||||
|
||||
if buf.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
try_into_utf8(buf.freeze())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Attempts to convert bytes into a `String`.
|
||||
fn try_into_utf8(buf: Bytes) -> io::Result<Option<String>> {
|
||||
String::from_utf8(buf.to_vec())
|
||||
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))
|
||||
.map(Some)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bytes::BufMut as _;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn lines_decoder() {
|
||||
let mut codec = LinesCodec::default();
|
||||
let mut buf = BytesMut::from("\nline 1\nline 2\r\nline 3\n\r\n\r");
|
||||
|
||||
assert_eq!("", codec.decode(&mut buf).unwrap().unwrap());
|
||||
assert_eq!("line 1", codec.decode(&mut buf).unwrap().unwrap());
|
||||
assert_eq!("line 2", codec.decode(&mut buf).unwrap().unwrap());
|
||||
assert_eq!("line 3", codec.decode(&mut buf).unwrap().unwrap());
|
||||
assert_eq!("", codec.decode(&mut buf).unwrap().unwrap());
|
||||
assert!(codec.decode(&mut buf).unwrap().is_none());
|
||||
assert!(codec.decode_eof(&mut buf).unwrap().is_none());
|
||||
|
||||
buf.put_slice(b"k");
|
||||
assert!(codec.decode(&mut buf).unwrap().is_none());
|
||||
assert_eq!("\rk", codec.decode_eof(&mut buf).unwrap().unwrap());
|
||||
|
||||
assert!(codec.decode(&mut buf).unwrap().is_none());
|
||||
assert!(codec.decode_eof(&mut buf).unwrap().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lines_encoder() {
|
||||
let mut codec = LinesCodec::default();
|
||||
|
||||
let mut buf = BytesMut::new();
|
||||
|
||||
codec.encode("", &mut buf).unwrap();
|
||||
assert_eq!(&buf[..], b"\n");
|
||||
|
||||
codec.encode("test", &mut buf).unwrap();
|
||||
assert_eq!(&buf[..], b"\ntest\n");
|
||||
|
||||
codec.encode("a\nb", &mut buf).unwrap();
|
||||
assert_eq!(&buf[..], b"\ntest\na\nb\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lines_encoder_no_overflow() {
|
||||
let mut codec = LinesCodec::default();
|
||||
|
||||
let mut buf = BytesMut::new();
|
||||
codec.encode("1234567", &mut buf).unwrap();
|
||||
assert_eq!(&buf[..], b"1234567\n");
|
||||
|
||||
let mut buf = BytesMut::new();
|
||||
codec.encode("12345678", &mut buf).unwrap();
|
||||
assert_eq!(&buf[..], b"12345678\n");
|
||||
|
||||
let mut buf = BytesMut::new();
|
||||
codec.encode("123456789111213", &mut buf).unwrap();
|
||||
assert_eq!(&buf[..], b"123456789111213\n");
|
||||
|
||||
let mut buf = BytesMut::new();
|
||||
codec.encode("1234567891112131", &mut buf).unwrap();
|
||||
assert_eq!(&buf[..], b"1234567891112131\n");
|
||||
}
|
||||
}
|
224
actix-codec/tests/test_framed_sink.rs
Normal file
224
actix-codec/tests/test_framed_sink.rs
Normal file
@ -0,0 +1,224 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
io::{self, Write},
|
||||
pin::Pin,
|
||||
task::{
|
||||
Context,
|
||||
Poll::{self, Pending, Ready},
|
||||
},
|
||||
};
|
||||
|
||||
use actix_codec::*;
|
||||
use bytes::{Buf as _, BufMut as _, BytesMut};
|
||||
use futures_sink::Sink;
|
||||
use tokio_test::{assert_ready, task};
|
||||
|
||||
macro_rules! bilateral {
|
||||
($($x:expr,)*) => {{
|
||||
let mut v = VecDeque::new();
|
||||
v.extend(vec![$($x),*]);
|
||||
Bilateral { calls: v }
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! assert_ready {
|
||||
($e:expr) => {{
|
||||
use core::task::Poll::*;
|
||||
match $e {
|
||||
Ready(v) => v,
|
||||
Pending => panic!("pending"),
|
||||
}
|
||||
}};
|
||||
($e:expr, $($msg:tt),+) => {{
|
||||
use core::task::Poll::*;
|
||||
match $e {
|
||||
Ready(v) => v,
|
||||
Pending => {
|
||||
let msg = format_args!($($msg),+);
|
||||
panic!("pending; {}", msg)
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Bilateral {
|
||||
pub calls: VecDeque<io::Result<Vec<u8>>>,
|
||||
}
|
||||
|
||||
impl Write for Bilateral {
|
||||
fn write(&mut self, src: &[u8]) -> io::Result<usize> {
|
||||
match self.calls.pop_front() {
|
||||
Some(Ok(data)) => {
|
||||
assert!(src.len() >= data.len());
|
||||
assert_eq!(&data[..], &src[..data.len()]);
|
||||
Ok(data.len())
|
||||
}
|
||||
Some(Err(err)) => Err(err),
|
||||
None => panic!("unexpected write; {:?}", src),
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for Bilateral {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<Result<usize, io::Error>> {
|
||||
match Pin::get_mut(self).write(buf) {
|
||||
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 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>> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for Bilateral {
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
_: &mut Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<Result<(), std::io::Error>> {
|
||||
use io::ErrorKind::WouldBlock;
|
||||
|
||||
match self.calls.pop_front() {
|
||||
Some(Ok(data)) => {
|
||||
debug_assert!(buf.remaining() >= data.len());
|
||||
buf.put_slice(&data);
|
||||
Ready(Ok(()))
|
||||
}
|
||||
Some(Err(ref err)) if err.kind() == WouldBlock => Pending,
|
||||
Some(Err(err)) => Ready(Err(err)),
|
||||
None => Ready(Ok(())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct U32;
|
||||
|
||||
impl Encoder<u32> for U32 {
|
||||
type Error = io::Error;
|
||||
|
||||
fn encode(&mut self, item: u32, dst: &mut BytesMut) -> io::Result<()> {
|
||||
// Reserve space
|
||||
dst.reserve(4);
|
||||
dst.put_u32(item);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Decoder for U32 {
|
||||
type Item = u32;
|
||||
type Error = io::Error;
|
||||
|
||||
fn decode(&mut self, buf: &mut BytesMut) -> io::Result<Option<u32>> {
|
||||
if buf.len() < 4 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let n = buf.split_to(4).get_u32();
|
||||
Ok(Some(n))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_hits_highwater_mark() {
|
||||
// see here for what this test is based on:
|
||||
// https://github.com/tokio-rs/tokio/blob/75c07770bfbfea4e5fd914af819c741ed9c3fc36/tokio-util/tests/framed_write.rs#L69
|
||||
|
||||
const ITER: usize = 2 * 1024;
|
||||
|
||||
let mut bi = bilateral! {
|
||||
Err(io::Error::new(io::ErrorKind::WouldBlock, "not ready")),
|
||||
Ok(b"".to_vec()),
|
||||
};
|
||||
|
||||
for i in 0..=ITER {
|
||||
let mut b = BytesMut::with_capacity(4);
|
||||
b.put_u32(i as u32);
|
||||
|
||||
// Append to the end
|
||||
match bi.calls.back_mut().unwrap() {
|
||||
Ok(ref mut data) => {
|
||||
// Write in 2kb chunks
|
||||
if data.len() < ITER {
|
||||
data.extend_from_slice(&b[..]);
|
||||
continue;
|
||||
} // else fall through and create a new buffer
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
// Push a new new chunk
|
||||
bi.calls.push_back(Ok(b[..].to_vec()));
|
||||
}
|
||||
|
||||
assert_eq!(bi.calls.len(), 6);
|
||||
let mut framed = Framed::new(bi, U32);
|
||||
// Send 8KB. This fills up FramedWrite2 buffer
|
||||
let mut task = task::spawn(());
|
||||
task.enter(|cx, _| {
|
||||
// Send 8KB. This fills up Framed buffer
|
||||
for i in 0..ITER {
|
||||
{
|
||||
#[allow(unused_mut)]
|
||||
let mut framed = Pin::new(&mut framed);
|
||||
assert!(assert_ready!(framed.poll_ready(cx)).is_ok());
|
||||
}
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut framed = Pin::new(&mut framed);
|
||||
// write the buffer
|
||||
assert!(framed.start_send(i as u32).is_ok());
|
||||
}
|
||||
|
||||
{
|
||||
#[allow(unused_mut)]
|
||||
let mut framed = Pin::new(&mut framed);
|
||||
|
||||
// Now we poll_ready which forces a flush. The bilateral pops the front message
|
||||
// and decides to block.
|
||||
assert!(framed.poll_ready(cx).is_pending());
|
||||
}
|
||||
|
||||
{
|
||||
#[allow(unused_mut)]
|
||||
let mut framed = Pin::new(&mut framed);
|
||||
// We poll again, forcing another flush, which this time succeeds
|
||||
// The whole 8KB buffer is flushed
|
||||
assert!(assert_ready!(framed.poll_ready(cx)).is_ok());
|
||||
}
|
||||
|
||||
{
|
||||
#[allow(unused_mut)]
|
||||
let mut framed = Pin::new(&mut framed);
|
||||
// Send more data. This matches the final message expected by the bilateral
|
||||
assert!(framed.start_send(ITER as u32).is_ok());
|
||||
}
|
||||
|
||||
{
|
||||
#[allow(unused_mut)]
|
||||
let mut framed = Pin::new(&mut framed);
|
||||
// Flush the rest of the buffer
|
||||
assert!(assert_ready!(framed.poll_flush(cx)).is_ok());
|
||||
}
|
||||
|
||||
// Ensure the mock is empty
|
||||
assert_eq!(0, Pin::new(&framed).get_ref().io_ref().calls.len());
|
||||
});
|
||||
}
|
@ -1,27 +1,53 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
## Unreleased
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.71.
|
||||
|
||||
## 0.2.0 - 2021-02-02
|
||||
* Update to latest `actix_rt::System::new` signature. [#261]
|
||||
## 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
|
||||
|
||||
- Improve error recovery potential when macro input is invalid. [#391]
|
||||
- Allow custom `System`s on test macro. [#391]
|
||||
|
||||
[#391]: https://github.com/actix/actix-net/pull/391
|
||||
|
||||
## 0.2.1
|
||||
|
||||
- Add optional argument `system` to `main` macro which can be used to specify the path to `actix_rt::System` (useful for re-exports). [#363]
|
||||
|
||||
[#363]: https://github.com/actix/actix-net/pull/363
|
||||
|
||||
## 0.2.0
|
||||
|
||||
- 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,23 +1,39 @@
|
||||
[package]
|
||||
name = "actix-macros"
|
||||
version = "0.2.0"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
version = "0.2.4"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Ibraheem Ahmed <ibrah1440@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
]
|
||||
description = "Macros for Actix system and runtime"
|
||||
repository = "https://github.com/actix/actix-net.git"
|
||||
documentation = "https://docs.rs/actix-macros"
|
||||
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", default-features = false }
|
||||
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,11 +26,20 @@ 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(_: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let mut input = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let mut input = match syn::parse::<syn::ItemFn>(item.clone()) {
|
||||
Ok(input) => input,
|
||||
// on parse err, make IDEs happy; see fn docs
|
||||
Err(err) => return input_and_compile_error(item, err),
|
||||
};
|
||||
|
||||
let 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;
|
||||
let sig = &mut input.sig;
|
||||
@ -43,13 +54,52 @@ pub fn main(_: TokenStream, item: TokenStream) -> TokenStream {
|
||||
.into();
|
||||
}
|
||||
|
||||
let mut system = syn::parse_str::<syn::Path>("::actix_rt::System").unwrap();
|
||||
|
||||
for arg in &args {
|
||||
match arg {
|
||||
syn::Meta::NameValue(syn::MetaNameValue {
|
||||
path,
|
||||
value:
|
||||
syn::Expr::Lit(syn::ExprLit {
|
||||
lit: syn::Lit::Str(lit),
|
||||
..
|
||||
}),
|
||||
..
|
||||
}) => match path
|
||||
.get_ident()
|
||||
.map(|i| i.to_string().to_lowercase())
|
||||
.as_deref()
|
||||
{
|
||||
Some("system") => match lit.parse() {
|
||||
Ok(path) => system = path,
|
||||
Err(_) => {
|
||||
return syn::Error::new_spanned(lit, "Expected path")
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
return syn::Error::new_spanned(arg, "Unknown attribute specified")
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
},
|
||||
|
||||
_ => {
|
||||
return syn::Error::new_spanned(arg, "Unknown attribute specified")
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sig.asyncness = None;
|
||||
|
||||
(quote! {
|
||||
#(#attrs)*
|
||||
#vis #sig {
|
||||
actix_rt::System::new()
|
||||
.block_on(async move { #body })
|
||||
<#system>::new().block_on(async move { #body })
|
||||
}
|
||||
})
|
||||
.into()
|
||||
@ -65,8 +115,19 @@ pub fn main(_: TokenStream, item: TokenStream) -> TokenStream {
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn test(_: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let mut input = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let mut input = match syn::parse::<syn::ItemFn>(item.clone()) {
|
||||
Ok(input) => input,
|
||||
// on parse err, make IDEs happy; see fn docs
|
||||
Err(err) => return input_and_compile_error(item, err),
|
||||
};
|
||||
|
||||
let 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;
|
||||
let sig = &mut input.sig;
|
||||
@ -74,7 +135,7 @@ pub fn test(_: 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;
|
||||
}
|
||||
}
|
||||
@ -91,18 +152,68 @@ pub fn test(_: TokenStream, item: TokenStream) -> TokenStream {
|
||||
sig.asyncness = None;
|
||||
|
||||
let missing_test_attr = if has_test_attr {
|
||||
quote!()
|
||||
quote! {}
|
||||
} else {
|
||||
quote!(#[test])
|
||||
quote! { #[::core::prelude::v1::test] }
|
||||
};
|
||||
|
||||
let mut system = syn::parse_str::<syn::Path>("::actix_rt::System").unwrap();
|
||||
|
||||
for arg in &args {
|
||||
match arg {
|
||||
syn::Meta::NameValue(syn::MetaNameValue {
|
||||
path,
|
||||
value:
|
||||
syn::Expr::Lit(syn::ExprLit {
|
||||
lit: syn::Lit::Str(lit),
|
||||
..
|
||||
}),
|
||||
..
|
||||
}) => match path
|
||||
.get_ident()
|
||||
.map(|i| i.to_string().to_lowercase())
|
||||
.as_deref()
|
||||
{
|
||||
Some("system") => match lit.parse() {
|
||||
Ok(path) => system = path,
|
||||
Err(_) => {
|
||||
return syn::Error::new_spanned(lit, "Expected path")
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
return syn::Error::new_spanned(arg, "Unknown attribute specified")
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
return syn::Error::new_spanned(arg, "Unknown attribute specified")
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(quote! {
|
||||
#missing_test_attr
|
||||
#(#attrs)*
|
||||
#vis #sig {
|
||||
actix_rt::System::new()
|
||||
.block_on(async { #body })
|
||||
<#system>::new().block_on(async { #body })
|
||||
}
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Converts the error to a token stream and appends it to the original input.
|
||||
///
|
||||
/// Returning the original input in addition to the error is good for IDEs which can gracefully
|
||||
/// recover and show more precise errors within the macro body.
|
||||
///
|
||||
/// See <https://github.com/rust-analyzer/rust-analyzer/issues/10468> for more info.
|
||||
fn input_and_compile_error(mut item: TokenStream, err: syn::Error) -> TokenStream {
|
||||
let compile_err = TokenStream::from(err.to_compile_error());
|
||||
item.extend(compile_err);
|
||||
item
|
||||
}
|
||||
|
@ -1,11 +1,21 @@
|
||||
#![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");
|
||||
t.pass("tests/trybuild/main-04-system-path.rs");
|
||||
t.compile_fail("tests/trybuild/main-05-system-expect-path.rs");
|
||||
t.compile_fail("tests/trybuild/main-06-unknown-attr.rs");
|
||||
|
||||
t.pass("tests/trybuild/test-01-basic.rs");
|
||||
t.pass("tests/trybuild/test-02-keep-attrs.rs");
|
||||
t.compile_fail("tests/trybuild/test-03-only-async.rs");
|
||||
t.pass("tests/trybuild/test-04-system-path.rs");
|
||||
t.compile_fail("tests/trybuild/test-05-system-expect-path.rs");
|
||||
t.compile_fail("tests/trybuild/test-06-unknown-attr.rs");
|
||||
}
|
||||
|
@ -1,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`
|
||||
|
8
actix-macros/tests/trybuild/main-04-system-path.rs
Normal file
8
actix-macros/tests/trybuild/main-04-system-path.rs
Normal file
@ -0,0 +1,8 @@
|
||||
mod system {
|
||||
pub use actix_rt::System as MySystem;
|
||||
}
|
||||
|
||||
#[actix_rt::main(system = "system::MySystem")]
|
||||
async fn main() {
|
||||
futures_util::future::ready(()).await
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
#[actix_rt::main(system = "!@#*&")]
|
||||
async fn main2() {}
|
||||
|
||||
fn main() {}
|
@ -0,0 +1,5 @@
|
||||
error: Expected path
|
||||
--> $DIR/main-05-system-expect-path.rs:1:27
|
||||
|
|
||||
1 | #[actix_rt::main(system = "!@#*&")]
|
||||
| ^^^^^^^
|
7
actix-macros/tests/trybuild/main-06-unknown-attr.rs
Normal file
7
actix-macros/tests/trybuild/main-06-unknown-attr.rs
Normal file
@ -0,0 +1,7 @@
|
||||
#[actix_rt::main(foo = "bar")]
|
||||
async fn async_main() {}
|
||||
|
||||
#[actix_rt::main(bar::baz)]
|
||||
async fn async_main2() {}
|
||||
|
||||
fn main() {}
|
11
actix-macros/tests/trybuild/main-06-unknown-attr.stderr
Normal file
11
actix-macros/tests/trybuild/main-06-unknown-attr.stderr
Normal file
@ -0,0 +1,11 @@
|
||||
error: Unknown attribute specified
|
||||
--> $DIR/main-06-unknown-attr.rs:1:18
|
||||
|
|
||||
1 | #[actix_rt::main(foo = "bar")]
|
||||
| ^^^^^^^^^^^
|
||||
|
||||
error: Unknown attribute specified
|
||||
--> $DIR/main-06-unknown-attr.rs:4:18
|
||||
|
|
||||
4 | #[actix_rt::main(bar::baz)]
|
||||
| ^^^^^^^^
|
10
actix-macros/tests/trybuild/test-04-system-path.rs
Normal file
10
actix-macros/tests/trybuild/test-04-system-path.rs
Normal file
@ -0,0 +1,10 @@
|
||||
mod system {
|
||||
pub use actix_rt::System as MySystem;
|
||||
}
|
||||
|
||||
#[actix_rt::test(system = "system::MySystem")]
|
||||
async fn my_test() {
|
||||
futures_util::future::ready(()).await
|
||||
}
|
||||
|
||||
fn main() {}
|
@ -0,0 +1,4 @@
|
||||
#[actix_rt::test(system = "!@#*&")]
|
||||
async fn my_test() {}
|
||||
|
||||
fn main() {}
|
@ -0,0 +1,5 @@
|
||||
error: Expected path
|
||||
--> $DIR/test-05-system-expect-path.rs:1:27
|
||||
|
|
||||
1 | #[actix_rt::test(system = "!@#*&")]
|
||||
| ^^^^^^^
|
7
actix-macros/tests/trybuild/test-06-unknown-attr.rs
Normal file
7
actix-macros/tests/trybuild/test-06-unknown-attr.rs
Normal file
@ -0,0 +1,7 @@
|
||||
#[actix_rt::test(foo = "bar")]
|
||||
async fn my_test_1() {}
|
||||
|
||||
#[actix_rt::test(bar::baz)]
|
||||
async fn my_test_2() {}
|
||||
|
||||
fn main() {}
|
11
actix-macros/tests/trybuild/test-06-unknown-attr.stderr
Normal file
11
actix-macros/tests/trybuild/test-06-unknown-attr.stderr
Normal file
@ -0,0 +1,11 @@
|
||||
error: Unknown attribute specified
|
||||
--> $DIR/test-06-unknown-attr.rs:1:18
|
||||
|
|
||||
1 | #[actix_rt::test(foo = "bar")]
|
||||
| ^^^^^^^^^^^
|
||||
|
||||
error: Unknown attribute specified
|
||||
--> $DIR/test-06-unknown-attr.rs:4:18
|
||||
|
|
||||
4 | #[actix_rt::test(bar::baz)]
|
||||
| ^^^^^^^^
|
@ -1,67 +0,0 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 0.2.7 - 2021-02-06
|
||||
* Add `Router::recognize_checked` [#247]
|
||||
|
||||
[#247]: https://github.com/actix/actix-net/pull/247
|
||||
|
||||
|
||||
## 0.2.6 - 2021-01-09
|
||||
* Use `bytestring` version range compatible with Bytes v1.0. [#246]
|
||||
|
||||
[#246]: https://github.com/actix/actix-net/pull/246
|
||||
|
||||
|
||||
## 0.2.5 - 2020-09-20
|
||||
* Fix `from_hex()` method
|
||||
|
||||
|
||||
## 0.2.4 - 2019-12-31
|
||||
* Add `ResourceDef::resource_path_named()` path generation method
|
||||
|
||||
|
||||
## 0.2.3 - 2019-12-25
|
||||
* Add impl `IntoPattern` for `&String`
|
||||
|
||||
|
||||
## 0.2.2 - 2019-12-25
|
||||
* Use `IntoPattern` for `RouterBuilder::path()`
|
||||
|
||||
|
||||
## 0.2.1 - 2019-12-25
|
||||
* Add `IntoPattern` trait
|
||||
* Add multi-pattern resources
|
||||
|
||||
|
||||
## 0.2.0 - 2019-12-07
|
||||
* Update http to 0.2
|
||||
* Update regex to 1.3
|
||||
* Use bytestring instead of string
|
||||
|
||||
|
||||
## 0.1.5 - 2019-05-15
|
||||
* Remove debug prints
|
||||
|
||||
|
||||
## 0.1.4 - 2019-05-15
|
||||
* Fix checked resource match
|
||||
|
||||
|
||||
## 0.1.3 - 2019-04-22
|
||||
* Added support for `remainder match` (i.e "/path/{tail}*")
|
||||
|
||||
|
||||
## 0.1.2 - 2019-04-07
|
||||
* Export `Quoter` type
|
||||
* Allow to reset `Path` instance
|
||||
|
||||
|
||||
## 0.1.1 - 2019-04-03
|
||||
* Get dynamic segment by name instead of iterator.
|
||||
|
||||
|
||||
## 0.1.0 - 2019-03-09
|
||||
* Initial release
|
@ -1,29 +0,0 @@
|
||||
[package]
|
||||
name = "actix-router"
|
||||
version = "0.2.7"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Resource path matching library"
|
||||
keywords = ["actix", "router", "routing"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-net.git"
|
||||
documentation = "https://docs.rs/actix-router"
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
name = "actix_router"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = ["http"]
|
||||
|
||||
[dependencies]
|
||||
regex = "1.3.1"
|
||||
serde = "1.0.104"
|
||||
bytestring = ">=0.1.5, <2"
|
||||
log = "0.4.8"
|
||||
http = { version = "0.2.2", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
http = "0.2.2"
|
||||
serde_derive = "1.0"
|
@ -1,717 +0,0 @@
|
||||
use serde::de::{self, Deserializer, Error as DeError, Visitor};
|
||||
use serde::forward_to_deserialize_any;
|
||||
|
||||
use crate::path::{Path, PathIter};
|
||||
use crate::ResourcePath;
|
||||
|
||||
macro_rules! unsupported_type {
|
||||
($trait_fn:ident, $name:expr) => {
|
||||
fn $trait_fn<V>(self, _: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
Err(de::value::Error::custom(concat!(
|
||||
"unsupported type: ",
|
||||
$name
|
||||
)))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! parse_single_value {
|
||||
($trait_fn:ident, $visit_fn:ident, $tp:tt) => {
|
||||
fn $trait_fn<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
if self.path.len() != 1 {
|
||||
Err(de::value::Error::custom(
|
||||
format!("wrong number of parameters: {} expected 1", self.path.len())
|
||||
.as_str(),
|
||||
))
|
||||
} else {
|
||||
let v = self.path[0].parse().map_err(|_| {
|
||||
de::value::Error::custom(format!(
|
||||
"can not parse {:?} to a {}",
|
||||
&self.path[0], $tp
|
||||
))
|
||||
})?;
|
||||
visitor.$visit_fn(v)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub struct PathDeserializer<'de, T: ResourcePath> {
|
||||
path: &'de Path<T>,
|
||||
}
|
||||
|
||||
impl<'de, T: ResourcePath + 'de> PathDeserializer<'de, T> {
|
||||
pub fn new(path: &'de Path<T>) -> Self {
|
||||
PathDeserializer { path }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T: ResourcePath + 'de> Deserializer<'de> for PathDeserializer<'de, T> {
|
||||
type Error = de::value::Error;
|
||||
|
||||
fn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
visitor.visit_map(ParamsDeserializer {
|
||||
params: self.path.iter(),
|
||||
current: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn deserialize_struct<V>(
|
||||
self,
|
||||
_: &'static str,
|
||||
_: &'static [&'static str],
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
self.deserialize_map(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
visitor.visit_unit()
|
||||
}
|
||||
|
||||
fn deserialize_unit_struct<V>(
|
||||
self,
|
||||
_: &'static str,
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
self.deserialize_unit(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_newtype_struct<V>(
|
||||
self,
|
||||
_: &'static str,
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
visitor.visit_newtype_struct(self)
|
||||
}
|
||||
|
||||
fn deserialize_tuple<V>(self, len: usize, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
if self.path.len() < len {
|
||||
Err(de::value::Error::custom(
|
||||
format!(
|
||||
"wrong number of parameters: {} expected {}",
|
||||
self.path.len(),
|
||||
len
|
||||
)
|
||||
.as_str(),
|
||||
))
|
||||
} else {
|
||||
visitor.visit_seq(ParamsSeq {
|
||||
params: self.path.iter(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_tuple_struct<V>(
|
||||
self,
|
||||
_: &'static str,
|
||||
len: usize,
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
if self.path.len() < len {
|
||||
Err(de::value::Error::custom(
|
||||
format!(
|
||||
"wrong number of parameters: {} expected {}",
|
||||
self.path.len(),
|
||||
len
|
||||
)
|
||||
.as_str(),
|
||||
))
|
||||
} else {
|
||||
visitor.visit_seq(ParamsSeq {
|
||||
params: self.path.iter(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_enum<V>(
|
||||
self,
|
||||
_: &'static str,
|
||||
_: &'static [&'static str],
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
if self.path.is_empty() {
|
||||
Err(de::value::Error::custom("expected at least one parameters"))
|
||||
} else {
|
||||
visitor.visit_enum(ValueEnum {
|
||||
value: &self.path[0],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
if self.path.len() != 1 {
|
||||
Err(de::value::Error::custom(
|
||||
format!("wrong number of parameters: {} expected 1", self.path.len()).as_str(),
|
||||
))
|
||||
} else {
|
||||
visitor.visit_str(&self.path[0])
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
visitor.visit_seq(ParamsSeq {
|
||||
params: self.path.iter(),
|
||||
})
|
||||
}
|
||||
|
||||
unsupported_type!(deserialize_any, "'any'");
|
||||
unsupported_type!(deserialize_bytes, "bytes");
|
||||
unsupported_type!(deserialize_option, "Option<T>");
|
||||
unsupported_type!(deserialize_identifier, "identifier");
|
||||
unsupported_type!(deserialize_ignored_any, "ignored_any");
|
||||
|
||||
parse_single_value!(deserialize_bool, visit_bool, "bool");
|
||||
parse_single_value!(deserialize_i8, visit_i8, "i8");
|
||||
parse_single_value!(deserialize_i16, visit_i16, "i16");
|
||||
parse_single_value!(deserialize_i32, visit_i32, "i32");
|
||||
parse_single_value!(deserialize_i64, visit_i64, "i64");
|
||||
parse_single_value!(deserialize_u8, visit_u8, "u8");
|
||||
parse_single_value!(deserialize_u16, visit_u16, "u16");
|
||||
parse_single_value!(deserialize_u32, visit_u32, "u32");
|
||||
parse_single_value!(deserialize_u64, visit_u64, "u64");
|
||||
parse_single_value!(deserialize_f32, visit_f32, "f32");
|
||||
parse_single_value!(deserialize_f64, visit_f64, "f64");
|
||||
parse_single_value!(deserialize_string, visit_string, "String");
|
||||
parse_single_value!(deserialize_byte_buf, visit_string, "String");
|
||||
parse_single_value!(deserialize_char, visit_char, "char");
|
||||
}
|
||||
|
||||
struct ParamsDeserializer<'de, T: ResourcePath> {
|
||||
params: PathIter<'de, T>,
|
||||
current: Option<(&'de str, &'de str)>,
|
||||
}
|
||||
|
||||
impl<'de, T: ResourcePath> de::MapAccess<'de> for ParamsDeserializer<'de, T> {
|
||||
type Error = de::value::Error;
|
||||
|
||||
fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Self::Error>
|
||||
where
|
||||
K: de::DeserializeSeed<'de>,
|
||||
{
|
||||
self.current = self.params.next().map(|ref item| (item.0, item.1));
|
||||
match self.current {
|
||||
Some((key, _)) => Ok(Some(seed.deserialize(Key { key })?)),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: de::DeserializeSeed<'de>,
|
||||
{
|
||||
if let Some((_, value)) = self.current.take() {
|
||||
seed.deserialize(Value { value })
|
||||
} else {
|
||||
Err(de::value::Error::custom("unexpected item"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Key<'de> {
|
||||
key: &'de str,
|
||||
}
|
||||
|
||||
impl<'de> Deserializer<'de> for Key<'de> {
|
||||
type Error = de::value::Error;
|
||||
|
||||
fn deserialize_identifier<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
visitor.visit_str(self.key)
|
||||
}
|
||||
|
||||
fn deserialize_any<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
Err(de::value::Error::custom("Unexpected"))
|
||||
}
|
||||
|
||||
forward_to_deserialize_any! {
|
||||
bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes
|
||||
byte_buf option unit unit_struct newtype_struct seq tuple
|
||||
tuple_struct map struct enum ignored_any
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! parse_value {
|
||||
($trait_fn:ident, $visit_fn:ident, $tp:tt) => {
|
||||
fn $trait_fn<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
let v = self.value.parse().map_err(|_| {
|
||||
de::value::Error::custom(format!("can not parse {:?} to a {}", self.value, $tp))
|
||||
})?;
|
||||
visitor.$visit_fn(v)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
struct Value<'de> {
|
||||
value: &'de str,
|
||||
}
|
||||
|
||||
impl<'de> Deserializer<'de> for Value<'de> {
|
||||
type Error = de::value::Error;
|
||||
|
||||
parse_value!(deserialize_bool, visit_bool, "bool");
|
||||
parse_value!(deserialize_i8, visit_i8, "i8");
|
||||
parse_value!(deserialize_i16, visit_i16, "i16");
|
||||
parse_value!(deserialize_i32, visit_i32, "i16");
|
||||
parse_value!(deserialize_i64, visit_i64, "i64");
|
||||
parse_value!(deserialize_u8, visit_u8, "u8");
|
||||
parse_value!(deserialize_u16, visit_u16, "u16");
|
||||
parse_value!(deserialize_u32, visit_u32, "u32");
|
||||
parse_value!(deserialize_u64, visit_u64, "u64");
|
||||
parse_value!(deserialize_f32, visit_f32, "f32");
|
||||
parse_value!(deserialize_f64, visit_f64, "f64");
|
||||
parse_value!(deserialize_string, visit_string, "String");
|
||||
parse_value!(deserialize_byte_buf, visit_string, "String");
|
||||
parse_value!(deserialize_char, visit_char, "char");
|
||||
|
||||
fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
visitor.visit_unit()
|
||||
}
|
||||
|
||||
fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
visitor.visit_unit()
|
||||
}
|
||||
|
||||
fn deserialize_unit_struct<V>(
|
||||
self,
|
||||
_: &'static str,
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
visitor.visit_unit()
|
||||
}
|
||||
|
||||
fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
visitor.visit_borrowed_bytes(self.value.as_bytes())
|
||||
}
|
||||
|
||||
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
visitor.visit_borrowed_str(self.value)
|
||||
}
|
||||
|
||||
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
visitor.visit_some(self)
|
||||
}
|
||||
|
||||
fn deserialize_enum<V>(
|
||||
self,
|
||||
_: &'static str,
|
||||
_: &'static [&'static str],
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
visitor.visit_enum(ValueEnum { value: self.value })
|
||||
}
|
||||
|
||||
fn deserialize_newtype_struct<V>(
|
||||
self,
|
||||
_: &'static str,
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
visitor.visit_newtype_struct(self)
|
||||
}
|
||||
|
||||
fn deserialize_tuple<V>(self, _: usize, _: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
Err(de::value::Error::custom("unsupported type: tuple"))
|
||||
}
|
||||
|
||||
fn deserialize_struct<V>(
|
||||
self,
|
||||
_: &'static str,
|
||||
_: &'static [&'static str],
|
||||
_: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
Err(de::value::Error::custom("unsupported type: struct"))
|
||||
}
|
||||
|
||||
fn deserialize_tuple_struct<V>(
|
||||
self,
|
||||
_: &'static str,
|
||||
_: usize,
|
||||
_: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
Err(de::value::Error::custom("unsupported type: tuple struct"))
|
||||
}
|
||||
|
||||
unsupported_type!(deserialize_any, "any");
|
||||
unsupported_type!(deserialize_seq, "seq");
|
||||
unsupported_type!(deserialize_map, "map");
|
||||
unsupported_type!(deserialize_identifier, "identifier");
|
||||
}
|
||||
|
||||
struct ParamsSeq<'de, T: ResourcePath> {
|
||||
params: PathIter<'de, T>,
|
||||
}
|
||||
|
||||
impl<'de, T: ResourcePath> de::SeqAccess<'de> for ParamsSeq<'de, T> {
|
||||
type Error = de::value::Error;
|
||||
|
||||
fn next_element_seed<U>(&mut self, seed: U) -> Result<Option<U::Value>, Self::Error>
|
||||
where
|
||||
U: de::DeserializeSeed<'de>,
|
||||
{
|
||||
match self.params.next() {
|
||||
Some(item) => Ok(Some(seed.deserialize(Value { value: item.1 })?)),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ValueEnum<'de> {
|
||||
value: &'de str,
|
||||
}
|
||||
|
||||
impl<'de> de::EnumAccess<'de> for ValueEnum<'de> {
|
||||
type Error = de::value::Error;
|
||||
type Variant = UnitVariant;
|
||||
|
||||
fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error>
|
||||
where
|
||||
V: de::DeserializeSeed<'de>,
|
||||
{
|
||||
Ok((seed.deserialize(Key { key: self.value })?, UnitVariant))
|
||||
}
|
||||
}
|
||||
|
||||
struct UnitVariant;
|
||||
|
||||
impl<'de> de::VariantAccess<'de> for UnitVariant {
|
||||
type Error = de::value::Error;
|
||||
|
||||
fn unit_variant(self) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn newtype_variant_seed<T>(self, _seed: T) -> Result<T::Value, Self::Error>
|
||||
where
|
||||
T: de::DeserializeSeed<'de>,
|
||||
{
|
||||
Err(de::value::Error::custom("not supported"))
|
||||
}
|
||||
|
||||
fn tuple_variant<V>(self, _len: usize, _visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
Err(de::value::Error::custom("not supported"))
|
||||
}
|
||||
|
||||
fn struct_variant<V>(
|
||||
self,
|
||||
_: &'static [&'static str],
|
||||
_: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
Err(de::value::Error::custom("not supported"))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde::de;
|
||||
use serde_derive::Deserialize;
|
||||
|
||||
use super::*;
|
||||
use crate::path::Path;
|
||||
use crate::router::Router;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct MyStruct {
|
||||
key: String,
|
||||
value: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Id {
|
||||
_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Test1(String, u32);
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Test2 {
|
||||
key: String,
|
||||
value: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
enum TestEnum {
|
||||
Val1,
|
||||
Val2,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Test3 {
|
||||
val: TestEnum,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_request_extract() {
|
||||
let mut router = Router::<()>::build();
|
||||
router.path("/{key}/{value}/", ());
|
||||
let router = router.finish();
|
||||
|
||||
let mut path = Path::new("/name/user1/");
|
||||
assert!(router.recognize(&mut path).is_some());
|
||||
|
||||
let s: MyStruct = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
|
||||
assert_eq!(s.key, "name");
|
||||
assert_eq!(s.value, "user1");
|
||||
|
||||
let s: (String, String) =
|
||||
de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
|
||||
assert_eq!(s.0, "name");
|
||||
assert_eq!(s.1, "user1");
|
||||
|
||||
let mut router = Router::<()>::build();
|
||||
router.path("/{key}/{value}/", ());
|
||||
let router = router.finish();
|
||||
|
||||
let mut path = Path::new("/name/32/");
|
||||
assert!(router.recognize(&mut path).is_some());
|
||||
|
||||
let s: Test1 = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
|
||||
assert_eq!(s.0, "name");
|
||||
assert_eq!(s.1, 32);
|
||||
|
||||
let s: Test2 = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
|
||||
assert_eq!(s.key, "name");
|
||||
assert_eq!(s.value, 32);
|
||||
|
||||
let s: (String, u8) =
|
||||
de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
|
||||
assert_eq!(s.0, "name");
|
||||
assert_eq!(s.1, 32);
|
||||
|
||||
let res: Vec<String> =
|
||||
de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
|
||||
assert_eq!(res[0], "name".to_owned());
|
||||
assert_eq!(res[1], "32".to_owned());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_path_single() {
|
||||
let mut router = Router::<()>::build();
|
||||
router.path("/{value}/", ());
|
||||
let router = router.finish();
|
||||
|
||||
let mut path = Path::new("/32/");
|
||||
assert!(router.recognize(&mut path).is_some());
|
||||
let i: i8 = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
|
||||
assert_eq!(i, 32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_enum() {
|
||||
let mut router = Router::<()>::build();
|
||||
router.path("/{val}/", ());
|
||||
let router = router.finish();
|
||||
|
||||
let mut path = Path::new("/val1/");
|
||||
assert!(router.recognize(&mut path).is_some());
|
||||
let i: TestEnum = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
|
||||
assert_eq!(i, TestEnum::Val1);
|
||||
|
||||
let mut router = Router::<()>::build();
|
||||
router.path("/{val1}/{val2}/", ());
|
||||
let router = router.finish();
|
||||
|
||||
let mut path = Path::new("/val1/val2/");
|
||||
assert!(router.recognize(&mut path).is_some());
|
||||
let i: (TestEnum, TestEnum) =
|
||||
de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
|
||||
assert_eq!(i, (TestEnum::Val1, TestEnum::Val2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_enum_value() {
|
||||
let mut router = Router::<()>::build();
|
||||
router.path("/{val}/", ());
|
||||
let router = router.finish();
|
||||
|
||||
let mut path = Path::new("/val1/");
|
||||
assert!(router.recognize(&mut path).is_some());
|
||||
let i: Test3 = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
|
||||
assert_eq!(i.val, TestEnum::Val1);
|
||||
|
||||
let mut path = Path::new("/val3/");
|
||||
assert!(router.recognize(&mut path).is_some());
|
||||
let i: Result<Test3, de::value::Error> =
|
||||
de::Deserialize::deserialize(PathDeserializer::new(&path));
|
||||
assert!(i.is_err());
|
||||
assert!(format!("{:?}", i).contains("unknown variant"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_errors() {
|
||||
let mut router = Router::<()>::build();
|
||||
router.path("/{value}/", ());
|
||||
let router = router.finish();
|
||||
|
||||
let mut path = Path::new("/name/");
|
||||
assert!(router.recognize(&mut path).is_some());
|
||||
|
||||
let s: Result<Test1, de::value::Error> =
|
||||
de::Deserialize::deserialize(PathDeserializer::new(&path));
|
||||
assert!(s.is_err());
|
||||
assert!(format!("{:?}", s).contains("wrong number of parameters"));
|
||||
|
||||
let s: Result<Test2, de::value::Error> =
|
||||
de::Deserialize::deserialize(PathDeserializer::new(&path));
|
||||
assert!(s.is_err());
|
||||
assert!(format!("{:?}", s).contains("can not parse"));
|
||||
|
||||
let s: Result<(String, String), de::value::Error> =
|
||||
de::Deserialize::deserialize(PathDeserializer::new(&path));
|
||||
assert!(s.is_err());
|
||||
assert!(format!("{:?}", s).contains("wrong number of parameters"));
|
||||
|
||||
let s: Result<u32, de::value::Error> =
|
||||
de::Deserialize::deserialize(PathDeserializer::new(&path));
|
||||
assert!(s.is_err());
|
||||
assert!(format!("{:?}", s).contains("can not parse"));
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_extract_path_decode() {
|
||||
// let mut router = Router::<()>::default();
|
||||
// router.register_resource(Resource::new(ResourceDef::new("/{value}/")));
|
||||
|
||||
// macro_rules! test_single_value {
|
||||
// ($value:expr, $expected:expr) => {{
|
||||
// let req = TestRequest::with_uri($value).finish();
|
||||
// let info = router.recognize(&req, &(), 0);
|
||||
// let req = req.with_route_info(info);
|
||||
// assert_eq!(
|
||||
// *Path::<String>::from_request(&req, &PathConfig::default()).unwrap(),
|
||||
// $expected
|
||||
// );
|
||||
// }};
|
||||
// }
|
||||
|
||||
// test_single_value!("/%25/", "%");
|
||||
// test_single_value!("/%40%C2%A3%24%25%5E%26%2B%3D/", "@£$%^&+=");
|
||||
// test_single_value!("/%2B/", "+");
|
||||
// test_single_value!("/%252B/", "%2B");
|
||||
// test_single_value!("/%2F/", "/");
|
||||
// test_single_value!("/%252F/", "%2F");
|
||||
// test_single_value!(
|
||||
// "/http%3A%2F%2Flocalhost%3A80%2Ffoo/",
|
||||
// "http://localhost:80/foo"
|
||||
// );
|
||||
// test_single_value!("/%2Fvar%2Flog%2Fsyslog/", "/var/log/syslog");
|
||||
// test_single_value!(
|
||||
// "/http%3A%2F%2Flocalhost%3A80%2Ffile%2F%252Fvar%252Flog%252Fsyslog/",
|
||||
// "http://localhost:80/file/%2Fvar%2Flog%2Fsyslog"
|
||||
// );
|
||||
|
||||
// let req = TestRequest::with_uri("/%25/7/?id=test").finish();
|
||||
|
||||
// let mut router = Router::<()>::default();
|
||||
// router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/")));
|
||||
// let info = router.recognize(&req, &(), 0);
|
||||
// let req = req.with_route_info(info);
|
||||
|
||||
// let s = Path::<Test2>::from_request(&req, &PathConfig::default()).unwrap();
|
||||
// assert_eq!(s.key, "%");
|
||||
// assert_eq!(s.value, 7);
|
||||
|
||||
// let s = Path::<(String, String)>::from_request(&req, &PathConfig::default()).unwrap();
|
||||
// assert_eq!(s.0, "%");
|
||||
// assert_eq!(s.1, "7");
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_extract_path_no_decode() {
|
||||
// let mut router = Router::<()>::default();
|
||||
// router.register_resource(Resource::new(ResourceDef::new("/{value}/")));
|
||||
|
||||
// let req = TestRequest::with_uri("/%25/").finish();
|
||||
// let info = router.recognize(&req, &(), 0);
|
||||
// let req = req.with_route_info(info);
|
||||
// assert_eq!(
|
||||
// *Path::<String>::from_request(&req, &&PathConfig::default().disable_decoding())
|
||||
// .unwrap(),
|
||||
// "%25"
|
||||
// );
|
||||
// }
|
||||
}
|
@ -1,152 +0,0 @@
|
||||
//! Resource path matching library.
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
|
||||
mod de;
|
||||
mod path;
|
||||
mod resource;
|
||||
mod router;
|
||||
|
||||
pub use self::de::PathDeserializer;
|
||||
pub use self::path::Path;
|
||||
pub use self::resource::ResourceDef;
|
||||
pub use self::router::{ResourceInfo, Router, RouterBuilder};
|
||||
|
||||
pub trait Resource<T: ResourcePath> {
|
||||
fn resource_path(&mut self) -> &mut Path<T>;
|
||||
}
|
||||
|
||||
pub trait ResourcePath {
|
||||
fn path(&self) -> &str;
|
||||
}
|
||||
|
||||
impl ResourcePath for String {
|
||||
fn path(&self) -> &str {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ResourcePath for &'a str {
|
||||
fn path(&self) -> &str {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourcePath for bytestring::ByteString {
|
||||
fn path(&self) -> &str {
|
||||
&*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper trait for type that could be converted to path pattern
|
||||
pub trait IntoPattern {
|
||||
fn is_single(&self) -> bool;
|
||||
|
||||
fn patterns(&self) -> Vec<String>;
|
||||
}
|
||||
|
||||
impl IntoPattern for String {
|
||||
fn is_single(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn patterns(&self) -> Vec<String> {
|
||||
vec![self.clone()]
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoPattern for &'a String {
|
||||
fn is_single(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn patterns(&self) -> Vec<String> {
|
||||
vec![self.as_str().to_string()]
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoPattern for &'a str {
|
||||
fn is_single(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn patterns(&self) -> Vec<String> {
|
||||
vec![(*self).to_string()]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<str>> IntoPattern for Vec<T> {
|
||||
fn is_single(&self) -> bool {
|
||||
self.len() == 1
|
||||
}
|
||||
|
||||
fn patterns(&self) -> Vec<String> {
|
||||
self.iter().map(|v| v.as_ref().to_string()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! array_patterns (($tp:ty, $num:tt) => {
|
||||
impl IntoPattern for [$tp; $num] {
|
||||
fn is_single(&self) -> bool {
|
||||
$num == 1
|
||||
}
|
||||
|
||||
fn patterns(&self) -> Vec<String> {
|
||||
self.iter().map(|v| v.to_string()).collect()
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
array_patterns!(&str, 1);
|
||||
array_patterns!(&str, 2);
|
||||
array_patterns!(&str, 3);
|
||||
array_patterns!(&str, 4);
|
||||
array_patterns!(&str, 5);
|
||||
array_patterns!(&str, 6);
|
||||
array_patterns!(&str, 7);
|
||||
array_patterns!(&str, 8);
|
||||
array_patterns!(&str, 9);
|
||||
array_patterns!(&str, 10);
|
||||
array_patterns!(&str, 11);
|
||||
array_patterns!(&str, 12);
|
||||
array_patterns!(&str, 13);
|
||||
array_patterns!(&str, 14);
|
||||
array_patterns!(&str, 15);
|
||||
array_patterns!(&str, 16);
|
||||
|
||||
array_patterns!(String, 1);
|
||||
array_patterns!(String, 2);
|
||||
array_patterns!(String, 3);
|
||||
array_patterns!(String, 4);
|
||||
array_patterns!(String, 5);
|
||||
array_patterns!(String, 6);
|
||||
array_patterns!(String, 7);
|
||||
array_patterns!(String, 8);
|
||||
array_patterns!(String, 9);
|
||||
array_patterns!(String, 10);
|
||||
array_patterns!(String, 11);
|
||||
array_patterns!(String, 12);
|
||||
array_patterns!(String, 13);
|
||||
array_patterns!(String, 14);
|
||||
array_patterns!(String, 15);
|
||||
array_patterns!(String, 16);
|
||||
|
||||
#[cfg(feature = "http")]
|
||||
mod url;
|
||||
|
||||
#[cfg(feature = "http")]
|
||||
pub use self::url::{Quoter, Url};
|
||||
|
||||
#[cfg(feature = "http")]
|
||||
mod http_support {
|
||||
use super::ResourcePath;
|
||||
use http::Uri;
|
||||
|
||||
impl ResourcePath for Uri {
|
||||
fn path(&self) -> &str {
|
||||
self.path()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,222 +0,0 @@
|
||||
use std::ops::Index;
|
||||
|
||||
use serde::de;
|
||||
|
||||
use crate::de::PathDeserializer;
|
||||
use crate::{Resource, ResourcePath};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) enum PathItem {
|
||||
Static(&'static str),
|
||||
Segment(u16, u16),
|
||||
}
|
||||
|
||||
/// Resource path match information
|
||||
///
|
||||
/// If resource path contains variable patterns, `Path` stores them.
|
||||
#[derive(Debug)]
|
||||
pub struct Path<T> {
|
||||
path: T,
|
||||
pub(crate) skip: u16,
|
||||
pub(crate) segments: Vec<(&'static str, PathItem)>,
|
||||
}
|
||||
|
||||
impl<T: Default> Default for Path<T> {
|
||||
fn default() -> Self {
|
||||
Path {
|
||||
path: T::default(),
|
||||
skip: 0,
|
||||
segments: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> Clone for Path<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Path {
|
||||
path: self.path.clone(),
|
||||
skip: self.skip,
|
||||
segments: self.segments.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ResourcePath> Path<T> {
|
||||
pub fn new(path: T) -> Path<T> {
|
||||
Path {
|
||||
path,
|
||||
skip: 0,
|
||||
segments: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get reference to inner path instance
|
||||
#[inline]
|
||||
pub fn get_ref(&self) -> &T {
|
||||
&self.path
|
||||
}
|
||||
|
||||
/// Get mutable reference to inner path instance
|
||||
#[inline]
|
||||
pub fn get_mut(&mut self) -> &mut T {
|
||||
&mut self.path
|
||||
}
|
||||
|
||||
/// Path
|
||||
#[inline]
|
||||
pub fn path(&self) -> &str {
|
||||
let skip = self.skip as usize;
|
||||
let path = self.path.path();
|
||||
if skip <= path.len() {
|
||||
&path[skip..]
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
/// Set new path
|
||||
#[inline]
|
||||
pub fn set(&mut self, path: T) {
|
||||
self.skip = 0;
|
||||
self.path = path;
|
||||
self.segments.clear();
|
||||
}
|
||||
|
||||
/// Reset state
|
||||
#[inline]
|
||||
pub fn reset(&mut self) {
|
||||
self.skip = 0;
|
||||
self.segments.clear();
|
||||
}
|
||||
|
||||
/// Skip first `n` chars in path
|
||||
#[inline]
|
||||
pub fn skip(&mut self, n: u16) {
|
||||
self.skip += n;
|
||||
}
|
||||
|
||||
pub(crate) fn add(&mut self, name: &'static str, value: PathItem) {
|
||||
match value {
|
||||
PathItem::Static(s) => self.segments.push((name, PathItem::Static(s))),
|
||||
PathItem::Segment(begin, end) => self
|
||||
.segments
|
||||
.push((name, PathItem::Segment(self.skip + begin, self.skip + end))),
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn add_static(&mut self, name: &'static str, value: &'static str) {
|
||||
self.segments.push((name, PathItem::Static(value)));
|
||||
}
|
||||
|
||||
/// Check if there are any matched patterns
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.segments.is_empty()
|
||||
}
|
||||
|
||||
/// Check number of extracted parameters
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.segments.len()
|
||||
}
|
||||
|
||||
/// Get matched parameter by name without type conversion
|
||||
pub fn get(&self, key: &str) -> Option<&str> {
|
||||
for item in self.segments.iter() {
|
||||
if key == item.0 {
|
||||
return match item.1 {
|
||||
PathItem::Static(ref s) => Some(&s),
|
||||
PathItem::Segment(s, e) => {
|
||||
Some(&self.path.path()[(s as usize)..(e as usize)])
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
if key == "tail" {
|
||||
Some(&self.path.path()[(self.skip as usize)..])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Get unprocessed part of the path
|
||||
pub fn unprocessed(&self) -> &str {
|
||||
&self.path.path()[(self.skip as usize)..]
|
||||
}
|
||||
|
||||
/// Get matched parameter by name.
|
||||
///
|
||||
/// If keyed parameter is not available empty string is used as default
|
||||
/// value.
|
||||
pub fn query(&self, key: &str) -> &str {
|
||||
if let Some(s) = self.get(key) {
|
||||
s
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
/// Return iterator to items in parameter container
|
||||
pub fn iter(&self) -> PathIter<'_, T> {
|
||||
PathIter {
|
||||
idx: 0,
|
||||
params: self,
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to deserialize matching parameters to a specified type `U`
|
||||
pub fn load<'de, U: serde::Deserialize<'de>>(&'de self) -> Result<U, de::value::Error> {
|
||||
de::Deserialize::deserialize(PathDeserializer::new(self))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PathIter<'a, T> {
|
||||
idx: usize,
|
||||
params: &'a Path<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: ResourcePath> Iterator for PathIter<'a, T> {
|
||||
type Item = (&'a str, &'a str);
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<(&'a str, &'a str)> {
|
||||
if self.idx < self.params.len() {
|
||||
let idx = self.idx;
|
||||
let res = match self.params.segments[idx].1 {
|
||||
PathItem::Static(ref s) => &s,
|
||||
PathItem::Segment(s, e) => &self.params.path.path()[(s as usize)..(e as usize)],
|
||||
};
|
||||
self.idx += 1;
|
||||
return Some((&self.params.segments[idx].0, res));
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ResourcePath> Index<&'a str> for Path<T> {
|
||||
type Output = str;
|
||||
|
||||
fn index(&self, name: &'a str) -> &str {
|
||||
self.get(name)
|
||||
.expect("Value for parameter is not available")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ResourcePath> Index<usize> for Path<T> {
|
||||
type Output = str;
|
||||
|
||||
fn index(&self, idx: usize) -> &str {
|
||||
match self.segments[idx].1 {
|
||||
PathItem::Static(ref s) => &s,
|
||||
PathItem::Segment(s, e) => &self.path.path()[(s as usize)..(e as usize)],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ResourcePath> Resource<T> for Path<T> {
|
||||
fn resource_path(&mut self) -> &mut Self {
|
||||
self
|
||||
}
|
||||
}
|
@ -1,947 +0,0 @@
|
||||
use std::cmp::min;
|
||||
use std::collections::HashMap;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
use regex::{escape, Regex, RegexSet};
|
||||
|
||||
use crate::path::{Path, PathItem};
|
||||
use crate::{IntoPattern, Resource, ResourcePath};
|
||||
|
||||
const MAX_DYNAMIC_SEGMENTS: usize = 16;
|
||||
|
||||
/// ResourceDef describes an entry in resources table
|
||||
///
|
||||
/// Resource definition can contain only 16 dynamic segments
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ResourceDef {
|
||||
id: u16,
|
||||
tp: PatternType,
|
||||
name: String,
|
||||
pattern: String,
|
||||
elements: Vec<PatternElement>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
enum PatternElement {
|
||||
Str(String),
|
||||
Var(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
enum PatternType {
|
||||
Static(String),
|
||||
Prefix(String),
|
||||
Dynamic(Regex, Vec<&'static str>, usize),
|
||||
DynamicSet(RegexSet, Vec<(Regex, Vec<&'static str>, usize)>),
|
||||
}
|
||||
|
||||
impl ResourceDef {
|
||||
/// Parse path pattern and create new `Pattern` instance.
|
||||
///
|
||||
/// Panics if path pattern is malformed.
|
||||
pub fn new<T: IntoPattern>(path: T) -> Self {
|
||||
if path.is_single() {
|
||||
let patterns = path.patterns();
|
||||
ResourceDef::with_prefix(&patterns[0], false)
|
||||
} else {
|
||||
let set = path.patterns();
|
||||
let mut data = Vec::new();
|
||||
let mut re_set = Vec::new();
|
||||
|
||||
for path in set {
|
||||
let (pattern, _, _, len) = ResourceDef::parse(&path, false);
|
||||
|
||||
let re = match Regex::new(&pattern) {
|
||||
Ok(re) => re,
|
||||
Err(err) => panic!("Wrong path pattern: \"{}\" {}", path, err),
|
||||
};
|
||||
// actix creates one router per thread
|
||||
let names: Vec<_> = re
|
||||
.capture_names()
|
||||
.filter_map(|name| {
|
||||
name.map(|name| Box::leak(Box::new(name.to_owned())).as_str())
|
||||
})
|
||||
.collect();
|
||||
data.push((re, names, len));
|
||||
re_set.push(pattern);
|
||||
}
|
||||
|
||||
ResourceDef {
|
||||
id: 0,
|
||||
tp: PatternType::DynamicSet(RegexSet::new(re_set).unwrap(), data),
|
||||
elements: Vec::new(),
|
||||
name: String::new(),
|
||||
pattern: "".to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse path pattern and create new `Pattern` instance.
|
||||
///
|
||||
/// Use `prefix` type instead of `static`.
|
||||
///
|
||||
/// Panics if path regex pattern is malformed.
|
||||
pub fn prefix(path: &str) -> Self {
|
||||
ResourceDef::with_prefix(path, true)
|
||||
}
|
||||
|
||||
/// Parse path pattern and create new `Pattern` instance.
|
||||
/// Inserts `/` to begging of the pattern.
|
||||
///
|
||||
///
|
||||
/// Use `prefix` type instead of `static`.
|
||||
///
|
||||
/// Panics if path regex pattern is malformed.
|
||||
pub fn root_prefix(path: &str) -> Self {
|
||||
ResourceDef::with_prefix(&insert_slash(path), true)
|
||||
}
|
||||
|
||||
/// Resource id
|
||||
pub fn id(&self) -> u16 {
|
||||
self.id
|
||||
}
|
||||
|
||||
/// Set resource id
|
||||
pub fn set_id(&mut self, id: u16) {
|
||||
self.id = id;
|
||||
}
|
||||
|
||||
/// Parse path pattern and create new `Pattern` instance with custom prefix
|
||||
fn with_prefix(path: &str, for_prefix: bool) -> Self {
|
||||
let path = path.to_owned();
|
||||
let (pattern, elements, is_dynamic, len) = ResourceDef::parse(&path, for_prefix);
|
||||
|
||||
let tp = if is_dynamic {
|
||||
let re = match Regex::new(&pattern) {
|
||||
Ok(re) => re,
|
||||
Err(err) => panic!("Wrong path pattern: \"{}\" {}", path, err),
|
||||
};
|
||||
// actix creates one router per thread
|
||||
let names = re
|
||||
.capture_names()
|
||||
.filter_map(|name| {
|
||||
name.map(|name| Box::leak(Box::new(name.to_owned())).as_str())
|
||||
})
|
||||
.collect();
|
||||
PatternType::Dynamic(re, names, len)
|
||||
} else if for_prefix {
|
||||
PatternType::Prefix(pattern)
|
||||
} else {
|
||||
PatternType::Static(pattern)
|
||||
};
|
||||
|
||||
ResourceDef {
|
||||
tp,
|
||||
elements,
|
||||
id: 0,
|
||||
name: String::new(),
|
||||
pattern: path,
|
||||
}
|
||||
}
|
||||
|
||||
/// Resource pattern name
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
/// Mutable reference to a name of a resource definition.
|
||||
pub fn name_mut(&mut self) -> &mut String {
|
||||
&mut self.name
|
||||
}
|
||||
|
||||
/// Path pattern of the resource
|
||||
pub fn pattern(&self) -> &str {
|
||||
&self.pattern
|
||||
}
|
||||
|
||||
/// Check if path matches this pattern.
|
||||
#[inline]
|
||||
pub fn is_match(&self, path: &str) -> bool {
|
||||
match self.tp {
|
||||
PatternType::Static(ref s) => s == path,
|
||||
PatternType::Prefix(ref s) => path.starts_with(s),
|
||||
PatternType::Dynamic(ref re, _, _) => re.is_match(path),
|
||||
PatternType::DynamicSet(ref re, _) => re.is_match(path),
|
||||
}
|
||||
}
|
||||
|
||||
/// Is prefix path a match against this resource.
|
||||
pub fn is_prefix_match(&self, path: &str) -> Option<usize> {
|
||||
let p_len = path.len();
|
||||
let path = if path.is_empty() { "/" } else { path };
|
||||
|
||||
match self.tp {
|
||||
PatternType::Static(ref s) => {
|
||||
if s == path {
|
||||
Some(p_len)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
PatternType::Dynamic(ref re, _, len) => {
|
||||
if let Some(captures) = re.captures(path) {
|
||||
let mut pos = 0;
|
||||
let mut passed = false;
|
||||
for capture in captures.iter() {
|
||||
if let Some(ref m) = capture {
|
||||
if !passed {
|
||||
passed = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
pos = m.end();
|
||||
}
|
||||
}
|
||||
Some(pos + len)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
PatternType::Prefix(ref s) => {
|
||||
let len = if path == s {
|
||||
s.len()
|
||||
} else if path.starts_with(s)
|
||||
&& (s.ends_with('/') || path.split_at(s.len()).1.starts_with('/'))
|
||||
{
|
||||
if s.ends_with('/') {
|
||||
s.len() - 1
|
||||
} else {
|
||||
s.len()
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
Some(min(p_len, len))
|
||||
}
|
||||
PatternType::DynamicSet(ref re, ref params) => {
|
||||
if let Some(idx) = re.matches(path).into_iter().next() {
|
||||
let (ref pattern, _, len) = params[idx];
|
||||
if let Some(captures) = pattern.captures(path) {
|
||||
let mut pos = 0;
|
||||
let mut passed = false;
|
||||
for capture in captures.iter() {
|
||||
if let Some(ref m) = capture {
|
||||
if !passed {
|
||||
passed = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
pos = m.end();
|
||||
}
|
||||
}
|
||||
Some(pos + len)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Is the given path and parameters a match against this pattern.
|
||||
pub fn match_path<T: ResourcePath>(&self, path: &mut Path<T>) -> bool {
|
||||
match self.tp {
|
||||
PatternType::Static(ref s) => {
|
||||
if s == path.path() {
|
||||
path.skip(path.len() as u16);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
PatternType::Prefix(ref s) => {
|
||||
let r_path = path.path();
|
||||
let len = if s == r_path {
|
||||
s.len()
|
||||
} else if r_path.starts_with(s)
|
||||
&& (s.ends_with('/') || r_path.split_at(s.len()).1.starts_with('/'))
|
||||
{
|
||||
if s.ends_with('/') {
|
||||
s.len() - 1
|
||||
} else {
|
||||
s.len()
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
let r_path_len = r_path.len();
|
||||
path.skip(min(r_path_len, len) as u16);
|
||||
true
|
||||
}
|
||||
PatternType::Dynamic(ref re, ref names, len) => {
|
||||
let mut idx = 0;
|
||||
let mut pos = 0;
|
||||
let mut segments: [PathItem; MAX_DYNAMIC_SEGMENTS] =
|
||||
[PathItem::Static(""); MAX_DYNAMIC_SEGMENTS];
|
||||
|
||||
if let Some(captures) = re.captures(path.path()) {
|
||||
for (no, name) in names.iter().enumerate() {
|
||||
if let Some(m) = captures.name(&name) {
|
||||
idx += 1;
|
||||
pos = m.end();
|
||||
segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16);
|
||||
} else {
|
||||
log::error!(
|
||||
"Dynamic path match but not all segments found: {}",
|
||||
name
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
for idx in 0..idx {
|
||||
path.add(names[idx], segments[idx]);
|
||||
}
|
||||
path.skip((pos + len) as u16);
|
||||
true
|
||||
}
|
||||
PatternType::DynamicSet(ref re, ref params) => {
|
||||
if let Some(idx) = re.matches(path.path()).into_iter().next() {
|
||||
let (ref pattern, ref names, len) = params[idx];
|
||||
let mut idx = 0;
|
||||
let mut pos = 0;
|
||||
let mut segments: [PathItem; MAX_DYNAMIC_SEGMENTS] =
|
||||
[PathItem::Static(""); MAX_DYNAMIC_SEGMENTS];
|
||||
|
||||
if let Some(captures) = pattern.captures(path.path()) {
|
||||
for (no, name) in names.iter().enumerate() {
|
||||
if let Some(m) = captures.name(&name) {
|
||||
idx += 1;
|
||||
pos = m.end();
|
||||
segments[no] =
|
||||
PathItem::Segment(m.start() as u16, m.end() as u16);
|
||||
} else {
|
||||
log::error!(
|
||||
"Dynamic path match but not all segments found: {}",
|
||||
name
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
for idx in 0..idx {
|
||||
path.add(names[idx], segments[idx]);
|
||||
}
|
||||
path.skip((pos + len) as u16);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Is the given path and parameters a match against this pattern?
|
||||
pub fn match_path_checked<R, T, F, U>(
|
||||
&self,
|
||||
res: &mut R,
|
||||
check: &F,
|
||||
user_data: &Option<U>,
|
||||
) -> bool
|
||||
where
|
||||
T: ResourcePath,
|
||||
R: Resource<T>,
|
||||
F: Fn(&R, &Option<U>) -> bool,
|
||||
{
|
||||
match self.tp {
|
||||
PatternType::Static(ref s) => {
|
||||
if s == res.resource_path().path() && check(res, user_data) {
|
||||
let path = res.resource_path();
|
||||
path.skip(path.len() as u16);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
PatternType::Prefix(ref s) => {
|
||||
let len = {
|
||||
let r_path = res.resource_path().path();
|
||||
if s == r_path {
|
||||
s.len()
|
||||
} else if r_path.starts_with(s)
|
||||
&& (s.ends_with('/') || r_path.split_at(s.len()).1.starts_with('/'))
|
||||
{
|
||||
if s.ends_with('/') {
|
||||
s.len() - 1
|
||||
} else {
|
||||
s.len()
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
if !check(res, user_data) {
|
||||
return false;
|
||||
}
|
||||
let path = res.resource_path();
|
||||
path.skip(min(path.path().len(), len) as u16);
|
||||
true
|
||||
}
|
||||
PatternType::Dynamic(ref re, ref names, len) => {
|
||||
let mut idx = 0;
|
||||
let mut pos = 0;
|
||||
let mut segments: [PathItem; MAX_DYNAMIC_SEGMENTS] =
|
||||
[PathItem::Static(""); MAX_DYNAMIC_SEGMENTS];
|
||||
|
||||
if let Some(captures) = re.captures(res.resource_path().path()) {
|
||||
for (no, name) in names.iter().enumerate() {
|
||||
if let Some(m) = captures.name(&name) {
|
||||
idx += 1;
|
||||
pos = m.end();
|
||||
segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16);
|
||||
} else {
|
||||
log::error!(
|
||||
"Dynamic path match but not all segments found: {}",
|
||||
name
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !check(res, user_data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let path = res.resource_path();
|
||||
for idx in 0..idx {
|
||||
path.add(names[idx], segments[idx]);
|
||||
}
|
||||
path.skip((pos + len) as u16);
|
||||
true
|
||||
}
|
||||
PatternType::DynamicSet(ref re, ref params) => {
|
||||
let path = res.resource_path().path();
|
||||
if let Some(idx) = re.matches(path).into_iter().next() {
|
||||
let (ref pattern, ref names, len) = params[idx];
|
||||
let mut idx = 0;
|
||||
let mut pos = 0;
|
||||
let mut segments: [PathItem; MAX_DYNAMIC_SEGMENTS] =
|
||||
[PathItem::Static(""); MAX_DYNAMIC_SEGMENTS];
|
||||
|
||||
if let Some(captures) = pattern.captures(path) {
|
||||
for (no, name) in names.iter().enumerate() {
|
||||
if let Some(m) = captures.name(&name) {
|
||||
idx += 1;
|
||||
pos = m.end();
|
||||
segments[no] =
|
||||
PathItem::Segment(m.start() as u16, m.end() as u16);
|
||||
} else {
|
||||
log::error!(
|
||||
"Dynamic path match but not all segments found: {}",
|
||||
name
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !check(res, user_data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let path = res.resource_path();
|
||||
for idx in 0..idx {
|
||||
path.add(names[idx], segments[idx]);
|
||||
}
|
||||
path.skip((pos + len) as u16);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Build resource path from elements. Returns `true` on success.
|
||||
pub fn resource_path<U, I>(&self, path: &mut String, elements: &mut U) -> bool
|
||||
where
|
||||
U: Iterator<Item = I>,
|
||||
I: AsRef<str>,
|
||||
{
|
||||
match self.tp {
|
||||
PatternType::Prefix(ref p) => path.push_str(p),
|
||||
PatternType::Static(ref p) => path.push_str(p),
|
||||
PatternType::Dynamic(..) => {
|
||||
for el in &self.elements {
|
||||
match *el {
|
||||
PatternElement::Str(ref s) => path.push_str(s),
|
||||
PatternElement::Var(_) => {
|
||||
if let Some(val) = elements.next() {
|
||||
path.push_str(val.as_ref())
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
PatternType::DynamicSet(..) => {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Build resource path from elements. Returns `true` on success.
|
||||
pub fn resource_path_named<K, V, S>(
|
||||
&self,
|
||||
path: &mut String,
|
||||
elements: &HashMap<K, V, S>,
|
||||
) -> bool
|
||||
where
|
||||
K: std::borrow::Borrow<str> + Eq + Hash,
|
||||
V: AsRef<str>,
|
||||
S: std::hash::BuildHasher,
|
||||
{
|
||||
match self.tp {
|
||||
PatternType::Prefix(ref p) => path.push_str(p),
|
||||
PatternType::Static(ref p) => path.push_str(p),
|
||||
PatternType::Dynamic(..) => {
|
||||
for el in &self.elements {
|
||||
match *el {
|
||||
PatternElement::Str(ref s) => path.push_str(s),
|
||||
PatternElement::Var(ref name) => {
|
||||
if let Some(val) = elements.get(name) {
|
||||
path.push_str(val.as_ref())
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
PatternType::DynamicSet(..) => {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn parse_param(pattern: &str) -> (PatternElement, String, &str, bool) {
|
||||
const DEFAULT_PATTERN: &str = "[^/]+";
|
||||
const DEFAULT_PATTERN_TAIL: &str = ".*";
|
||||
let mut params_nesting = 0usize;
|
||||
let close_idx = pattern
|
||||
.find(|c| match c {
|
||||
'{' => {
|
||||
params_nesting += 1;
|
||||
false
|
||||
}
|
||||
'}' => {
|
||||
params_nesting -= 1;
|
||||
params_nesting == 0
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
.expect("malformed dynamic segment");
|
||||
let (mut param, mut rem) = pattern.split_at(close_idx + 1);
|
||||
param = ¶m[1..param.len() - 1]; // Remove outer brackets
|
||||
let tail = rem == "*";
|
||||
|
||||
let (name, pattern) = match param.find(':') {
|
||||
Some(idx) => {
|
||||
if tail {
|
||||
panic!("Custom regex is not supported for remainder match");
|
||||
}
|
||||
let (name, pattern) = param.split_at(idx);
|
||||
(name, &pattern[1..])
|
||||
}
|
||||
None => (
|
||||
param,
|
||||
if tail {
|
||||
rem = &rem[1..];
|
||||
DEFAULT_PATTERN_TAIL
|
||||
} else {
|
||||
DEFAULT_PATTERN
|
||||
},
|
||||
),
|
||||
};
|
||||
(
|
||||
PatternElement::Var(name.to_string()),
|
||||
format!(r"(?P<{}>{})", &name, &pattern),
|
||||
rem,
|
||||
tail,
|
||||
)
|
||||
}
|
||||
|
||||
fn parse(
|
||||
mut pattern: &str,
|
||||
mut for_prefix: bool,
|
||||
) -> (String, Vec<PatternElement>, bool, usize) {
|
||||
if pattern.find('{').is_none() {
|
||||
// TODO: MSRV: 1.45
|
||||
#[allow(clippy::manual_strip)]
|
||||
return if pattern.ends_with('*') {
|
||||
let path = &pattern[..pattern.len() - 1];
|
||||
let re = String::from("^") + path + "(.*)";
|
||||
(re, vec![PatternElement::Str(String::from(path))], true, 0)
|
||||
} else {
|
||||
(
|
||||
String::from(pattern),
|
||||
vec![PatternElement::Str(String::from(pattern))],
|
||||
false,
|
||||
pattern.chars().count(),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
let mut elements = Vec::new();
|
||||
let mut re = String::from("^");
|
||||
let mut dyn_elements = 0;
|
||||
|
||||
while let Some(idx) = pattern.find('{') {
|
||||
let (prefix, rem) = pattern.split_at(idx);
|
||||
elements.push(PatternElement::Str(String::from(prefix)));
|
||||
re.push_str(&escape(prefix));
|
||||
let (param_pattern, re_part, rem, tail) = Self::parse_param(rem);
|
||||
if tail {
|
||||
for_prefix = true;
|
||||
}
|
||||
|
||||
elements.push(param_pattern);
|
||||
re.push_str(&re_part);
|
||||
pattern = rem;
|
||||
dyn_elements += 1;
|
||||
}
|
||||
|
||||
elements.push(PatternElement::Str(String::from(pattern)));
|
||||
re.push_str(&escape(pattern));
|
||||
|
||||
if dyn_elements > MAX_DYNAMIC_SEGMENTS {
|
||||
panic!(
|
||||
"Only {} dynamic segments are allowed, provided: {}",
|
||||
MAX_DYNAMIC_SEGMENTS, dyn_elements
|
||||
);
|
||||
}
|
||||
|
||||
if !for_prefix {
|
||||
re.push('$');
|
||||
}
|
||||
(re, elements, true, pattern.chars().count())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for ResourceDef {}
|
||||
|
||||
impl PartialEq for ResourceDef {
|
||||
fn eq(&self, other: &ResourceDef) -> bool {
|
||||
self.pattern == other.pattern
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for ResourceDef {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.pattern.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for ResourceDef {
|
||||
fn from(path: &'a str) -> ResourceDef {
|
||||
ResourceDef::new(path)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for ResourceDef {
|
||||
fn from(path: String) -> ResourceDef {
|
||||
ResourceDef::new(path)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn insert_slash(path: &str) -> String {
|
||||
let mut path = path.to_owned();
|
||||
if !path.is_empty() && !path.starts_with('/') {
|
||||
path.insert(0, '/');
|
||||
};
|
||||
path
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use http::Uri;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
#[test]
|
||||
fn test_parse_static() {
|
||||
let re = ResourceDef::new("/");
|
||||
assert!(re.is_match("/"));
|
||||
assert!(!re.is_match("/a"));
|
||||
|
||||
let re = ResourceDef::new("/name");
|
||||
assert!(re.is_match("/name"));
|
||||
assert!(!re.is_match("/name1"));
|
||||
assert!(!re.is_match("/name/"));
|
||||
assert!(!re.is_match("/name~"));
|
||||
|
||||
assert_eq!(re.is_prefix_match("/name"), Some(5));
|
||||
assert_eq!(re.is_prefix_match("/name1"), None);
|
||||
assert_eq!(re.is_prefix_match("/name/"), None);
|
||||
assert_eq!(re.is_prefix_match("/name~"), None);
|
||||
|
||||
let re = ResourceDef::new("/name/");
|
||||
assert!(re.is_match("/name/"));
|
||||
assert!(!re.is_match("/name"));
|
||||
assert!(!re.is_match("/name/gs"));
|
||||
|
||||
let re = ResourceDef::new("/user/profile");
|
||||
assert!(re.is_match("/user/profile"));
|
||||
assert!(!re.is_match("/user/profile/profile"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_param() {
|
||||
let re = ResourceDef::new("/user/{id}");
|
||||
assert!(re.is_match("/user/profile"));
|
||||
assert!(re.is_match("/user/2345"));
|
||||
assert!(!re.is_match("/user/2345/"));
|
||||
assert!(!re.is_match("/user/2345/sdg"));
|
||||
|
||||
let mut path = Path::new("/user/profile");
|
||||
assert!(re.match_path(&mut path));
|
||||
assert_eq!(path.get("id").unwrap(), "profile");
|
||||
|
||||
let mut path = Path::new("/user/1245125");
|
||||
assert!(re.match_path(&mut path));
|
||||
assert_eq!(path.get("id").unwrap(), "1245125");
|
||||
|
||||
let re = ResourceDef::new("/v{version}/resource/{id}");
|
||||
assert!(re.is_match("/v1/resource/320120"));
|
||||
assert!(!re.is_match("/v/resource/1"));
|
||||
assert!(!re.is_match("/resource"));
|
||||
|
||||
let mut path = Path::new("/v151/resource/adage32");
|
||||
assert!(re.match_path(&mut path));
|
||||
assert_eq!(path.get("version").unwrap(), "151");
|
||||
assert_eq!(path.get("id").unwrap(), "adage32");
|
||||
|
||||
let re = ResourceDef::new("/{id:[[:digit:]]{6}}");
|
||||
assert!(re.is_match("/012345"));
|
||||
assert!(!re.is_match("/012"));
|
||||
assert!(!re.is_match("/01234567"));
|
||||
assert!(!re.is_match("/XXXXXX"));
|
||||
|
||||
let mut path = Path::new("/012345");
|
||||
assert!(re.match_path(&mut path));
|
||||
assert_eq!(path.get("id").unwrap(), "012345");
|
||||
}
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
#[test]
|
||||
fn test_dynamic_set() {
|
||||
let re = ResourceDef::new(vec![
|
||||
"/user/{id}",
|
||||
"/v{version}/resource/{id}",
|
||||
"/{id:[[:digit:]]{6}}",
|
||||
]);
|
||||
assert!(re.is_match("/user/profile"));
|
||||
assert!(re.is_match("/user/2345"));
|
||||
assert!(!re.is_match("/user/2345/"));
|
||||
assert!(!re.is_match("/user/2345/sdg"));
|
||||
|
||||
let mut path = Path::new("/user/profile");
|
||||
assert!(re.match_path(&mut path));
|
||||
assert_eq!(path.get("id").unwrap(), "profile");
|
||||
|
||||
let mut path = Path::new("/user/1245125");
|
||||
assert!(re.match_path(&mut path));
|
||||
assert_eq!(path.get("id").unwrap(), "1245125");
|
||||
|
||||
assert!(re.is_match("/v1/resource/320120"));
|
||||
assert!(!re.is_match("/v/resource/1"));
|
||||
assert!(!re.is_match("/resource"));
|
||||
|
||||
let mut path = Path::new("/v151/resource/adage32");
|
||||
assert!(re.match_path(&mut path));
|
||||
assert_eq!(path.get("version").unwrap(), "151");
|
||||
assert_eq!(path.get("id").unwrap(), "adage32");
|
||||
|
||||
assert!(re.is_match("/012345"));
|
||||
assert!(!re.is_match("/012"));
|
||||
assert!(!re.is_match("/01234567"));
|
||||
assert!(!re.is_match("/XXXXXX"));
|
||||
|
||||
let mut path = Path::new("/012345");
|
||||
assert!(re.match_path(&mut path));
|
||||
assert_eq!(path.get("id").unwrap(), "012345");
|
||||
|
||||
let re = ResourceDef::new([
|
||||
"/user/{id}",
|
||||
"/v{version}/resource/{id}",
|
||||
"/{id:[[:digit:]]{6}}",
|
||||
]);
|
||||
assert!(re.is_match("/user/profile"));
|
||||
assert!(re.is_match("/user/2345"));
|
||||
assert!(!re.is_match("/user/2345/"));
|
||||
assert!(!re.is_match("/user/2345/sdg"));
|
||||
|
||||
let re = ResourceDef::new([
|
||||
"/user/{id}".to_string(),
|
||||
"/v{version}/resource/{id}".to_string(),
|
||||
"/{id:[[:digit:]]{6}}".to_string(),
|
||||
]);
|
||||
assert!(re.is_match("/user/profile"));
|
||||
assert!(re.is_match("/user/2345"));
|
||||
assert!(!re.is_match("/user/2345/"));
|
||||
assert!(!re.is_match("/user/2345/sdg"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_tail() {
|
||||
let re = ResourceDef::new("/user/-{id}*");
|
||||
|
||||
let mut path = Path::new("/user/-profile");
|
||||
assert!(re.match_path(&mut path));
|
||||
assert_eq!(path.get("id").unwrap(), "profile");
|
||||
|
||||
let mut path = Path::new("/user/-2345");
|
||||
assert!(re.match_path(&mut path));
|
||||
assert_eq!(path.get("id").unwrap(), "2345");
|
||||
|
||||
let mut path = Path::new("/user/-2345/");
|
||||
assert!(re.match_path(&mut path));
|
||||
assert_eq!(path.get("id").unwrap(), "2345/");
|
||||
|
||||
let mut path = Path::new("/user/-2345/sdg");
|
||||
assert!(re.match_path(&mut path));
|
||||
assert_eq!(path.get("id").unwrap(), "2345/sdg");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_static_tail() {
|
||||
let re = ResourceDef::new("/user*");
|
||||
assert!(re.is_match("/user/profile"));
|
||||
assert!(re.is_match("/user/2345"));
|
||||
assert!(re.is_match("/user/2345/"));
|
||||
assert!(re.is_match("/user/2345/sdg"));
|
||||
|
||||
let re = ResourceDef::new("/user/*");
|
||||
assert!(re.is_match("/user/profile"));
|
||||
assert!(re.is_match("/user/2345"));
|
||||
assert!(re.is_match("/user/2345/"));
|
||||
assert!(re.is_match("/user/2345/sdg"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_urlencoded_param() {
|
||||
let re = ResourceDef::new("/user/{id}/test");
|
||||
|
||||
let mut path = Path::new("/user/2345/test");
|
||||
assert!(re.match_path(&mut path));
|
||||
assert_eq!(path.get("id").unwrap(), "2345");
|
||||
|
||||
let mut path = Path::new("/user/qwe%25/test");
|
||||
assert!(re.match_path(&mut path));
|
||||
assert_eq!(path.get("id").unwrap(), "qwe%25");
|
||||
|
||||
let uri = Uri::try_from("/user/qwe%25/test").unwrap();
|
||||
let mut path = Path::new(uri);
|
||||
assert!(re.match_path(&mut path));
|
||||
assert_eq!(path.get("id").unwrap(), "qwe%25");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resource_prefix() {
|
||||
let re = ResourceDef::prefix("/name");
|
||||
assert!(re.is_match("/name"));
|
||||
assert!(re.is_match("/name/"));
|
||||
assert!(re.is_match("/name/test/test"));
|
||||
assert!(re.is_match("/name1"));
|
||||
assert!(re.is_match("/name~"));
|
||||
|
||||
assert_eq!(re.is_prefix_match("/name"), Some(5));
|
||||
assert_eq!(re.is_prefix_match("/name/"), Some(5));
|
||||
assert_eq!(re.is_prefix_match("/name/test/test"), Some(5));
|
||||
assert_eq!(re.is_prefix_match("/name1"), None);
|
||||
assert_eq!(re.is_prefix_match("/name~"), None);
|
||||
|
||||
let re = ResourceDef::prefix("/name/");
|
||||
assert!(re.is_match("/name/"));
|
||||
assert!(re.is_match("/name/gs"));
|
||||
assert!(!re.is_match("/name"));
|
||||
|
||||
let re = ResourceDef::root_prefix("name/");
|
||||
assert!(re.is_match("/name/"));
|
||||
assert!(re.is_match("/name/gs"));
|
||||
assert!(!re.is_match("/name"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resource_prefix_dynamic() {
|
||||
let re = ResourceDef::prefix("/{name}/");
|
||||
assert!(re.is_match("/name/"));
|
||||
assert!(re.is_match("/name/gs"));
|
||||
assert!(!re.is_match("/name"));
|
||||
|
||||
assert_eq!(re.is_prefix_match("/name/"), Some(6));
|
||||
assert_eq!(re.is_prefix_match("/name/gs"), Some(6));
|
||||
assert_eq!(re.is_prefix_match("/name"), None);
|
||||
|
||||
let mut path = Path::new("/test2/");
|
||||
assert!(re.match_path(&mut path));
|
||||
assert_eq!(&path["name"], "test2");
|
||||
assert_eq!(&path[0], "test2");
|
||||
|
||||
let mut path = Path::new("/test2/subpath1/subpath2/index.html");
|
||||
assert!(re.match_path(&mut path));
|
||||
assert_eq!(&path["name"], "test2");
|
||||
assert_eq!(&path[0], "test2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resource_path() {
|
||||
let mut s = String::new();
|
||||
let resource = ResourceDef::new("/user/{item1}/test");
|
||||
assert!(resource.resource_path(&mut s, &mut (&["user1"]).iter()));
|
||||
assert_eq!(s, "/user/user1/test");
|
||||
|
||||
let mut s = String::new();
|
||||
let resource = ResourceDef::new("/user/{item1}/{item2}/test");
|
||||
assert!(resource.resource_path(&mut s, &mut (&["item", "item2"]).iter()));
|
||||
assert_eq!(s, "/user/item/item2/test");
|
||||
|
||||
let mut s = String::new();
|
||||
let resource = ResourceDef::new("/user/{item1}/{item2}");
|
||||
assert!(resource.resource_path(&mut s, &mut (&["item", "item2"]).iter()));
|
||||
assert_eq!(s, "/user/item/item2");
|
||||
|
||||
let mut s = String::new();
|
||||
let resource = ResourceDef::new("/user/{item1}/{item2}/");
|
||||
assert!(resource.resource_path(&mut s, &mut (&["item", "item2"]).iter()));
|
||||
assert_eq!(s, "/user/item/item2/");
|
||||
|
||||
let mut s = String::new();
|
||||
assert!(!resource.resource_path(&mut s, &mut (&["item"]).iter()));
|
||||
|
||||
let mut s = String::new();
|
||||
assert!(resource.resource_path(&mut s, &mut (&["item", "item2"]).iter()));
|
||||
assert_eq!(s, "/user/item/item2/");
|
||||
assert!(!resource.resource_path(&mut s, &mut (&["item"]).iter()));
|
||||
|
||||
let mut s = String::new();
|
||||
assert!(resource.resource_path(&mut s, &mut vec!["item", "item2"].into_iter()));
|
||||
assert_eq!(s, "/user/item/item2/");
|
||||
|
||||
let mut map = HashMap::new();
|
||||
map.insert("item1", "item");
|
||||
|
||||
let mut s = String::new();
|
||||
assert!(!resource.resource_path_named(&mut s, &map));
|
||||
|
||||
let mut s = String::new();
|
||||
map.insert("item2", "item2");
|
||||
assert!(resource.resource_path_named(&mut s, &map));
|
||||
assert_eq!(s, "/user/item/item2/");
|
||||
}
|
||||
}
|
@ -1,259 +0,0 @@
|
||||
use crate::{IntoPattern, Resource, ResourceDef, ResourcePath};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct ResourceId(pub u16);
|
||||
|
||||
/// Information about current resource
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ResourceInfo {
|
||||
resource: ResourceId,
|
||||
}
|
||||
|
||||
/// Resource router.
|
||||
pub struct Router<T, U = ()>(Vec<(ResourceDef, T, Option<U>)>);
|
||||
|
||||
impl<T, U> Router<T, U> {
|
||||
pub fn build() -> RouterBuilder<T, U> {
|
||||
RouterBuilder {
|
||||
resources: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recognize<R, P>(&self, resource: &mut R) -> Option<(&T, ResourceId)>
|
||||
where
|
||||
R: Resource<P>,
|
||||
P: ResourcePath,
|
||||
{
|
||||
for item in self.0.iter() {
|
||||
if item.0.match_path(resource.resource_path()) {
|
||||
return Some((&item.1, ResourceId(item.0.id())));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn recognize_mut<R, P>(&mut self, resource: &mut R) -> Option<(&mut T, ResourceId)>
|
||||
where
|
||||
R: Resource<P>,
|
||||
P: ResourcePath,
|
||||
{
|
||||
for item in self.0.iter_mut() {
|
||||
if item.0.match_path(resource.resource_path()) {
|
||||
return Some((&mut item.1, ResourceId(item.0.id())));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn recognize_checked<R, P, F>(
|
||||
&self,
|
||||
resource: &mut R,
|
||||
check: F,
|
||||
) -> Option<(&T, ResourceId)>
|
||||
where
|
||||
F: Fn(&R, &Option<U>) -> bool,
|
||||
R: Resource<P>,
|
||||
P: ResourcePath,
|
||||
{
|
||||
for item in self.0.iter() {
|
||||
if item.0.match_path_checked(resource, &check, &item.2) {
|
||||
return Some((&item.1, ResourceId(item.0.id())));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn recognize_mut_checked<R, P, F>(
|
||||
&mut self,
|
||||
resource: &mut R,
|
||||
check: F,
|
||||
) -> Option<(&mut T, ResourceId)>
|
||||
where
|
||||
F: Fn(&R, &Option<U>) -> bool,
|
||||
R: Resource<P>,
|
||||
P: ResourcePath,
|
||||
{
|
||||
for item in self.0.iter_mut() {
|
||||
if item.0.match_path_checked(resource, &check, &item.2) {
|
||||
return Some((&mut item.1, ResourceId(item.0.id())));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RouterBuilder<T, U = ()> {
|
||||
resources: Vec<(ResourceDef, T, Option<U>)>,
|
||||
}
|
||||
|
||||
impl<T, U> RouterBuilder<T, U> {
|
||||
/// Register resource for specified path.
|
||||
pub fn path<P: IntoPattern>(
|
||||
&mut self,
|
||||
path: P,
|
||||
resource: T,
|
||||
) -> &mut (ResourceDef, T, Option<U>) {
|
||||
self.resources
|
||||
.push((ResourceDef::new(path), resource, None));
|
||||
self.resources.last_mut().unwrap()
|
||||
}
|
||||
|
||||
/// Register resource for specified path prefix.
|
||||
pub fn prefix(&mut self, prefix: &str, resource: T) -> &mut (ResourceDef, T, Option<U>) {
|
||||
self.resources
|
||||
.push((ResourceDef::prefix(prefix), resource, None));
|
||||
self.resources.last_mut().unwrap()
|
||||
}
|
||||
|
||||
/// Register resource for ResourceDef
|
||||
pub fn rdef(&mut self, rdef: ResourceDef, resource: T) -> &mut (ResourceDef, T, Option<U>) {
|
||||
self.resources.push((rdef, resource, None));
|
||||
self.resources.last_mut().unwrap()
|
||||
}
|
||||
|
||||
/// Finish configuration and create router instance.
|
||||
pub fn finish(self) -> Router<T, U> {
|
||||
Router(self.resources)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::path::Path;
|
||||
use crate::router::{ResourceId, Router};
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
#[test]
|
||||
fn test_recognizer_1() {
|
||||
let mut router = Router::<usize>::build();
|
||||
router.path("/name", 10).0.set_id(0);
|
||||
router.path("/name/{val}", 11).0.set_id(1);
|
||||
router.path("/name/{val}/index.html", 12).0.set_id(2);
|
||||
router.path("/file/{file}.{ext}", 13).0.set_id(3);
|
||||
router.path("/v{val}/{val2}/index.html", 14).0.set_id(4);
|
||||
router.path("/v/{tail:.*}", 15).0.set_id(5);
|
||||
router.path("/test2/{test}.html", 16).0.set_id(6);
|
||||
router.path("/{test}/index.html", 17).0.set_id(7);
|
||||
let mut router = router.finish();
|
||||
|
||||
let mut path = Path::new("/unknown");
|
||||
assert!(router.recognize_mut(&mut path).is_none());
|
||||
|
||||
let mut path = Path::new("/name");
|
||||
let (h, info) = router.recognize_mut(&mut path).unwrap();
|
||||
assert_eq!(*h, 10);
|
||||
assert_eq!(info, ResourceId(0));
|
||||
assert!(path.is_empty());
|
||||
|
||||
let mut path = Path::new("/name/value");
|
||||
let (h, info) = router.recognize_mut(&mut path).unwrap();
|
||||
assert_eq!(*h, 11);
|
||||
assert_eq!(info, ResourceId(1));
|
||||
assert_eq!(path.get("val").unwrap(), "value");
|
||||
assert_eq!(&path["val"], "value");
|
||||
|
||||
let mut path = Path::new("/name/value2/index.html");
|
||||
let (h, info) = router.recognize_mut(&mut path).unwrap();
|
||||
assert_eq!(*h, 12);
|
||||
assert_eq!(info, ResourceId(2));
|
||||
assert_eq!(path.get("val").unwrap(), "value2");
|
||||
|
||||
let mut path = Path::new("/file/file.gz");
|
||||
let (h, info) = router.recognize_mut(&mut path).unwrap();
|
||||
assert_eq!(*h, 13);
|
||||
assert_eq!(info, ResourceId(3));
|
||||
assert_eq!(path.get("file").unwrap(), "file");
|
||||
assert_eq!(path.get("ext").unwrap(), "gz");
|
||||
|
||||
let mut path = Path::new("/vtest/ttt/index.html");
|
||||
let (h, info) = router.recognize_mut(&mut path).unwrap();
|
||||
assert_eq!(*h, 14);
|
||||
assert_eq!(info, ResourceId(4));
|
||||
assert_eq!(path.get("val").unwrap(), "test");
|
||||
assert_eq!(path.get("val2").unwrap(), "ttt");
|
||||
|
||||
let mut path = Path::new("/v/blah-blah/index.html");
|
||||
let (h, info) = router.recognize_mut(&mut path).unwrap();
|
||||
assert_eq!(*h, 15);
|
||||
assert_eq!(info, ResourceId(5));
|
||||
assert_eq!(path.get("tail").unwrap(), "blah-blah/index.html");
|
||||
|
||||
let mut path = Path::new("/test2/index.html");
|
||||
let (h, info) = router.recognize_mut(&mut path).unwrap();
|
||||
assert_eq!(*h, 16);
|
||||
assert_eq!(info, ResourceId(6));
|
||||
assert_eq!(path.get("test").unwrap(), "index");
|
||||
|
||||
let mut path = Path::new("/bbb/index.html");
|
||||
let (h, info) = router.recognize_mut(&mut path).unwrap();
|
||||
assert_eq!(*h, 17);
|
||||
assert_eq!(info, ResourceId(7));
|
||||
assert_eq!(path.get("test").unwrap(), "bbb");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_recognizer_2() {
|
||||
let mut router = Router::<usize>::build();
|
||||
router.path("/index.json", 10);
|
||||
router.path("/{source}.json", 11);
|
||||
let mut router = router.finish();
|
||||
|
||||
let mut path = Path::new("/index.json");
|
||||
let (h, _) = router.recognize_mut(&mut path).unwrap();
|
||||
assert_eq!(*h, 10);
|
||||
|
||||
let mut path = Path::new("/test.json");
|
||||
let (h, _) = router.recognize_mut(&mut path).unwrap();
|
||||
assert_eq!(*h, 11);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_recognizer_with_prefix() {
|
||||
let mut router = Router::<usize>::build();
|
||||
router.path("/name", 10).0.set_id(0);
|
||||
router.path("/name/{val}", 11).0.set_id(1);
|
||||
let mut router = router.finish();
|
||||
|
||||
let mut path = Path::new("/name");
|
||||
path.skip(5);
|
||||
assert!(router.recognize_mut(&mut path).is_none());
|
||||
|
||||
let mut path = Path::new("/test/name");
|
||||
path.skip(5);
|
||||
let (h, _) = router.recognize_mut(&mut path).unwrap();
|
||||
assert_eq!(*h, 10);
|
||||
|
||||
let mut path = Path::new("/test/name/value");
|
||||
path.skip(5);
|
||||
let (h, id) = router.recognize_mut(&mut path).unwrap();
|
||||
assert_eq!(*h, 11);
|
||||
assert_eq!(id, ResourceId(1));
|
||||
assert_eq!(path.get("val").unwrap(), "value");
|
||||
assert_eq!(&path["val"], "value");
|
||||
|
||||
// same patterns
|
||||
let mut router = Router::<usize>::build();
|
||||
router.path("/name", 10);
|
||||
router.path("/name/{val}", 11);
|
||||
let mut router = router.finish();
|
||||
|
||||
let mut path = Path::new("/name");
|
||||
path.skip(6);
|
||||
assert!(router.recognize_mut(&mut path).is_none());
|
||||
|
||||
let mut path = Path::new("/test2/name");
|
||||
path.skip(6);
|
||||
let (h, _) = router.recognize_mut(&mut path).unwrap();
|
||||
assert_eq!(*h, 10);
|
||||
|
||||
let mut path = Path::new("/test2/name-test");
|
||||
path.skip(6);
|
||||
assert!(router.recognize_mut(&mut path).is_none());
|
||||
|
||||
let mut path = Path::new("/test2/name/ttt");
|
||||
path.skip(6);
|
||||
let (h, _) = router.recognize_mut(&mut path).unwrap();
|
||||
assert_eq!(*h, 11);
|
||||
assert_eq!(&path["val"], "ttt");
|
||||
}
|
||||
}
|
@ -1,249 +0,0 @@
|
||||
use crate::ResourcePath;
|
||||
|
||||
#[allow(dead_code)]
|
||||
const GEN_DELIMS: &[u8] = b":/?#[]@";
|
||||
#[allow(dead_code)]
|
||||
const SUB_DELIMS_WITHOUT_QS: &[u8] = b"!$'()*,";
|
||||
#[allow(dead_code)]
|
||||
const SUB_DELIMS: &[u8] = b"!$'()*,+?=;";
|
||||
#[allow(dead_code)]
|
||||
const RESERVED: &[u8] = b":/?#[]@!$'()*,+?=;";
|
||||
#[allow(dead_code)]
|
||||
const UNRESERVED: &[u8] = b"abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||
1234567890
|
||||
-._~";
|
||||
const ALLOWED: &[u8] = b"abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||
1234567890
|
||||
-._~
|
||||
!$'()*,";
|
||||
const QS: &[u8] = b"+&=;b";
|
||||
|
||||
#[inline]
|
||||
fn bit_at(array: &[u8], ch: u8) -> bool {
|
||||
array[(ch >> 3) as usize] & (1 << (ch & 7)) != 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_bit(array: &mut [u8], ch: u8) {
|
||||
array[(ch >> 3) as usize] |= 1 << (ch & 7)
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static DEFAULT_QUOTER: Quoter = Quoter::new(b"@:", b"/+");
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct Url {
|
||||
uri: http::Uri,
|
||||
path: Option<String>,
|
||||
}
|
||||
|
||||
impl Url {
|
||||
pub fn new(uri: http::Uri) -> Url {
|
||||
let path = DEFAULT_QUOTER.with(|q| q.requote(uri.path().as_bytes()));
|
||||
|
||||
Url { uri, path }
|
||||
}
|
||||
|
||||
pub fn with_quoter(uri: http::Uri, quoter: &Quoter) -> Url {
|
||||
Url {
|
||||
path: quoter.requote(uri.path().as_bytes()),
|
||||
uri,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uri(&self) -> &http::Uri {
|
||||
&self.uri
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &str {
|
||||
if let Some(ref s) = self.path {
|
||||
s
|
||||
} else {
|
||||
self.uri.path()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn update(&mut self, uri: &http::Uri) {
|
||||
self.uri = uri.clone();
|
||||
self.path = DEFAULT_QUOTER.with(|q| q.requote(uri.path().as_bytes()));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn update_with_quoter(&mut self, uri: &http::Uri, quoter: &Quoter) {
|
||||
self.uri = uri.clone();
|
||||
self.path = quoter.requote(uri.path().as_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourcePath for Url {
|
||||
#[inline]
|
||||
fn path(&self) -> &str {
|
||||
self.path()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Quoter {
|
||||
safe_table: [u8; 16],
|
||||
protected_table: [u8; 16],
|
||||
}
|
||||
|
||||
impl Quoter {
|
||||
pub fn new(safe: &[u8], protected: &[u8]) -> Quoter {
|
||||
let mut q = Quoter {
|
||||
safe_table: [0; 16],
|
||||
protected_table: [0; 16],
|
||||
};
|
||||
|
||||
// prepare safe table
|
||||
for i in 0..128 {
|
||||
if ALLOWED.contains(&i) {
|
||||
set_bit(&mut q.safe_table, i);
|
||||
}
|
||||
if QS.contains(&i) {
|
||||
set_bit(&mut q.safe_table, i);
|
||||
}
|
||||
}
|
||||
|
||||
for ch in safe {
|
||||
set_bit(&mut q.safe_table, *ch)
|
||||
}
|
||||
|
||||
// prepare protected table
|
||||
for ch in protected {
|
||||
set_bit(&mut q.safe_table, *ch);
|
||||
set_bit(&mut q.protected_table, *ch);
|
||||
}
|
||||
|
||||
q
|
||||
}
|
||||
|
||||
pub fn requote(&self, val: &[u8]) -> Option<String> {
|
||||
let mut has_pct = 0;
|
||||
let mut pct = [b'%', 0, 0];
|
||||
let mut idx = 0;
|
||||
let mut cloned: Option<Vec<u8>> = None;
|
||||
|
||||
let len = val.len();
|
||||
while idx < len {
|
||||
let ch = val[idx];
|
||||
|
||||
if has_pct != 0 {
|
||||
pct[has_pct] = val[idx];
|
||||
has_pct += 1;
|
||||
if has_pct == 3 {
|
||||
has_pct = 0;
|
||||
let buf = cloned.as_mut().unwrap();
|
||||
|
||||
if let Some(ch) = restore_ch(pct[1], pct[2]) {
|
||||
if ch < 128 {
|
||||
if bit_at(&self.protected_table, ch) {
|
||||
buf.extend_from_slice(&pct);
|
||||
idx += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if bit_at(&self.safe_table, ch) {
|
||||
buf.push(ch);
|
||||
idx += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
buf.push(ch);
|
||||
} else {
|
||||
buf.extend_from_slice(&pct[..]);
|
||||
}
|
||||
}
|
||||
} else if ch == b'%' {
|
||||
has_pct = 1;
|
||||
if cloned.is_none() {
|
||||
let mut c = Vec::with_capacity(len);
|
||||
c.extend_from_slice(&val[..idx]);
|
||||
cloned = Some(c);
|
||||
}
|
||||
} else if let Some(ref mut cloned) = cloned {
|
||||
cloned.push(ch)
|
||||
}
|
||||
idx += 1;
|
||||
}
|
||||
|
||||
if let Some(data) = cloned {
|
||||
// Unsafe: we get data from http::Uri, which does utf-8 checks already
|
||||
// this code only decodes valid pct encoded values
|
||||
Some(unsafe { String::from_utf8_unchecked(data) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn from_hex(v: u8) -> Option<u8> {
|
||||
if (b'0'..=b'9').contains(&v) {
|
||||
Some(v - 0x30) // ord('0') == 0x30
|
||||
} else if (b'A'..=b'F').contains(&v) {
|
||||
Some(v - 0x41 + 10) // ord('A') == 0x41
|
||||
} else if (b'a'..=b'f').contains(&v) {
|
||||
Some(v - 0x61 + 10) // ord('a') == 0x61
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn restore_ch(d1: u8, d2: u8) -> Option<u8> {
|
||||
from_hex(d1).and_then(|d1| from_hex(d2).map(move |d2| d1 << 4 | d2))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use http::Uri;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use super::*;
|
||||
use crate::{Path, ResourceDef};
|
||||
|
||||
#[test]
|
||||
fn test_parse_url() {
|
||||
let re = ResourceDef::new("/user/{id}/test");
|
||||
|
||||
let url = Uri::try_from("/user/2345/test").unwrap();
|
||||
let mut path = Path::new(Url::new(url));
|
||||
assert!(re.match_path(&mut path));
|
||||
assert_eq!(path.get("id").unwrap(), "2345");
|
||||
|
||||
let url = Uri::try_from("/user/qwe%25/test").unwrap();
|
||||
let mut path = Path::new(Url::new(url));
|
||||
assert!(re.match_path(&mut path));
|
||||
assert_eq!(path.get("id").unwrap(), "qwe%");
|
||||
|
||||
let url = Uri::try_from("/user/qwe%25rty/test").unwrap();
|
||||
let mut path = Path::new(Url::new(url));
|
||||
assert!(re.match_path(&mut path));
|
||||
assert_eq!(path.get("id").unwrap(), "qwe%rty");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_hex() {
|
||||
let hex = b"0123456789abcdefABCDEF";
|
||||
|
||||
for i in 0..256 {
|
||||
let c = i as u8;
|
||||
if hex.contains(&c) {
|
||||
assert!(from_hex(c).is_some())
|
||||
} else {
|
||||
assert!(from_hex(c).is_none())
|
||||
}
|
||||
}
|
||||
|
||||
let expected = [
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 10, 11, 12, 13, 14, 15,
|
||||
];
|
||||
for i in 0..hex.len() {
|
||||
assert_eq!(from_hex(hex[i]).unwrap(), expected[i]);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,192 +1,174 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
## Unreleased
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.71.
|
||||
|
||||
## 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.10.0
|
||||
|
||||
[#274]: https://github.com/actix/actix-net/pull/274
|
||||
[#275]: https://github.com/actix/actix-net/pull/275
|
||||
- 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.
|
||||
|
||||
## 2.9.0
|
||||
|
||||
## 2.0.1 - 2021-02-06
|
||||
* Expose `JoinError` from Tokio. [#271]
|
||||
- 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.
|
||||
|
||||
[#271]: https://github.com/actix/actix-net/pull/271
|
||||
## 2.8.0
|
||||
|
||||
- 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.0.0 - 2021-02-02
|
||||
* Remove all Arbiter-local storage methods. [#262]
|
||||
* Re-export `tokio::pin`. [#262]
|
||||
## 2.7.0
|
||||
|
||||
[#262]: https://github.com/actix/actix-net/pull/262
|
||||
- Update `tokio-uring` dependency to `0.3`.
|
||||
- Minimum supported Rust version (MSRV) is now 1.49.
|
||||
|
||||
## 2.6.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]
|
||||
- Update `tokio-uring` dependency to `0.2`.
|
||||
|
||||
[#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.5.1
|
||||
|
||||
- Expose `System::with_tokio_rt` and `Arbiter::with_tokio_rt`.
|
||||
|
||||
## 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.5.0
|
||||
|
||||
[#245]: https://github.com/actix/actix-net/pull/245
|
||||
- Add `System::run_with_code` to allow retrieving the exit code on stop.
|
||||
|
||||
## 2.4.0
|
||||
|
||||
## 2.0.0-beta.1 - 2020-12-28
|
||||
### Added
|
||||
* Add `System::attach_to_tokio` method. [#173]
|
||||
- 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.
|
||||
|
||||
### Changed
|
||||
* Update `tokio` dependency to `1.0`. [#236]
|
||||
* Rename `time` module `delay_for` to `sleep`, `delay_until` to `sleep_until`, `Delay` to `Sleep`
|
||||
to stay aligned with Tokio's naming. [#236]
|
||||
* Remove `'static` lifetime requirement for `Runtime::block_on` and `SystemRunner::block_on`.
|
||||
* These methods now accept `&self` when calling. [#236]
|
||||
* Remove `'static` lifetime requirement for `System::run` and `Builder::run`. [#236]
|
||||
* `Arbiter::spawn` now panics when `System` is not in scope. [#207]
|
||||
## 2.3.0
|
||||
|
||||
### Fixed
|
||||
* Fix work load issue by removing `PENDING` thread local. [#207]
|
||||
- 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.
|
||||
|
||||
[#207]: https://github.com/actix/actix-net/pull/207
|
||||
[#236]: https://github.com/actix/actix-net/pull/236
|
||||
## 2.2.0
|
||||
|
||||
## [1.1.1] - 2020-04-30
|
||||
- **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.
|
||||
|
||||
### Fixed
|
||||
## 2.1.0
|
||||
|
||||
* Fix memory leak due to [#94] (see [#129] for more detail)
|
||||
- Add `ActixStream` extension trait to include readiness methods.
|
||||
- Re-export `tokio::net::TcpSocket` in `net` module
|
||||
|
||||
[#129]: https://github.com/actix/actix-net/issues/129
|
||||
## 2.0.2
|
||||
|
||||
## [1.1.0] - 2020-04-08
|
||||
- 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.
|
||||
|
||||
**This version has been yanked.**
|
||||
## 2.0.1
|
||||
|
||||
### Added
|
||||
- Expose `JoinError` from Tokio.
|
||||
|
||||
* 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]
|
||||
## 2.0.0
|
||||
|
||||
[#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
|
||||
- Remove all Arbiter-local storage methods.
|
||||
- Re-export `tokio::pin`.
|
||||
|
||||
## [1.0.0] - 2019-12-11
|
||||
## 2.0.0-beta.3
|
||||
|
||||
* Update dependencies
|
||||
- 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.
|
||||
|
||||
## [1.0.0-alpha.3] - 2019-12-07
|
||||
## 2.0.0-beta.2
|
||||
|
||||
### Fixed
|
||||
- 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`.
|
||||
|
||||
* Fix compilation on non-unix platforms
|
||||
## 2.0.0-beta.1
|
||||
|
||||
### Changed
|
||||
- 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.
|
||||
|
||||
* Migrate to tokio 0.2
|
||||
## 1.1.1
|
||||
|
||||
- Fix memory leak due to
|
||||
|
||||
## [1.0.0-alpha.2] - 2019-12-02
|
||||
## 1.1.0 _(YANKED)_
|
||||
|
||||
Added
|
||||
- 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
|
||||
|
||||
* Export `main` and `test` attribute macros
|
||||
## 1.0.0
|
||||
|
||||
* Export `time` module (re-export of tokio-timer)
|
||||
- Update dependencies
|
||||
|
||||
* Export `net` module (re-export of tokio-net)
|
||||
## 1.0.0-alpha.3
|
||||
|
||||
- Migrate to tokio 0.2
|
||||
- Fix compilation on non-unix platforms
|
||||
|
||||
## [1.0.0-alpha.1] - 2019-11-22
|
||||
## 1.0.0-alpha.2
|
||||
|
||||
### Changed
|
||||
- Export `main` and `test` attribute macros
|
||||
- Export `time` module (re-export of tokio-timer)
|
||||
- Export `net` module (re-export of tokio-net)
|
||||
|
||||
* Migrate to std::future and tokio 0.2
|
||||
## 1.0.0-alpha.1
|
||||
|
||||
- Migrate to std::future and tokio 0.2
|
||||
|
||||
## [0.2.6] - 2019-11-14
|
||||
## 0.2.6
|
||||
|
||||
### Fixed
|
||||
- Allow to join arbiter's thread. #60
|
||||
- Fix arbiter's thread panic message.
|
||||
|
||||
* Fix arbiter's thread panic message.
|
||||
## 0.2.5
|
||||
|
||||
### Added
|
||||
- Add arbiter specific storage
|
||||
|
||||
* Allow to join arbiter's thread. #60
|
||||
## 0.2.4
|
||||
|
||||
- Avoid a copy of the Future when initializing the Box. #29
|
||||
|
||||
## [0.2.5] - 2019-09-02
|
||||
## 0.2.3
|
||||
|
||||
### Added
|
||||
- Allow to start System using existing CurrentThread Handle #22
|
||||
|
||||
* Add arbiter specific storage
|
||||
## 0.2.2
|
||||
|
||||
- Moved `blocking` module to `actix-threadpool` crate
|
||||
|
||||
## [0.2.4] - 2019-07-17
|
||||
## 0.2.1
|
||||
|
||||
### Changed
|
||||
- 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
|
||||
|
||||
* Avoid a copy of the Future when initializing the Box. #29
|
||||
## 0.2.0
|
||||
|
||||
- `run` method returns `io::Result<()>`
|
||||
- Removed `Handle`
|
||||
|
||||
## [0.2.3] - 2019-06-22
|
||||
## 0.1.0
|
||||
|
||||
### Added
|
||||
|
||||
* Allow to start System using exsiting CurrentThread Handle #22
|
||||
|
||||
|
||||
## [0.2.2] - 2019-03-28
|
||||
|
||||
### Changed
|
||||
|
||||
* Moved `blocking` module to `actix-threadpool` crate
|
||||
|
||||
|
||||
## [0.2.1] - 2019-03-11
|
||||
|
||||
### Added
|
||||
|
||||
* Added `blocking` module
|
||||
|
||||
* Arbiter::exec_fn - execute fn on the arbiter's thread
|
||||
|
||||
* Arbiter::exec - execute fn on the arbiter's thread and wait result
|
||||
|
||||
|
||||
## [0.2.0] - 2019-03-06
|
||||
|
||||
* `run` method returns `io::Result<()>`
|
||||
|
||||
* Removed `Handle`
|
||||
|
||||
|
||||
## [0.1.0] - 2018-12-09
|
||||
|
||||
* Initial release
|
||||
- Initial release
|
||||
|
@ -1,33 +1,36 @@
|
||||
[package]
|
||||
name = "actix-rt"
|
||||
version = "2.0.2"
|
||||
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"
|
||||
documentation = "https://docs.rs/actix-rt"
|
||||
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"]
|
||||
macros = ["actix-macros"]
|
||||
io-uring = ["tokio-uring"]
|
||||
|
||||
[dependencies]
|
||||
actix-macros = { version = "0.2.0", optional = true }
|
||||
actix-macros = { version = "0.2.3", optional = true }
|
||||
|
||||
futures-core = { version = "0.3", default-features = false }
|
||||
tokio = { version = "1.2", 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.5", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.2", features = ["full"] }
|
||||
hyper = { version = "0.14", default-features = false, features = ["server", "tcp", "http1"] }
|
||||
tokio = { version = "1.23.1", features = ["full"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
@ -2,4 +2,13 @@
|
||||
|
||||
> Tokio-based single-threaded async runtime for the Actix ecosystem.
|
||||
|
||||
[](https://crates.io/crates/actix-rt)
|
||||
[](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.10.0)
|
||||

|
||||
[](https://discord.gg/WghFtEH6Hb)
|
||||
|
||||
See crate documentation for more: https://docs.rs/actix-rt.
|
||||
|
@ -1,28 +0,0 @@
|
||||
use hyper::service::{make_service_fn, service_fn};
|
||||
use hyper::{Body, Request, Response, Server};
|
||||
use std::convert::Infallible;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
async fn handle(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
|
||||
Ok(Response::new(Body::from("Hello World")))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
actix_rt::System::with_tokio_rt(|| {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
})
|
||||
.block_on(async {
|
||||
let make_service =
|
||||
make_service_fn(|_conn| async { Ok::<_, Infallible>(service_fn(handle)) });
|
||||
|
||||
let server =
|
||||
Server::bind(&SocketAddr::from(([127, 0, 0, 1], 3000))).serve(make_service);
|
||||
|
||||
if let Err(e) = server.await {
|
||||
eprintln!("server error: {}", e);
|
||||
}
|
||||
})
|
||||
}
|
60
actix-rt/examples/multi_thread_system.rs
Normal file
60
actix-rt/examples/multi_thread_system.rs
Normal file
@ -0,0 +1,60 @@
|
||||
//! An example on how to build a multi-thread tokio runtime for Actix System.
|
||||
//! Then spawn async task that can make use of work stealing of tokio runtime.
|
||||
|
||||
use actix_rt::System;
|
||||
|
||||
fn main() {
|
||||
System::with_tokio_rt(|| {
|
||||
// build system with a multi-thread tokio runtime.
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(2)
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
})
|
||||
.block_on(async_main());
|
||||
}
|
||||
|
||||
// async main function that acts like #[actix_web::main] or #[tokio::main]
|
||||
async fn async_main() {
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
|
||||
// get a handle to system arbiter and spawn async task on it
|
||||
System::current().arbiter().spawn(async {
|
||||
// use tokio::spawn to get inside the context of multi thread tokio runtime
|
||||
let h1 = tokio::spawn(async {
|
||||
println!("thread id is {:?}", std::thread::current().id());
|
||||
std::thread::sleep(std::time::Duration::from_secs(2));
|
||||
});
|
||||
|
||||
// work stealing occurs for this task spawn
|
||||
let h2 = tokio::spawn(async {
|
||||
println!("thread id is {:?}", std::thread::current().id());
|
||||
});
|
||||
|
||||
h1.await.unwrap();
|
||||
h2.await.unwrap();
|
||||
let _ = tx.send(());
|
||||
});
|
||||
|
||||
rx.await.unwrap();
|
||||
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
let now = std::time::Instant::now();
|
||||
|
||||
// without additional tokio::spawn, all spawned tasks run on single thread
|
||||
System::current().arbiter().spawn(async {
|
||||
println!("thread id is {:?}", std::thread::current().id());
|
||||
std::thread::sleep(std::time::Duration::from_secs(2));
|
||||
let _ = tx.send(());
|
||||
});
|
||||
|
||||
// previous spawn task has blocked the system arbiter thread
|
||||
// so this task will wait for 2 seconds until it can be run
|
||||
System::current().arbiter().spawn(async move {
|
||||
println!("thread id is {:?}", std::thread::current().id());
|
||||
assert!(now.elapsed() > std::time::Duration::from_secs(2));
|
||||
});
|
||||
|
||||
rx.await.unwrap();
|
||||
}
|
@ -9,17 +9,14 @@ use std::{
|
||||
};
|
||||
|
||||
use futures_core::ready;
|
||||
use tokio::{sync::mpsc, task::LocalSet};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use crate::{
|
||||
runtime::{default_tokio_runtime, Runtime},
|
||||
system::{System, SystemCommand},
|
||||
};
|
||||
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 {
|
||||
@ -98,20 +95,21 @@ impl Arbiter {
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if a [System] is not registered on the current thread.
|
||||
#[cfg(not(all(target_os = "linux", feature = "io-uring")))]
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub fn new() -> Arbiter {
|
||||
Self::with_tokio_rt(|| {
|
||||
default_tokio_runtime().expect("Cannot create new Arbiter's Runtime.")
|
||||
crate::runtime::default_tokio_runtime().expect("Cannot create new Arbiter's Runtime.")
|
||||
})
|
||||
}
|
||||
|
||||
/// Spawn a new Arbiter using the [Tokio Runtime](tokio-runtime) returned from a closure.
|
||||
///
|
||||
/// [tokio-runtime]: tokio::runtime::Runtime
|
||||
#[doc(hidden)]
|
||||
#[cfg(not(all(target_os = "linux", feature = "io-uring")))]
|
||||
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();
|
||||
@ -127,7 +125,7 @@ impl Arbiter {
|
||||
.spawn({
|
||||
let tx = tx.clone();
|
||||
move || {
|
||||
let rt = Runtime::from(runtime_factory());
|
||||
let rt = crate::runtime::Runtime::from(runtime_factory());
|
||||
let hnd = ArbiterHandle::new(tx);
|
||||
|
||||
System::set_current(sys);
|
||||
@ -150,24 +148,72 @@ 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();
|
||||
|
||||
Arbiter { tx, thread_handle }
|
||||
}
|
||||
|
||||
/// Sets up an Arbiter runner in a new System using the provided runtime local task set.
|
||||
pub(crate) fn in_new_system(local: &LocalSet) -> ArbiterHandle {
|
||||
/// Spawn a new Arbiter thread and start its event loop with `tokio-uring` runtime.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if a [System] is not registered on the current thread.
|
||||
#[cfg(all(target_os = "linux", feature = "io-uring"))]
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub fn new() -> Arbiter {
|
||||
let sys = System::current();
|
||||
let system_id = sys.id();
|
||||
let arb_id = COUNT.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
let name = format!("actix-rt|system:{}|arbiter:{}", system_id, arb_id);
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
|
||||
let (ready_tx, ready_rx) = std::sync::mpsc::channel::<()>();
|
||||
|
||||
let thread_handle = thread::Builder::new()
|
||||
.name(name.clone())
|
||||
.spawn({
|
||||
let tx = tx.clone();
|
||||
move || {
|
||||
let hnd = ArbiterHandle::new(tx);
|
||||
|
||||
System::set_current(sys);
|
||||
|
||||
HANDLE.with(|cell| *cell.borrow_mut() = Some(hnd.clone()));
|
||||
|
||||
// register arbiter
|
||||
let _ = System::current()
|
||||
.tx()
|
||||
.send(SystemCommand::RegisterArbiter(arb_id, hnd));
|
||||
|
||||
ready_tx.send(()).unwrap();
|
||||
|
||||
// run arbiter event processing loop
|
||||
tokio_uring::start(ArbiterRunner { rx });
|
||||
|
||||
// deregister arbiter
|
||||
let _ = System::current()
|
||||
.tx()
|
||||
.send(SystemCommand::DeregisterArbiter(arb_id));
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|err| panic!("Cannot spawn Arbiter's thread: {name:?}: {err:?}"));
|
||||
|
||||
ready_rx.recv().unwrap();
|
||||
|
||||
Arbiter { tx, thread_handle }
|
||||
}
|
||||
|
||||
/// Sets up an Arbiter runner in a new System using the environment's local set.
|
||||
pub(crate) fn in_new_system() -> ArbiterHandle {
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
|
||||
let hnd = ArbiterHandle::new(tx);
|
||||
|
||||
HANDLE.with(|cell| *cell.borrow_mut() = Some(hnd.clone()));
|
||||
|
||||
local.spawn_local(ArbiterRunner { rx });
|
||||
crate::spawn(ArbiterRunner { rx });
|
||||
|
||||
hnd
|
||||
}
|
||||
@ -188,6 +234,15 @@ impl Arbiter {
|
||||
})
|
||||
}
|
||||
|
||||
/// Try to get current running arbiter handle.
|
||||
///
|
||||
/// Returns `None` if no Arbiter has been started.
|
||||
///
|
||||
/// Unlike [`current`](Self::current), this never panics.
|
||||
pub fn try_current() -> Option<ArbiterHandle> {
|
||||
HANDLE.with(|cell| cell.borrow().clone())
|
||||
}
|
||||
|
||||
/// Stop Arbiter from continuing it's event loop.
|
||||
///
|
||||
/// Returns true if stop message was sent successfully and false if the Arbiter has been dropped.
|
||||
@ -200,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,
|
||||
@ -215,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,
|
||||
@ -241,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(()),
|
||||
|
||||
|
@ -15,7 +15,7 @@
|
||||
//! blocking task thread-pool using [`task::spawn_blocking`].
|
||||
//!
|
||||
//! # Examples
|
||||
//! ```
|
||||
//! ```no_run
|
||||
//! use std::sync::mpsc;
|
||||
//! use actix_rt::{Arbiter, System};
|
||||
//!
|
||||
@ -32,31 +32,43 @@
|
||||
//! arbiter.stop();
|
||||
//! arbiter.join().unwrap();
|
||||
//! ```
|
||||
//!
|
||||
//! # `io-uring` Support
|
||||
//!
|
||||
//! There is experimental support for using io-uring with this crate by enabling the
|
||||
//! `io-uring` feature. For now, it is semver exempt.
|
||||
//!
|
||||
//! 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")]
|
||||
|
||||
use std::future::Future;
|
||||
#[cfg(all(not(target_os = "linux"), feature = "io-uring"))]
|
||||
compile_error!("io_uring is a linux only feature.");
|
||||
|
||||
use tokio::task::JoinHandle;
|
||||
use std::future::Future;
|
||||
|
||||
// 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, test};
|
||||
pub use actix_macros::main;
|
||||
#[cfg(feature = "macros")]
|
||||
pub use actix_macros::test;
|
||||
|
||||
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).
|
||||
@ -70,22 +82,82 @@ pub mod signal {
|
||||
}
|
||||
|
||||
pub mod net {
|
||||
//! TCP/UDP/Unix bindings (Tokio re-exports).
|
||||
//! TCP/UDP/Unix bindings (mostly Tokio re-exports).
|
||||
|
||||
pub use tokio::net::UdpSocket;
|
||||
pub use tokio::net::{TcpListener, TcpStream};
|
||||
use std::{
|
||||
future::Future,
|
||||
io,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use tokio::io::{AsyncRead, AsyncWrite, Interest};
|
||||
#[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)]
|
||||
pub trait ActixStream: AsyncRead + AsyncWrite + Unpin {
|
||||
/// Poll stream and check read readiness of Self.
|
||||
///
|
||||
/// See [tokio::net::TcpStream::poll_read_ready] for detail on intended use.
|
||||
fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>>;
|
||||
|
||||
/// Poll stream and check write readiness of Self.
|
||||
///
|
||||
/// See [tokio::net::TcpStream::poll_write_ready] for detail on intended use.
|
||||
fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>>;
|
||||
}
|
||||
|
||||
impl ActixStream for TcpStream {
|
||||
fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
|
||||
let ready = self.ready(Interest::READABLE);
|
||||
tokio::pin!(ready);
|
||||
ready.poll(cx)
|
||||
}
|
||||
|
||||
fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
|
||||
let ready = self.ready(Interest::WRITABLE);
|
||||
tokio::pin!(ready);
|
||||
ready.poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl ActixStream for UnixStream {
|
||||
fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
|
||||
let ready = self.ready(Interest::READABLE);
|
||||
tokio::pin!(ready);
|
||||
ready.poll(cx)
|
||||
}
|
||||
|
||||
fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
|
||||
let ready = self.ready(Interest::WRITABLE);
|
||||
tokio::pin!(ready);
|
||||
ready.poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Io: ActixStream + ?Sized> ActixStream for Box<Io> {
|
||||
fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
|
||||
(**self).poll_read_ready(cx)
|
||||
}
|
||||
|
||||
fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
|
||||
(**self).poll_write_ready(cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod time {
|
||||
//! Utilities for tracking time (Tokio re-exports).
|
||||
|
||||
pub use tokio::time::Instant;
|
||||
pub use tokio::time::{interval, interval_at, Interval};
|
||||
pub use tokio::time::{sleep, sleep_until, Sleep};
|
||||
pub use tokio::time::{timeout, Timeout};
|
||||
pub use tokio::time::{
|
||||
interval, interval_at, sleep, sleep_until, timeout, Instant, Interval, Sleep, Timeout,
|
||||
};
|
||||
}
|
||||
|
||||
pub mod task {
|
||||
@ -94,14 +166,42 @@ pub mod task {
|
||||
pub use tokio::task::{spawn_blocking, yield_now, JoinError, JoinHandle};
|
||||
}
|
||||
|
||||
/// Spawns a future on the current thread.
|
||||
/// Spawns a future on the current thread as a new task.
|
||||
///
|
||||
/// If not immediately awaited, the task can be cancelled using [`JoinHandle::abort`].
|
||||
///
|
||||
/// The provided future is spawned as a new task; therefore, panics are caught.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if Actix system is not running.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use std::time::Duration;
|
||||
/// # actix_rt::Runtime::new().unwrap().block_on(async {
|
||||
/// // task resolves successfully
|
||||
/// assert_eq!(actix_rt::spawn(async { 1 }).await.unwrap(), 1);
|
||||
///
|
||||
/// // task panics
|
||||
/// assert!(actix_rt::spawn(async {
|
||||
/// panic!("panic is caught at task boundary");
|
||||
/// })
|
||||
/// .await
|
||||
/// .unwrap_err()
|
||||
/// .is_panic());
|
||||
///
|
||||
/// // task is cancelled before completion
|
||||
/// let handle = actix_rt::spawn(actix_rt::time::sleep(Duration::from_secs(100)));
|
||||
/// handle.abort();
|
||||
/// assert!(handle.await.unwrap_err().is_cancelled());
|
||||
/// # });
|
||||
/// ```
|
||||
#[track_caller]
|
||||
#[inline]
|
||||
pub fn spawn<Fut>(f: Fut) -> JoinHandle<()>
|
||||
pub fn spawn<Fut>(f: Fut) -> JoinHandle<Fut::Output>
|
||||
where
|
||||
Fut: Future<Output = ()> + 'static,
|
||||
Fut: Future + 'static,
|
||||
Fut::Output: 'static,
|
||||
{
|
||||
tokio::task::spawn_local(f)
|
||||
}
|
||||
|
@ -31,11 +31,6 @@ impl Runtime {
|
||||
})
|
||||
}
|
||||
|
||||
/// Reference to local task set.
|
||||
pub(crate) fn local_set(&self) -> &LocalSet {
|
||||
&self.local
|
||||
}
|
||||
|
||||
/// Offload a future onto the single-threaded runtime.
|
||||
///
|
||||
/// The returned join handle can be used to await the future's result.
|
||||
@ -58,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,
|
||||
@ -65,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
|
||||
@ -78,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,
|
||||
|
@ -11,12 +11,12 @@ use std::{
|
||||
use futures_core::ready;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
|
||||
use crate::{arbiter::ArbiterHandle, runtime::default_tokio_runtime, Arbiter, Runtime};
|
||||
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.
|
||||
@ -29,6 +29,7 @@ pub struct System {
|
||||
arbiter_handle: ArbiterHandle,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "io-uring"))]
|
||||
impl System {
|
||||
/// Create a new system.
|
||||
///
|
||||
@ -37,7 +38,7 @@ impl System {
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new() -> SystemRunner {
|
||||
Self::with_tokio_rt(|| {
|
||||
default_tokio_runtime()
|
||||
crate::runtime::default_tokio_runtime()
|
||||
.expect("Default Actix (Tokio) runtime could not be created.")
|
||||
})
|
||||
}
|
||||
@ -45,16 +46,15 @@ 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();
|
||||
|
||||
let rt = Runtime::from(runtime_factory());
|
||||
let sys_arbiter = Arbiter::in_new_system(rt.local_set());
|
||||
let rt = crate::runtime::Runtime::from(runtime_factory());
|
||||
let sys_arbiter = rt.block_on(async { Arbiter::in_new_system() });
|
||||
let system = System::construct(sys_tx, sys_arbiter.clone());
|
||||
|
||||
system
|
||||
@ -66,13 +66,34 @@ impl System {
|
||||
let sys_ctrl = SystemController::new(sys_rx, stop_tx);
|
||||
rt.spawn(sys_ctrl);
|
||||
|
||||
SystemRunner {
|
||||
rt,
|
||||
stop_rx,
|
||||
system,
|
||||
}
|
||||
SystemRunner { rt, stop_rx }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "io-uring")]
|
||||
impl System {
|
||||
/// Create a new system.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if underlying Tokio runtime can not be created.
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new() -> SystemRunner {
|
||||
SystemRunner
|
||||
}
|
||||
|
||||
/// 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>(_: F) -> SystemRunner
|
||||
where
|
||||
F: FnOnce() -> tokio::runtime::Runtime,
|
||||
{
|
||||
unimplemented!("System::with_tokio_rt is not implemented for io-uring feature yet")
|
||||
}
|
||||
}
|
||||
|
||||
impl System {
|
||||
/// Constructs new system and registers it on the current thread.
|
||||
pub(crate) fn construct(
|
||||
sys_tx: mpsc::UnboundedSender<SystemCommand>,
|
||||
@ -104,7 +125,7 @@ impl System {
|
||||
///
|
||||
/// Returns `None` if no System has been started.
|
||||
///
|
||||
/// Contrary to `current`, this never panics.
|
||||
/// Unlike [`current`](Self::current), this never panics.
|
||||
pub fn try_current() -> Option<System> {
|
||||
CURRENT.with(|cell| cell.borrow().clone())
|
||||
}
|
||||
@ -150,40 +171,121 @@ impl System {
|
||||
}
|
||||
|
||||
/// Runner that keeps a [System]'s event loop alive until stop message is received.
|
||||
#[cfg(not(feature = "io-uring"))]
|
||||
#[must_use = "A SystemRunner does nothing unless `run` is called."]
|
||||
#[derive(Debug)]
|
||||
pub struct SystemRunner {
|
||||
rt: Runtime,
|
||||
rt: crate::runtime::Runtime,
|
||||
stop_rx: oneshot::Receiver<i32>,
|
||||
system: System,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "io-uring"))]
|
||||
impl SystemRunner {
|
||||
/// Starts event loop and will return once [System] is [stopped](System::stop).
|
||||
pub fn run(self) -> io::Result<()> {
|
||||
let exit_code = self.run_with_code()?;
|
||||
|
||||
match exit_code {
|
||||
0 => Ok(()),
|
||||
nonzero => Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("Non-zero exit code: {}", nonzero),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs the event loop until [stopped](System::stop_with_code), returning the exit code.
|
||||
pub fn run_with_code(self) -> io::Result<i32> {
|
||||
let SystemRunner { rt, stop_rx, .. } = self;
|
||||
|
||||
// run loop
|
||||
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(())
|
||||
}
|
||||
}
|
||||
rt.block_on(stop_rx)
|
||||
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
|
||||
}
|
||||
|
||||
Err(e) => Err(io::Error::new(io::ErrorKind::Other, e)),
|
||||
}
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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;
|
||||
|
||||
#[cfg(feature = "io-uring")]
|
||||
impl SystemRunner {
|
||||
/// Starts event loop and will return once [System] is [stopped](System::stop).
|
||||
pub fn run(self) -> io::Result<()> {
|
||||
unimplemented!("SystemRunner::run is not implemented for io-uring feature yet");
|
||||
}
|
||||
|
||||
/// Runs the event loop until [stopped](System::stop_with_code), returning the exit code.
|
||||
pub fn run_with_code(self) -> io::Result<i32> {
|
||||
unimplemented!("SystemRunner::run_with_code is not implemented for io-uring feature yet");
|
||||
}
|
||||
|
||||
/// Runs the provided future, blocking the current thread until the future completes.
|
||||
#[inline]
|
||||
pub fn block_on<F: Future>(&self, fut: F) -> F::Output {
|
||||
self.rt.block_on(fut)
|
||||
tokio_uring::start(async move {
|
||||
let (stop_tx, stop_rx) = oneshot::channel();
|
||||
let (sys_tx, sys_rx) = mpsc::unbounded_channel();
|
||||
|
||||
let sys_arbiter = Arbiter::in_new_system();
|
||||
let system = System::construct(sys_tx, sys_arbiter.clone());
|
||||
|
||||
system
|
||||
.tx()
|
||||
.send(SystemCommand::RegisterArbiter(usize::MAX, sys_arbiter))
|
||||
.unwrap();
|
||||
|
||||
// init background system arbiter
|
||||
let sys_ctrl = SystemController::new(sys_rx, stop_tx);
|
||||
tokio_uring::spawn(sys_ctrl);
|
||||
|
||||
let res = fut.await;
|
||||
drop(stop_rx);
|
||||
res
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -222,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(()),
|
||||
|
||||
|
17
actix-rt/tests/test-macro-import-conflict.rs
Normal file
17
actix-rt/tests/test-macro-import-conflict.rs
Normal file
@ -0,0 +1,17 @@
|
||||
//! Checks that test macro does not cause problems in the presence of imports named "test" that
|
||||
//! could be either a module with test items or the "test with runtime" macro itself.
|
||||
//!
|
||||
//! Before actix/actix-net#399 was implemented, this macro was running twice. The first run output
|
||||
//! `#[test]` and it got run again and since it was in scope.
|
||||
//!
|
||||
//! Prevented by using the fully-qualified test marker (`#[::core::prelude::v1::test]`).
|
||||
|
||||
#![cfg(feature = "macros")]
|
||||
|
||||
use actix_rt::time as test;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_naming_conflict() {
|
||||
use test as time;
|
||||
time::sleep(std::time::Duration::from_millis(2)).await;
|
||||
}
|
@ -1,15 +1,16 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use std::{
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
mpsc::channel,
|
||||
Arc,
|
||||
},
|
||||
thread,
|
||||
future::Future,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use actix_rt::{Arbiter, System};
|
||||
use tokio::sync::oneshot;
|
||||
use actix_rt::{task::JoinError, Arbiter, System};
|
||||
#[cfg(not(feature = "io-uring"))]
|
||||
use {
|
||||
std::{sync::mpsc::channel, thread},
|
||||
tokio::sync::oneshot,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn await_for_timer() {
|
||||
@ -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,13 +109,17 @@ 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());
|
||||
}
|
||||
|
||||
// Temporary disabled tests for io-uring feature.
|
||||
// They should be enabled when possible.
|
||||
|
||||
#[cfg(not(feature = "io-uring"))]
|
||||
#[test]
|
||||
fn arbiter_spawn_fn_runs() {
|
||||
let _ = System::new();
|
||||
@ -122,6 +136,7 @@ fn arbiter_spawn_fn_runs() {
|
||||
arbiter.join().unwrap();
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "io-uring"))]
|
||||
#[test]
|
||||
fn arbiter_handle_spawn_fn_runs() {
|
||||
let sys = System::new();
|
||||
@ -144,6 +159,7 @@ fn arbiter_handle_spawn_fn_runs() {
|
||||
sys.run().unwrap();
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "io-uring"))]
|
||||
#[test]
|
||||
fn arbiter_drop_no_panic_fn() {
|
||||
let _ = System::new();
|
||||
@ -155,6 +171,7 @@ fn arbiter_drop_no_panic_fn() {
|
||||
arbiter.join().unwrap();
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "io-uring"))]
|
||||
#[test]
|
||||
fn arbiter_drop_no_panic_fut() {
|
||||
let _ = System::new();
|
||||
@ -166,18 +183,7 @@ fn arbiter_drop_no_panic_fut() {
|
||||
arbiter.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn no_system_current_panic() {
|
||||
System::current();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn no_system_arbiter_new_panic() {
|
||||
Arbiter::new();
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "io-uring"))]
|
||||
#[test]
|
||||
fn system_arbiter_spawn() {
|
||||
let runner = System::new();
|
||||
@ -208,6 +214,7 @@ fn system_arbiter_spawn() {
|
||||
thread.join().unwrap();
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "io-uring"))]
|
||||
#[test]
|
||||
fn system_stop_stops_arbiters() {
|
||||
let sys = System::new();
|
||||
@ -220,8 +227,8 @@ fn system_stop_stops_arbiters() {
|
||||
System::current().stop();
|
||||
sys.run().unwrap();
|
||||
|
||||
// account for slightly slow thread de-spawns (only observed on windows)
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
// account for slightly slow thread de-spawns
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
|
||||
// arbiter should be dead and return false
|
||||
assert!(!Arbiter::current().spawn_fn(|| {}));
|
||||
@ -230,6 +237,7 @@ fn system_stop_stops_arbiters() {
|
||||
arb.join().unwrap();
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "io-uring"))]
|
||||
#[test]
|
||||
fn new_system_with_tokio() {
|
||||
let (tx, rx) = channel();
|
||||
@ -262,8 +270,14 @@ fn new_system_with_tokio() {
|
||||
assert_eq!(rx.recv().unwrap(), 42);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "io-uring"))]
|
||||
#[test]
|
||||
fn new_arbiter_with_tokio() {
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
};
|
||||
|
||||
let _ = System::new();
|
||||
|
||||
let arb = Arbiter::with_tokio_rt(|| {
|
||||
@ -286,7 +300,19 @@ fn new_arbiter_with_tokio() {
|
||||
|
||||
arb.join().unwrap();
|
||||
|
||||
assert_eq!(false, counter.load(Ordering::SeqCst));
|
||||
assert!(!counter.load(Ordering::SeqCst));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn no_system_current_panic() {
|
||||
System::current();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn no_system_arbiter_new_panic() {
|
||||
Arbiter::new();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -298,3 +324,55 @@ fn try_current_no_system() {
|
||||
fn try_current_with_system() {
|
||||
System::new().block_on(async { assert!(System::try_current().is_some()) });
|
||||
}
|
||||
|
||||
#[allow(clippy::unit_cmp)]
|
||||
#[test]
|
||||
fn spawn_local() {
|
||||
System::new().block_on(async {
|
||||
// demonstrate that spawn -> R is strictly more capable than spawn -> ()
|
||||
|
||||
assert_eq!(actix_rt::spawn(async {}).await.unwrap(), ());
|
||||
assert_eq!(actix_rt::spawn(async { 1 }).await.unwrap(), 1);
|
||||
assert!(actix_rt::spawn(async { panic!("") }).await.is_err());
|
||||
|
||||
actix_rt::spawn(async { tokio::time::sleep(Duration::from_millis(50)).await })
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
fn g<F: Future<Output = Result<(), JoinError>>>(_f: F) {}
|
||||
g(actix_rt::spawn(async {}));
|
||||
// g(actix_rt::spawn(async { 1 })); // compile err
|
||||
|
||||
fn h<F: Future<Output = Result<R, JoinError>>, R>(_f: F) {}
|
||||
h(actix_rt::spawn(async {}));
|
||||
h(actix_rt::spawn(async { 1 }));
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(all(target_os = "linux", feature = "io-uring"))]
|
||||
#[test]
|
||||
fn tokio_uring_arbiter() {
|
||||
System::new().block_on(async {
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
|
||||
Arbiter::new().spawn(async move {
|
||||
let handle = actix_rt::spawn(async move {
|
||||
let f = tokio_uring::fs::File::create("test.txt").await.unwrap();
|
||||
let buf = b"Hello World!";
|
||||
|
||||
let (res, _) = f.write_all_at(&buf[..], 0).await;
|
||||
assert!(res.is_ok());
|
||||
|
||||
f.sync_all().await.unwrap();
|
||||
f.close().await.unwrap();
|
||||
|
||||
std::fs::remove_file("test.txt").unwrap();
|
||||
});
|
||||
|
||||
handle.await.unwrap();
|
||||
tx.send(true).unwrap();
|
||||
});
|
||||
|
||||
assert!(rx.recv().unwrap());
|
||||
})
|
||||
}
|
||||
|
@ -1,162 +1,232 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
## Unreleased
|
||||
|
||||
## 2.5.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]
|
||||
- Fix panic in test server.
|
||||
- Minimum supported Rust version (MSRV) is now 1.71.
|
||||
|
||||
[#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.5.0
|
||||
|
||||
- Update `mio` dependency to `1`.
|
||||
|
||||
## 2.0.0-beta.2 - 2021-01-03
|
||||
* Merge `actix-testing` to `actix-server` as `test_server` mod. [#242]
|
||||
## 2.4.0
|
||||
|
||||
[#242]: https://github.com/actix/actix-net/pull/242
|
||||
- Update `tokio-uring` dependency to `0.5`.
|
||||
- Minimum supported Rust version (MSRV) is now 1.70.
|
||||
|
||||
## 2.3.0
|
||||
|
||||
## 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]
|
||||
- Add support for MultiPath TCP (MPTCP) with `MpTcp` enum and `ServerBuilder::mptcp()` method.
|
||||
- Minimum supported Rust version (MSRV) is now 1.65.
|
||||
|
||||
[#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.2.0
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.59.
|
||||
- Update `tokio-uring` dependency to `0.4`.
|
||||
|
||||
## 1.0.4 - 2020-09-12
|
||||
* Update actix-codec to 0.3.0.
|
||||
* Workers must be greater than 0. [#167]
|
||||
## 2.1.1
|
||||
|
||||
[#167]: https://github.com/actix/actix-net/pull/167
|
||||
- No significant changes since `2.1.0`.
|
||||
|
||||
## 2.1.0
|
||||
|
||||
## 1.0.3 - 2020-05-19
|
||||
* Replace deprecated `net2` crate with `socket2` [#140]
|
||||
- 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.
|
||||
|
||||
[#140]: https://github.com/actix/actix-net/pull/140
|
||||
## 2.0.0
|
||||
|
||||
- No significant changes since `2.0.0-rc.4`.
|
||||
|
||||
## 1.0.2 - 2020-02-26
|
||||
* Avoid error by calling `reregister()` on Windows [#103]
|
||||
## 2.0.0-rc.4
|
||||
|
||||
[#103]: https://github.com/actix/actix-net/pull/103
|
||||
- Update `tokio-uring` dependency to `0.2`.
|
||||
|
||||
## 2.0.0-rc.3
|
||||
|
||||
## 1.0.1 - 2019-12-29
|
||||
* Rename `.start()` method to `.run()`
|
||||
- No significant changes since `2.0.0-rc.2`.
|
||||
|
||||
## 2.0.0-rc.2
|
||||
|
||||
## 1.0.0 - 2019-12-11
|
||||
* Use actix-net releases
|
||||
- Simplify `TestServer`.
|
||||
|
||||
## 2.0.0-rc.1
|
||||
|
||||
## 1.0.0-alpha.4 - 2019-12-08
|
||||
* Use actix-service 1.0.0-alpha.4
|
||||
- Hide implementation details of `Server`.
|
||||
- `Server` now runs only after awaiting it.
|
||||
|
||||
## 2.0.0-beta.9
|
||||
|
||||
## 1.0.0-alpha.3 - 2019-12-07
|
||||
* Migrate to tokio 0.2
|
||||
* Fix compilation on non-unix platforms
|
||||
* Better handling server configuration
|
||||
- Restore `Arbiter` support lost in `beta.8`.
|
||||
|
||||
## 2.0.0-beta.8
|
||||
|
||||
## 1.0.0-alpha.2 - 2019-12-02
|
||||
* Simplify server service (remove actix-server-config)
|
||||
* Allow to wait on `Server` until server stops
|
||||
- Fix non-unix signal handler.
|
||||
|
||||
## 2.0.0-beta.7
|
||||
|
||||
## 0.8.0-alpha.1 - 2019-11-22
|
||||
* Migrate to `std::future`
|
||||
- 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
|
||||
|
||||
## 0.7.0 - 2019-10-04
|
||||
* Update `rustls` to 0.16
|
||||
* Minimum required Rust version upped to 1.37.0
|
||||
- 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
|
||||
|
||||
## 0.6.1 - 2019-09-25
|
||||
* Add UDS listening support to `ServerBuilder`
|
||||
- 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
|
||||
|
||||
## 0.6.0 - 2019-07-18
|
||||
* Support Unix domain sockets #3
|
||||
- Prevent panic when `shutdown_timeout` is very large. [f9262db]
|
||||
|
||||
## 2.0.0-beta.3
|
||||
|
||||
## 0.5.1 - 2019-05-18
|
||||
* ServerBuilder::shutdown_timeout() accepts u64
|
||||
- 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.5.0 - 2019-05-12
|
||||
* Add `Debug` impl for `SslError`
|
||||
* Derive debug for `Server` and `ServerCommand`
|
||||
* Upgrade to actix-service 0.4
|
||||
- Merge `actix-testing` to `actix-server` as `test_server` mod.
|
||||
|
||||
## 2.0.0-beta.1
|
||||
|
||||
## 0.4.3 - 2019-04-16
|
||||
* Re-export `IoStream` trait
|
||||
* Depend on `ssl` and `rust-tls` features from actix-server-config
|
||||
- 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.4.2 - 2019-03-30
|
||||
* Fix SIGINT force shutdown
|
||||
- Update actix-codec to 0.3.0.
|
||||
- Workers must be greater than 0.
|
||||
|
||||
## 1.0.3
|
||||
|
||||
## 0.4.1 - 2019-03-14
|
||||
* `SystemRuntime::on_start()` - allow to run future before server service initialization
|
||||
- Replace deprecated `net2` crate with `socket2`.
|
||||
|
||||
## 1.0.2
|
||||
|
||||
## 0.4.0 - 2019-03-12
|
||||
* Use `ServerConfig` for service factory
|
||||
* Wrap tcp socket to `Io` type
|
||||
* Upgrade actix-service
|
||||
- Avoid error by calling `reregister()` on Windows.
|
||||
|
||||
## 1.0.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`
|
||||
- Rename `.start()` method to `.run()`
|
||||
|
||||
## 1.0.0
|
||||
|
||||
## 0.3.0 - 2019-03-02
|
||||
* Use new `NewService` trait
|
||||
- Use actix-net releases
|
||||
|
||||
## 1.0.0-alpha.4
|
||||
|
||||
## 0.2.1 - 2019-02-09
|
||||
* Drop service response
|
||||
- Use actix-service 1.0.0-alpha.4
|
||||
|
||||
## 1.0.0-alpha.3
|
||||
|
||||
## 0.2.0 - 2019-02-01
|
||||
* Migrate to actix-service 0.2
|
||||
* Updated rustls dependency
|
||||
- Migrate to tokio 0.2
|
||||
- Fix compilation on non-unix platforms
|
||||
- Better handling server configuration
|
||||
|
||||
## 1.0.0-alpha.2
|
||||
|
||||
## 0.1.3 - 2018-12-21
|
||||
* Fix max concurrent connections handling
|
||||
- Simplify server service (remove actix-server-config)
|
||||
- Allow to wait on `Server` until server stops
|
||||
|
||||
## 0.8.0-alpha.1
|
||||
|
||||
## 0.1.2 - 2018-12-12
|
||||
* rename ServiceConfig::rt() to ServiceConfig::apply()
|
||||
* Fix back-pressure for concurrent ssl handshakes
|
||||
- Migrate to `std::future`
|
||||
|
||||
## 0.7.0
|
||||
|
||||
## 0.1.1 - 2018-12-11
|
||||
* Fix signal handling on windows
|
||||
- Update `rustls` to 0.16
|
||||
- Minimum required Rust version upped to 1.37.0
|
||||
|
||||
## 0.6.1
|
||||
|
||||
## 0.1.0 - 2018-12-09
|
||||
* Move server to separate crate
|
||||
- Add UDS listening support to `ServerBuilder`
|
||||
|
||||
## 0.6.0
|
||||
|
||||
- Support Unix domain sockets #3
|
||||
|
||||
## 0.5.1
|
||||
|
||||
- ServerBuilder::shutdown_timeout() accepts u64
|
||||
|
||||
## 0.5.0
|
||||
|
||||
- Add `Debug` impl for `SslError`
|
||||
- Derive debug for `Server` and `ServerCommand`
|
||||
- Upgrade to actix-service 0.4
|
||||
|
||||
## 0.4.3
|
||||
|
||||
- Re-export `IoStream` trait
|
||||
- Depend on `ssl` and `rust-tls` features from actix-server-config
|
||||
|
||||
## 0.4.2
|
||||
|
||||
- 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
|
||||
|
59
actix-server/Cargo.toml
Executable file → Normal file
59
actix-server/Cargo.toml
Executable file → Normal file
@ -1,43 +1,50 @@
|
||||
[package]
|
||||
name = "actix-server"
|
||||
version = "2.0.0-beta.3"
|
||||
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"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-net.git"
|
||||
documentation = "https://docs.rs/actix-server"
|
||||
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"
|
||||
exclude = [".gitignore", ".cargo/config"]
|
||||
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 = ["tokio-uring", "actix-rt/io-uring"]
|
||||
|
||||
[dependencies]
|
||||
actix-codec = "0.4.0-beta.1"
|
||||
actix-rt = { version = "2.0.0", default-features = false }
|
||||
actix-service = "2.0.0-beta.4"
|
||||
actix-utils = "3.0.0-beta.2"
|
||||
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"
|
||||
slab = "0.4"
|
||||
tokio = { version = "1", features = ["sync"] }
|
||||
# runtime for `io-uring` feature
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
tokio-uring = { version = "0.5", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "2.0.0"
|
||||
actix-codec = "0.5"
|
||||
actix-rt = "2.8"
|
||||
|
||||
bytes = "1"
|
||||
env_logger = "0.8"
|
||||
futures-util = { version = "0.3.7", default-features = false, features = ["sink"] }
|
||||
tokio = { version = "1", features = ["io-util"] }
|
||||
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(())
|
||||
// }
|
@ -9,39 +9,39 @@
|
||||
//! Start typing. When you press enter the typed line will be echoed back. The server will log
|
||||
//! the length of each line it echos and the total size of data sent when the connection is closed.
|
||||
|
||||
use std::sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc,
|
||||
use std::{
|
||||
io,
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
use std::{env, io};
|
||||
|
||||
use actix_rt::net::TcpStream;
|
||||
use actix_server::Server;
|
||||
use actix_service::pipeline_factory;
|
||||
use actix_service::{fn_service, ServiceFactoryExt as _};
|
||||
use bytes::BytesMut;
|
||||
use futures_util::future::ok;
|
||||
use log::{error, info};
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};
|
||||
|
||||
#[actix_rt::main]
|
||||
async fn main() -> io::Result<()> {
|
||||
env::set_var("RUST_LOG", "actix=trace,basic=trace");
|
||||
env_logger::init();
|
||||
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 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);
|
||||
let num2 = Arc::clone(&count);
|
||||
|
||||
pipeline_factory(move |mut stream: TcpStream| {
|
||||
fn_service(move |mut stream: TcpStream| {
|
||||
let count = Arc::clone(&count);
|
||||
|
||||
async move {
|
||||
@ -58,14 +58,14 @@ async fn main() -> 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,27 @@ async fn main() -> 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
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> io::Result<()> {
|
||||
run().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// alternatively:
|
||||
// #[actix_rt::main]
|
||||
// async fn main() -> io::Result<()> {
|
||||
// run().await?;
|
||||
// Ok(())
|
||||
// }
|
@ -1,428 +1,462 @@
|
||||
use std::time::Duration;
|
||||
use std::{io, thread};
|
||||
use std::{io, thread, time::Duration};
|
||||
|
||||
use actix_rt::{
|
||||
time::{sleep_until, Instant},
|
||||
System,
|
||||
};
|
||||
use log::{error, info};
|
||||
use actix_rt::time::Instant;
|
||||
use mio::{Interest, Poll, Token as MioToken};
|
||||
use slab::Slab;
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
use crate::server::Server;
|
||||
use crate::socket::{MioListener, SocketAddr};
|
||||
use crate::waker_queue::{WakerInterest, WakerQueue, WAKER_TOKEN};
|
||||
use crate::worker::{Conn, WorkerHandle};
|
||||
use crate::Token;
|
||||
use crate::{
|
||||
availability::Availability,
|
||||
socket::MioListener,
|
||||
waker_queue::{WakerInterest, WakerQueue, WAKER_TOKEN},
|
||||
worker::{Conn, ServerWorker, WorkerHandleAccept, WorkerHandleServer},
|
||||
ServerBuilder, ServerHandle,
|
||||
};
|
||||
|
||||
const TIMEOUT_DURATION_ON_ERROR: Duration = Duration::from_millis(510);
|
||||
|
||||
struct ServerSocketInfo {
|
||||
// addr for socket. mainly used for logging.
|
||||
addr: SocketAddr,
|
||||
// be ware this is the crate token for identify socket and should not be confused with
|
||||
// mio::Token
|
||||
token: Token,
|
||||
token: usize,
|
||||
|
||||
lst: MioListener,
|
||||
// timeout is used to mark the deadline when this socket's listener should be registered again
|
||||
// after an error.
|
||||
timeout: Option<Instant>,
|
||||
|
||||
/// Timeout is used to mark the deadline when this socket's listener should be registered again
|
||||
/// after an error.
|
||||
timeout: Option<actix_rt::time::Instant>,
|
||||
}
|
||||
|
||||
/// Accept loop would live with `ServerBuilder`.
|
||||
///
|
||||
/// It's tasked with construct `Poll` instance and `WakerQueue` which would be distributed to
|
||||
/// `Accept` and `Worker`.
|
||||
///
|
||||
/// It would also listen to `ServerCommand` and push interests to `WakerQueue`.
|
||||
pub(crate) struct AcceptLoop {
|
||||
srv: Option<Server>,
|
||||
poll: Option<Poll>,
|
||||
waker: WakerQueue,
|
||||
}
|
||||
|
||||
impl AcceptLoop {
|
||||
pub fn new(srv: Server) -> Self {
|
||||
let poll = Poll::new().unwrap_or_else(|e| panic!("Can not create `mio::Poll`: {}", e));
|
||||
let waker = WakerQueue::new(poll.registry())
|
||||
.unwrap_or_else(|e| panic!("Can not create `mio::Waker`: {}", e));
|
||||
|
||||
Self {
|
||||
srv: Some(srv),
|
||||
poll: Some(poll),
|
||||
waker,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn waker_owned(&self) -> WakerQueue {
|
||||
self.waker.clone()
|
||||
}
|
||||
|
||||
pub fn wake(&self, i: WakerInterest) {
|
||||
self.waker.wake(i);
|
||||
}
|
||||
|
||||
pub(crate) fn start(
|
||||
&mut self,
|
||||
socks: Vec<(Token, MioListener)>,
|
||||
handles: Vec<WorkerHandle>,
|
||||
) {
|
||||
let srv = self.srv.take().expect("Can not re-use AcceptInfo");
|
||||
let poll = self.poll.take().unwrap();
|
||||
let waker = self.waker.clone();
|
||||
|
||||
Accept::start(poll, waker, socks, srv, handles);
|
||||
}
|
||||
}
|
||||
|
||||
/// poll instance of the server.
|
||||
struct Accept {
|
||||
/// Poll instance of the server.
|
||||
pub(crate) struct Accept {
|
||||
poll: Poll,
|
||||
waker: WakerQueue,
|
||||
handles: Vec<WorkerHandle>,
|
||||
srv: Server,
|
||||
waker_queue: WakerQueue,
|
||||
handles: Vec<WorkerHandleAccept>,
|
||||
srv: ServerHandle,
|
||||
next: usize,
|
||||
backpressure: bool,
|
||||
}
|
||||
|
||||
/// This function defines errors that are per-connection. Which basically
|
||||
/// means that if we get this error from `accept()` system call it means
|
||||
/// next connection might be ready to be accepted.
|
||||
///
|
||||
/// All other errors will incur a timeout before next `accept()` is performed.
|
||||
/// The timeout is useful to handle resource exhaustion errors like ENFILE
|
||||
/// and EMFILE. Otherwise, could enter into tight loop.
|
||||
fn connection_error(e: &io::Error) -> bool {
|
||||
e.kind() == io::ErrorKind::ConnectionRefused
|
||||
|| e.kind() == io::ErrorKind::ConnectionAborted
|
||||
|| e.kind() == io::ErrorKind::ConnectionReset
|
||||
avail: Availability,
|
||||
/// use the smallest duration from sockets timeout.
|
||||
timeout: Option<Duration>,
|
||||
paused: bool,
|
||||
}
|
||||
|
||||
impl Accept {
|
||||
pub(crate) fn start(
|
||||
poll: Poll,
|
||||
waker: WakerQueue,
|
||||
socks: Vec<(Token, MioListener)>,
|
||||
srv: Server,
|
||||
handles: Vec<WorkerHandle>,
|
||||
) {
|
||||
// Accept runs in its own thread and would want to spawn additional futures to current
|
||||
// actix system.
|
||||
let sys = System::current();
|
||||
thread::Builder::new()
|
||||
.name("actix-server accept loop".to_owned())
|
||||
.spawn(move || {
|
||||
System::set_current(sys);
|
||||
let (mut accept, sockets) =
|
||||
Accept::new_with_sockets(poll, waker, socks, handles, srv);
|
||||
accept.poll_with(sockets);
|
||||
sockets: Vec<(usize, MioListener)>,
|
||||
builder: &ServerBuilder,
|
||||
) -> io::Result<(WakerQueue, Vec<WorkerHandleServer>, thread::JoinHandle<()>)> {
|
||||
let handle_server = ServerHandle::new(builder.cmd_tx.clone());
|
||||
|
||||
// construct poll instance and its waker
|
||||
let poll = Poll::new()?;
|
||||
let waker_queue = WakerQueue::new(poll.registry())?;
|
||||
|
||||
// start workers and collect handles
|
||||
let (handles_accept, handles_server) = (0..builder.threads)
|
||||
.map(|idx| {
|
||||
// clone service factories
|
||||
let factories = builder
|
||||
.factories
|
||||
.iter()
|
||||
.map(|f| f.clone_factory())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// start worker using service factories
|
||||
ServerWorker::start(idx, factories, waker_queue.clone(), builder.worker_config)
|
||||
})
|
||||
.unwrap();
|
||||
.collect::<io::Result<Vec<_>>>()?
|
||||
.into_iter()
|
||||
.unzip();
|
||||
|
||||
let (mut accept, mut sockets) = Accept::new_with_sockets(
|
||||
poll,
|
||||
waker_queue.clone(),
|
||||
sockets,
|
||||
handles_accept,
|
||||
handle_server,
|
||||
)?;
|
||||
|
||||
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, accept_handle))
|
||||
}
|
||||
|
||||
fn new_with_sockets(
|
||||
poll: Poll,
|
||||
waker: WakerQueue,
|
||||
socks: Vec<(Token, MioListener)>,
|
||||
handles: Vec<WorkerHandle>,
|
||||
srv: Server,
|
||||
) -> (Accept, Slab<ServerSocketInfo>) {
|
||||
let mut sockets = Slab::new();
|
||||
for (hnd_token, mut lst) in socks.into_iter() {
|
||||
let addr = lst.local_addr();
|
||||
waker_queue: WakerQueue,
|
||||
sockets: Vec<(usize, MioListener)>,
|
||||
accept_handles: Vec<WorkerHandleAccept>,
|
||||
server_handle: ServerHandle,
|
||||
) -> io::Result<(Accept, Box<[ServerSocketInfo]>)> {
|
||||
let sockets = sockets
|
||||
.into_iter()
|
||||
.map(|(token, mut lst)| {
|
||||
// Start listening for incoming connections
|
||||
poll.registry()
|
||||
.register(&mut lst, MioToken(token), Interest::READABLE)?;
|
||||
|
||||
let entry = sockets.vacant_entry();
|
||||
let token = entry.key();
|
||||
Ok(ServerSocketInfo {
|
||||
token,
|
||||
lst,
|
||||
timeout: None,
|
||||
})
|
||||
})
|
||||
.collect::<io::Result<_>>()?;
|
||||
|
||||
// Start listening for incoming connections
|
||||
poll.registry()
|
||||
.register(&mut lst, MioToken(token), Interest::READABLE)
|
||||
.unwrap_or_else(|e| panic!("Can not register io: {}", e));
|
||||
let mut avail = Availability::default();
|
||||
|
||||
entry.insert(ServerSocketInfo {
|
||||
addr,
|
||||
token: hnd_token,
|
||||
lst,
|
||||
timeout: None,
|
||||
});
|
||||
}
|
||||
// Assume all handles are avail at construct time.
|
||||
avail.set_available_all(&accept_handles);
|
||||
|
||||
let accept = Accept {
|
||||
poll,
|
||||
waker,
|
||||
handles,
|
||||
srv,
|
||||
waker_queue,
|
||||
handles: accept_handles,
|
||||
srv: server_handle,
|
||||
next: 0,
|
||||
backpressure: false,
|
||||
avail,
|
||||
timeout: None,
|
||||
paused: false,
|
||||
};
|
||||
|
||||
(accept, sockets)
|
||||
Ok((accept, sockets))
|
||||
}
|
||||
|
||||
fn poll_with(&mut self, mut sockets: Slab<ServerSocketInfo>) {
|
||||
let mut events = mio::Events::with_capacity(128);
|
||||
/// blocking wait for readiness events triggered by mio
|
||||
fn poll_with(&mut self, sockets: &mut [ServerSocketInfo]) {
|
||||
let mut events = mio::Events::with_capacity(256);
|
||||
|
||||
loop {
|
||||
if let Err(e) = self.poll.poll(&mut events, None) {
|
||||
match e.kind() {
|
||||
std::io::ErrorKind::Interrupted => {
|
||||
continue;
|
||||
}
|
||||
_ => {
|
||||
panic!("Poll error: {}", e);
|
||||
}
|
||||
if let Err(err) = self.poll.poll(&mut events, self.timeout) {
|
||||
match err.kind() {
|
||||
io::ErrorKind::Interrupted => {}
|
||||
_ => panic!("Poll error: {}", err),
|
||||
}
|
||||
}
|
||||
|
||||
for event in events.iter() {
|
||||
let token = event.token();
|
||||
match token {
|
||||
// This is a loop because interests for command from previous version was
|
||||
// a loop that would try to drain the command channel. It's yet unknown
|
||||
// if it's necessary/good practice to actively drain the waker queue.
|
||||
WAKER_TOKEN => 'waker: loop {
|
||||
// take guard with every iteration so no new interest can be added
|
||||
// until the current task is done.
|
||||
let mut guard = self.waker.guard();
|
||||
match guard.pop_front() {
|
||||
// worker notify it becomes available. we may want to recover
|
||||
// from backpressure.
|
||||
Some(WakerInterest::WorkerAvailable) => {
|
||||
drop(guard);
|
||||
self.maybe_backpressure(&mut sockets, false);
|
||||
}
|
||||
// a new worker thread is made and it's handle would be added
|
||||
// to Accept
|
||||
Some(WakerInterest::Worker(handle)) => {
|
||||
drop(guard);
|
||||
// maybe we want to recover from a backpressure.
|
||||
self.maybe_backpressure(&mut sockets, false);
|
||||
self.handles.push(handle);
|
||||
}
|
||||
// got timer interest and it's time to try register socket(s)
|
||||
// again.
|
||||
Some(WakerInterest::Timer) => {
|
||||
drop(guard);
|
||||
self.process_timer(&mut sockets)
|
||||
}
|
||||
Some(WakerInterest::Pause) => {
|
||||
drop(guard);
|
||||
sockets.iter_mut().for_each(|(_, info)| {
|
||||
match self.deregister(info) {
|
||||
Ok(_) => info!(
|
||||
"Paused accepting connections on {}",
|
||||
info.addr
|
||||
),
|
||||
Err(e) => {
|
||||
error!("Can not deregister server socket {}", e)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
Some(WakerInterest::Resume) => {
|
||||
drop(guard);
|
||||
sockets.iter_mut().for_each(|(token, info)| {
|
||||
self.register_logged(token, info);
|
||||
});
|
||||
}
|
||||
Some(WakerInterest::Stop) => {
|
||||
return self.deregister_all(&mut sockets);
|
||||
}
|
||||
// waker queue is drained.
|
||||
None => {
|
||||
// Reset the WakerQueue before break so it does not grow
|
||||
// infinitely.
|
||||
WakerQueue::reset(&mut guard);
|
||||
break 'waker;
|
||||
}
|
||||
WAKER_TOKEN => {
|
||||
let exit = self.handle_waker(sockets);
|
||||
if exit {
|
||||
info!("accept thread stopped");
|
||||
return;
|
||||
}
|
||||
},
|
||||
}
|
||||
_ => {
|
||||
let token = usize::from(token);
|
||||
self.accept(&mut sockets, token);
|
||||
self.accept(sockets, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check for timeout and re-register sockets
|
||||
self.process_timeout(sockets);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_waker(&mut self, sockets: &mut [ServerSocketInfo]) -> bool {
|
||||
// This is a loop because interests for command from previous version was
|
||||
// a loop that would try to drain the command channel. It's yet unknown
|
||||
// if it's necessary/good practice to actively drain the waker queue.
|
||||
loop {
|
||||
// Take guard with every iteration so no new 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 notified it became available.
|
||||
Some(WakerInterest::WorkerAvailable(idx)) => {
|
||||
drop(guard);
|
||||
|
||||
self.avail.set_available(idx, true);
|
||||
|
||||
if !self.paused {
|
||||
self.accept_all(sockets);
|
||||
}
|
||||
}
|
||||
|
||||
// A new worker thread has been created so store its handle.
|
||||
Some(WakerInterest::Worker(handle)) => {
|
||||
drop(guard);
|
||||
|
||||
self.avail.set_available(handle.idx(), true);
|
||||
self.handles.push(handle);
|
||||
|
||||
if !self.paused {
|
||||
self.accept_all(sockets);
|
||||
}
|
||||
}
|
||||
|
||||
Some(WakerInterest::Pause) => {
|
||||
drop(guard);
|
||||
|
||||
if !self.paused {
|
||||
self.paused = true;
|
||||
|
||||
self.deregister_all(sockets);
|
||||
}
|
||||
}
|
||||
|
||||
Some(WakerInterest::Resume) => {
|
||||
drop(guard);
|
||||
|
||||
if self.paused {
|
||||
self.paused = false;
|
||||
|
||||
sockets.iter_mut().for_each(|info| {
|
||||
self.register_logged(info);
|
||||
});
|
||||
|
||||
self.accept_all(sockets);
|
||||
}
|
||||
}
|
||||
|
||||
Some(WakerInterest::Stop) => {
|
||||
if !self.paused {
|
||||
self.deregister_all(sockets);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// waker queue is drained
|
||||
None => {
|
||||
// Reset the WakerQueue before break so it does not grow infinitely
|
||||
WakerQueue::reset(&mut guard);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_timer(&self, sockets: &mut Slab<ServerSocketInfo>) {
|
||||
let now = Instant::now();
|
||||
sockets.iter_mut().for_each(|(token, info)| {
|
||||
// only the ServerSocketInfo have an associate timeout value was de registered.
|
||||
if let Some(inst) = info.timeout.take() {
|
||||
if now > inst {
|
||||
self.register_logged(token, info);
|
||||
} else {
|
||||
info.timeout = Some(inst);
|
||||
fn process_timeout(&mut self, sockets: &mut [ServerSocketInfo]) {
|
||||
// always remove old timeouts
|
||||
if self.timeout.take().is_some() {
|
||||
let now = Instant::now();
|
||||
|
||||
sockets
|
||||
.iter_mut()
|
||||
// Only sockets that had an associated timeout were deregistered.
|
||||
.filter(|info| info.timeout.is_some())
|
||||
.for_each(|info| {
|
||||
let inst = info.timeout.take().unwrap();
|
||||
|
||||
if now < inst {
|
||||
// still timed out; try to set new timeout
|
||||
info.timeout = Some(inst);
|
||||
self.set_timeout(inst - now);
|
||||
} else if !self.paused {
|
||||
// timeout expired; register socket again
|
||||
self.register_logged(info);
|
||||
}
|
||||
|
||||
// Drop the timeout if server is paused and socket timeout is expired.
|
||||
// When server recovers from pause it will register all sockets without
|
||||
// a timeout value so this socket register will be delayed till then.
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Update accept timeout with `duration` if it is shorter than current timeout.
|
||||
fn set_timeout(&mut self, duration: Duration) {
|
||||
match self.timeout {
|
||||
Some(ref mut timeout) => {
|
||||
if *timeout > duration {
|
||||
*timeout = duration;
|
||||
}
|
||||
}
|
||||
});
|
||||
None => self.timeout = Some(duration),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn register(&self, token: usize, info: &mut ServerSocketInfo) -> io::Result<()> {
|
||||
fn register(&self, info: &mut ServerSocketInfo) -> io::Result<()> {
|
||||
let token = MioToken(info.token);
|
||||
self.poll
|
||||
.registry()
|
||||
.register(&mut info.lst, MioToken(token), Interest::READABLE)
|
||||
.register(&mut info.lst, token, Interest::READABLE)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn register(&self, token: usize, info: &mut ServerSocketInfo) -> io::Result<()> {
|
||||
fn register(&self, info: &mut ServerSocketInfo) -> io::Result<()> {
|
||||
// On windows, calling register without deregister cause an error.
|
||||
// See https://github.com/actix/actix-web/issues/905
|
||||
// Calling reregister seems to fix the issue.
|
||||
let token = MioToken(info.token);
|
||||
self.poll
|
||||
.registry()
|
||||
.register(&mut info.lst, mio::Token(token), Interest::READABLE)
|
||||
.register(&mut info.lst, token, Interest::READABLE)
|
||||
.or_else(|_| {
|
||||
self.poll.registry().reregister(
|
||||
&mut info.lst,
|
||||
mio::Token(token),
|
||||
Interest::READABLE,
|
||||
)
|
||||
self.poll
|
||||
.registry()
|
||||
.reregister(&mut info.lst, token, Interest::READABLE)
|
||||
})
|
||||
}
|
||||
|
||||
fn register_logged(&self, token: usize, info: &mut ServerSocketInfo) {
|
||||
match self.register(token, info) {
|
||||
Ok(_) => info!("Resume accepting connections on {}", info.addr),
|
||||
Err(e) => error!("Can not register server socket {}", e),
|
||||
fn register_logged(&self, info: &mut ServerSocketInfo) {
|
||||
match self.register(info) {
|
||||
Ok(_) => debug!("resume accepting connections on {}", info.lst.local_addr()),
|
||||
Err(err) => error!("can not register server socket {}", err),
|
||||
}
|
||||
}
|
||||
|
||||
fn deregister(&self, info: &mut ServerSocketInfo) -> io::Result<()> {
|
||||
self.poll.registry().deregister(&mut info.lst)
|
||||
}
|
||||
|
||||
fn deregister_all(&self, sockets: &mut Slab<ServerSocketInfo>) {
|
||||
sockets.iter_mut().for_each(|(_, info)| {
|
||||
info!("Accepting connections on {} has been paused", info.addr);
|
||||
let _ = self.deregister(info);
|
||||
});
|
||||
}
|
||||
|
||||
fn maybe_backpressure(&mut self, sockets: &mut Slab<ServerSocketInfo>, on: bool) {
|
||||
if self.backpressure {
|
||||
if !on {
|
||||
self.backpressure = false;
|
||||
for (token, info) in sockets.iter_mut() {
|
||||
if info.timeout.is_some() {
|
||||
// socket will attempt to re-register itself when its timeout completes
|
||||
continue;
|
||||
}
|
||||
self.register_logged(token, info);
|
||||
}
|
||||
fn deregister_logged(&self, info: &mut ServerSocketInfo) {
|
||||
match self.poll.registry().deregister(&mut info.lst) {
|
||||
Ok(_) => debug!("paused accepting connections on {}", info.lst.local_addr()),
|
||||
Err(err) => {
|
||||
error!("can not deregister server socket {}", err)
|
||||
}
|
||||
} else if on {
|
||||
self.backpressure = true;
|
||||
self.deregister_all(sockets);
|
||||
}
|
||||
}
|
||||
|
||||
fn accept_one(&mut self, sockets: &mut Slab<ServerSocketInfo>, mut msg: Conn) {
|
||||
if self.backpressure {
|
||||
while !self.handles.is_empty() {
|
||||
match self.handles[self.next].send(msg) {
|
||||
Ok(_) => {
|
||||
self.set_next();
|
||||
break;
|
||||
}
|
||||
Err(tmp) => {
|
||||
// worker lost contact and could be gone. a message is sent to
|
||||
// `ServerBuilder` future to notify it a new worker should be made.
|
||||
// after that remove the fault worker.
|
||||
self.srv.worker_faulted(self.handles[self.next].idx);
|
||||
msg = tmp;
|
||||
self.handles.swap_remove(self.next);
|
||||
if self.handles.is_empty() {
|
||||
error!("No workers");
|
||||
return;
|
||||
} else if self.handles.len() <= self.next {
|
||||
self.next = 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let mut idx = 0;
|
||||
while idx < self.handles.len() {
|
||||
idx += 1;
|
||||
if self.handles[self.next].available() {
|
||||
match self.handles[self.next].send(msg) {
|
||||
Ok(_) => {
|
||||
self.set_next();
|
||||
return;
|
||||
}
|
||||
// worker lost contact and could be gone. a message is sent to
|
||||
// `ServerBuilder` future to notify it a new worker should be made.
|
||||
// after that remove the fault worker and enter backpressure if necessary.
|
||||
Err(tmp) => {
|
||||
self.srv.worker_faulted(self.handles[self.next].idx);
|
||||
msg = tmp;
|
||||
self.handles.swap_remove(self.next);
|
||||
if self.handles.is_empty() {
|
||||
error!("No workers");
|
||||
self.maybe_backpressure(sockets, true);
|
||||
return;
|
||||
} else if self.handles.len() <= self.next {
|
||||
self.next = 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
fn deregister_all(&self, sockets: &mut [ServerSocketInfo]) {
|
||||
// This is a best effort implementation with following limitation:
|
||||
//
|
||||
// Every ServerSocketInfo with associated timeout will be skipped and it's timeout is
|
||||
// removed in the process.
|
||||
//
|
||||
// Therefore WakerInterest::Pause followed by WakerInterest::Resume in a very short gap
|
||||
// (less than 500ms) would cause all timing out ServerSocketInfos be re-registered before
|
||||
// expected timing.
|
||||
sockets
|
||||
.iter_mut()
|
||||
// Take all timeout.
|
||||
// This is to prevent Accept::process_timer method re-register a socket afterwards.
|
||||
.map(|info| (info.timeout.take(), info))
|
||||
// Socket info with a timeout is already deregistered so skip them.
|
||||
.filter(|(timeout, _)| timeout.is_none())
|
||||
.for_each(|(_, info)| self.deregister_logged(info));
|
||||
}
|
||||
|
||||
// Send connection to worker and handle error.
|
||||
fn send_connection(&mut self, conn: Conn) -> Result<(), Conn> {
|
||||
let next = self.next();
|
||||
match next.send(conn) {
|
||||
Ok(_) => {
|
||||
// Increment counter of WorkerHandle.
|
||||
// Set worker to unavailable with it hit max (Return false).
|
||||
if !next.inc_counter() {
|
||||
let idx = next.idx();
|
||||
self.avail.set_available(idx, false);
|
||||
}
|
||||
self.set_next();
|
||||
Ok(())
|
||||
}
|
||||
Err(conn) => {
|
||||
// Worker thread is error and could be gone.
|
||||
// Remove worker handle and notify `ServerBuilder`.
|
||||
self.remove_next();
|
||||
|
||||
if self.handles.is_empty() {
|
||||
error!("no workers");
|
||||
// All workers are gone and Conn is nowhere to be sent.
|
||||
// Treat this situation as Ok and drop Conn.
|
||||
return Ok(());
|
||||
} else if self.handles.len() <= self.next {
|
||||
self.next = 0;
|
||||
}
|
||||
|
||||
Err(conn)
|
||||
}
|
||||
// enable backpressure
|
||||
self.maybe_backpressure(sockets, true);
|
||||
self.accept_one(sockets, msg);
|
||||
}
|
||||
}
|
||||
|
||||
// set next worker handle that would accept work.
|
||||
fn accept_one(&mut self, mut conn: Conn) {
|
||||
loop {
|
||||
let next = self.next();
|
||||
let idx = next.idx();
|
||||
|
||||
if self.avail.get_available(idx) {
|
||||
match self.send_connection(conn) {
|
||||
Ok(_) => return,
|
||||
Err(c) => conn = c,
|
||||
}
|
||||
} else {
|
||||
self.avail.set_available(idx, false);
|
||||
self.set_next();
|
||||
|
||||
if !self.avail.available() {
|
||||
while let Err(c) = self.send_connection(conn) {
|
||||
conn = c;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn accept(&mut self, sockets: &mut [ServerSocketInfo], token: usize) {
|
||||
while self.avail.available() {
|
||||
let info = &mut sockets[token];
|
||||
|
||||
match info.lst.accept() {
|
||||
Ok(io) => {
|
||||
let conn = Conn { io, token };
|
||||
self.accept_one(conn);
|
||||
}
|
||||
Err(ref 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);
|
||||
|
||||
// sleep after error. write the timeout to socket info as later
|
||||
// the poll would need it mark which socket and when it's
|
||||
// listener should be registered
|
||||
info.timeout = Some(Instant::now() + Duration::from_millis(500));
|
||||
self.set_timeout(TIMEOUT_DURATION_ON_ERROR);
|
||||
|
||||
return;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn accept_all(&mut self, sockets: &mut [ServerSocketInfo]) {
|
||||
sockets
|
||||
.iter_mut()
|
||||
.map(|info| info.token)
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
.for_each(|idx| self.accept(sockets, idx))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn next(&self) -> &WorkerHandleAccept {
|
||||
&self.handles[self.next]
|
||||
}
|
||||
|
||||
/// Set next worker handle that would accept connection.
|
||||
#[inline(always)]
|
||||
fn set_next(&mut self) {
|
||||
self.next = (self.next + 1) % self.handles.len();
|
||||
}
|
||||
|
||||
fn accept(&mut self, sockets: &mut Slab<ServerSocketInfo>, token: usize) {
|
||||
loop {
|
||||
let msg = if let Some(info) = sockets.get_mut(token) {
|
||||
match info.lst.accept() {
|
||||
Ok(Some((io, addr))) => Conn {
|
||||
io,
|
||||
token: info.token,
|
||||
peer: Some(addr),
|
||||
},
|
||||
Ok(None) => return,
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => return,
|
||||
Err(ref e) if connection_error(e) => continue,
|
||||
Err(e) => {
|
||||
// deregister listener temporary
|
||||
error!("Error accepting connection: {}", e);
|
||||
if let Err(err) = self.deregister(info) {
|
||||
error!("Can not deregister server socket {}", err);
|
||||
}
|
||||
|
||||
// sleep after error. write the timeout to socket info as later the poll
|
||||
// would need it mark which socket and when it's listener should be
|
||||
// registered.
|
||||
info.timeout = Some(Instant::now() + Duration::from_millis(500));
|
||||
|
||||
// after the sleep a Timer interest is sent to Accept Poll
|
||||
let waker = self.waker.clone();
|
||||
System::current().arbiter().spawn(async move {
|
||||
sleep_until(Instant::now() + Duration::from_millis(510)).await;
|
||||
waker.wake(WakerInterest::Timer);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
self.accept_one(sockets, msg);
|
||||
}
|
||||
/// Remove next worker handle that fail to accept connection.
|
||||
fn remove_next(&mut self) {
|
||||
let handle = self.handles.swap_remove(self.next);
|
||||
let idx = handle.idx();
|
||||
// A message is sent to `ServerBuilder` future to notify it a new worker
|
||||
// should be made.
|
||||
self.srv.worker_faulted(idx);
|
||||
self.avail.set_available(idx, false);
|
||||
}
|
||||
}
|
||||
|
||||
/// This function defines errors that are per-connection; if we get this error from the `accept()`
|
||||
/// system call it means the next connection might be ready to be accepted.
|
||||
///
|
||||
/// All other errors will incur a timeout before next `accept()` call is attempted. The timeout is
|
||||
/// useful to handle resource exhaustion errors like `ENFILE` and `EMFILE`. Otherwise, it could
|
||||
/// enter into a temporary spin loop.
|
||||
fn connection_error(e: &io::Error) -> bool {
|
||||
e.kind() == io::ErrorKind::ConnectionRefused
|
||||
|| e.kind() == io::ErrorKind::ConnectionAborted
|
||||
|| e.kind() == io::ErrorKind::ConnectionReset
|
||||
}
|
||||
|
121
actix-server/src/availability.rs
Normal file
121
actix-server/src/availability.rs
Normal file
@ -0,0 +1,121 @@
|
||||
use crate::worker::WorkerHandleAccept;
|
||||
|
||||
/// Array of u128 with every bit as marker for a worker handle's availability.
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct Availability([u128; 4]);
|
||||
|
||||
impl Availability {
|
||||
/// Check if any worker handle is available
|
||||
#[inline(always)]
|
||||
pub(crate) fn available(&self) -> bool {
|
||||
self.0.iter().any(|a| *a != 0)
|
||||
}
|
||||
|
||||
/// Check if worker handle is available by index
|
||||
#[inline(always)]
|
||||
pub(crate) fn get_available(&self, idx: usize) -> bool {
|
||||
let (offset, idx) = Self::offset(idx);
|
||||
|
||||
self.0[offset] & (1 << idx as u128) != 0
|
||||
}
|
||||
|
||||
/// Set worker handle available state by index.
|
||||
pub(crate) fn set_available(&mut self, idx: usize, avail: bool) {
|
||||
let (offset, idx) = Self::offset(idx);
|
||||
|
||||
let off = 1 << idx as u128;
|
||||
if avail {
|
||||
self.0[offset] |= off;
|
||||
} else {
|
||||
self.0[offset] &= !off
|
||||
}
|
||||
}
|
||||
|
||||
/// Set all worker handle to available state.
|
||||
/// This would result in a re-check on all workers' availability.
|
||||
pub(crate) fn set_available_all(&mut self, handles: &[WorkerHandleAccept]) {
|
||||
handles.iter().for_each(|handle| {
|
||||
self.set_available(handle.idx(), true);
|
||||
})
|
||||
}
|
||||
|
||||
/// Get offset and adjusted index of given worker handle index.
|
||||
pub(crate) fn offset(idx: usize) -> (usize, usize) {
|
||||
if idx < 128 {
|
||||
(0, idx)
|
||||
} else if idx < 128 * 2 {
|
||||
(1, idx - 128)
|
||||
} else if idx < 128 * 3 {
|
||||
(2, idx - 128 * 2)
|
||||
} else if idx < 128 * 4 {
|
||||
(3, idx - 128 * 3)
|
||||
} else {
|
||||
panic!("Max WorkerHandle count is 512")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn single(aval: &mut Availability, idx: usize) {
|
||||
aval.set_available(idx, true);
|
||||
assert!(aval.available());
|
||||
|
||||
aval.set_available(idx, true);
|
||||
|
||||
aval.set_available(idx, false);
|
||||
assert!(!aval.available());
|
||||
|
||||
aval.set_available(idx, false);
|
||||
assert!(!aval.available());
|
||||
}
|
||||
|
||||
fn multi(aval: &mut Availability, mut idx: Vec<usize>) {
|
||||
idx.iter().for_each(|idx| aval.set_available(*idx, true));
|
||||
|
||||
assert!(aval.available());
|
||||
|
||||
while let Some(idx) = idx.pop() {
|
||||
assert!(aval.available());
|
||||
aval.set_available(idx, false);
|
||||
}
|
||||
|
||||
assert!(!aval.available());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn availability() {
|
||||
let mut aval = Availability::default();
|
||||
|
||||
single(&mut aval, 1);
|
||||
single(&mut aval, 128);
|
||||
single(&mut aval, 256);
|
||||
single(&mut aval, 511);
|
||||
|
||||
let idx = (0..511).filter(|i| i % 3 == 0 && i % 5 == 0).collect();
|
||||
|
||||
multi(&mut aval, idx);
|
||||
|
||||
multi(&mut aval, (0..511).collect())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn overflow() {
|
||||
let mut aval = Availability::default();
|
||||
single(&mut aval, 512);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pin_point() {
|
||||
let mut aval = Availability::default();
|
||||
|
||||
aval.set_available(438, true);
|
||||
|
||||
aval.set_available(479, true);
|
||||
|
||||
assert_eq!(aval.0[3], 1 << (438 - 384) | 1 << (479 - 384));
|
||||
}
|
||||
}
|
@ -1,42 +1,47 @@
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use std::time::Duration;
|
||||
use std::{io, mem};
|
||||
use std::{io, num::NonZeroUsize, time::Duration};
|
||||
|
||||
use actix_rt::net::TcpStream;
|
||||
use actix_rt::time::{sleep_until, Instant};
|
||||
use actix_rt::{self as rt, System};
|
||||
use log::{error, info};
|
||||
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver};
|
||||
use tokio::sync::oneshot;
|
||||
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
|
||||
|
||||
use crate::accept::AcceptLoop;
|
||||
use crate::config::{ConfiguredService, ServiceConfig};
|
||||
use crate::server::{Server, ServerCommand};
|
||||
use crate::service::{InternalServiceFactory, ServiceFactory, StreamNewService};
|
||||
use crate::signals::{Signal, Signals};
|
||||
use crate::socket::{MioListener, StdSocketAddr, StdTcpListener, ToSocketAddrs};
|
||||
use crate::socket::{MioTcpListener, MioTcpSocket};
|
||||
use crate::waker_queue::{WakerInterest, WakerQueue};
|
||||
use crate::worker::{self, ServerWorker, ServerWorkerConfig, WorkerAvailability, WorkerHandle};
|
||||
use crate::{join_all, Token};
|
||||
use crate::{
|
||||
server::ServerCommand,
|
||||
service::{InternalServiceFactory, ServerServiceFactory, StreamNewService},
|
||||
socket::{create_mio_tcp_listener, MioListener, MioTcpListener, StdTcpListener, ToSocketAddrs},
|
||||
worker::ServerWorkerConfig,
|
||||
Server,
|
||||
};
|
||||
|
||||
/// Server builder
|
||||
/// 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 {
|
||||
threads: usize,
|
||||
token: Token,
|
||||
backlog: u32,
|
||||
handles: Vec<(usize, WorkerHandle)>,
|
||||
services: Vec<Box<dyn InternalServiceFactory>>,
|
||||
sockets: Vec<(Token, String, MioListener)>,
|
||||
accept: AcceptLoop,
|
||||
exit: bool,
|
||||
no_signals: bool,
|
||||
cmd: UnboundedReceiver<ServerCommand>,
|
||||
server: Server,
|
||||
notify: Vec<oneshot::Sender<()>>,
|
||||
worker_config: ServerWorkerConfig,
|
||||
pub(crate) threads: usize,
|
||||
pub(crate) token: usize,
|
||||
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>,
|
||||
pub(crate) cmd_rx: UnboundedReceiver<ServerCommand>,
|
||||
pub(crate) worker_config: ServerWorkerConfig,
|
||||
}
|
||||
|
||||
impl Default for ServerBuilder {
|
||||
@ -48,30 +53,36 @@ impl Default for ServerBuilder {
|
||||
impl ServerBuilder {
|
||||
/// Create new Server builder instance
|
||||
pub fn new() -> ServerBuilder {
|
||||
let (tx, rx) = unbounded_channel();
|
||||
let server = Server::new(tx);
|
||||
let (cmd_tx, cmd_rx) = unbounded_channel();
|
||||
|
||||
ServerBuilder {
|
||||
threads: num_cpus::get(),
|
||||
token: Token::default(),
|
||||
handles: Vec::new(),
|
||||
services: Vec::new(),
|
||||
threads: std::thread::available_parallelism().map_or(2, NonZeroUsize::get),
|
||||
token: 0,
|
||||
factories: Vec::new(),
|
||||
sockets: Vec::new(),
|
||||
accept: AcceptLoop::new(server.clone()),
|
||||
backlog: 2048,
|
||||
mptcp: MpTcp::Disabled,
|
||||
exit: false,
|
||||
no_signals: false,
|
||||
cmd: rx,
|
||||
notify: Vec::new(),
|
||||
server,
|
||||
listen_os_signals: true,
|
||||
cmd_tx,
|
||||
cmd_rx,
|
||||
worker_config: ServerWorkerConfig::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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;
|
||||
@ -98,10 +109,9 @@ impl ServerBuilder {
|
||||
|
||||
/// Set the maximum number of pending connections.
|
||||
///
|
||||
/// This refers to the number of clients that can be waiting to be served.
|
||||
/// Exceeding this number results in the client getting an error when
|
||||
/// attempting to connect. It should only affect servers under significant
|
||||
/// load.
|
||||
/// This refers to the number of clients that can be waiting to be served. Exceeding this number
|
||||
/// results in the client getting an error when attempting to connect. It should only affect
|
||||
/// servers under significant load.
|
||||
///
|
||||
/// Generally set in the 64-2048 range. Default value is 2048.
|
||||
///
|
||||
@ -111,34 +121,59 @@ impl ServerBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the maximum per-worker number of concurrent connections.
|
||||
/// Sets MultiPath TCP (MPTCP) preference on bound sockets.
|
||||
///
|
||||
/// All socket listeners will stop accepting connections when this limit is
|
||||
/// reached for each worker.
|
||||
/// 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.
|
||||
///
|
||||
/// By default max connections is set to a 25k per worker.
|
||||
pub fn maxconn(self, num: usize) -> Self {
|
||||
worker::max_concurrent_connections(num);
|
||||
/// 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
|
||||
}
|
||||
|
||||
/// Stop actix system.
|
||||
/// Sets the maximum per-worker number of concurrent connections.
|
||||
///
|
||||
/// All socket listeners will stop accepting connections when this limit is reached for
|
||||
/// each worker.
|
||||
///
|
||||
/// By default max connections is set to a 25k per worker.
|
||||
pub fn max_concurrent_connections(mut self, num: usize) -> Self {
|
||||
self.worker_config.max_concurrent_connections(num);
|
||||
self
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[deprecated(since = "2.0.0", note = "Renamed to `max_concurrent_connections`.")]
|
||||
pub fn maxconn(self, num: usize) -> Self {
|
||||
self.max_concurrent_connections(num)
|
||||
}
|
||||
|
||||
/// 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 signal handling
|
||||
/// Disables OS signal handling.
|
||||
pub fn disable_signals(mut self) -> Self {
|
||||
self.no_signals = true;
|
||||
self.listen_os_signals = false;
|
||||
self
|
||||
}
|
||||
|
||||
/// Timeout for graceful workers shutdown in seconds.
|
||||
///
|
||||
/// After receiving a stop signal, workers have this much time to finish
|
||||
/// serving requests. Workers still alive after the timeout are force
|
||||
/// dropped.
|
||||
/// After receiving a stop signal, workers have this much time to finish serving requests.
|
||||
/// Workers still alive after the timeout are force dropped.
|
||||
///
|
||||
/// By default shutdown timeout sets to 30 seconds.
|
||||
pub fn shutdown_timeout(mut self, sec: u64) -> Self {
|
||||
@ -147,105 +182,62 @@ impl ServerBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Execute external configuration as part of the server building
|
||||
/// process.
|
||||
/// Adds new service to the server.
|
||||
///
|
||||
/// This function is useful for moving parts of configuration to a
|
||||
/// different module or even library.
|
||||
pub fn configure<F>(mut self, f: F) -> io::Result<ServerBuilder>
|
||||
/// 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: Fn(&mut ServiceConfig) -> io::Result<()>,
|
||||
{
|
||||
let mut cfg = ServiceConfig::new(self.threads, self.backlog);
|
||||
|
||||
f(&mut cfg)?;
|
||||
|
||||
if let Some(apply) = cfg.apply {
|
||||
let mut srv = ConfiguredService::new(apply);
|
||||
for (name, lst) in cfg.services {
|
||||
let token = self.token.next();
|
||||
srv.stream(token, name.clone(), lst.local_addr()?);
|
||||
self.sockets.push((token, name, MioListener::Tcp(lst)));
|
||||
}
|
||||
self.services.push(Box::new(srv));
|
||||
}
|
||||
self.threads = cfg.threads;
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Add new service to the server.
|
||||
pub fn bind<F, U, N: AsRef<str>>(mut self, name: N, addr: U, factory: F) -> io::Result<Self>
|
||||
where
|
||||
F: ServiceFactory<TcpStream>,
|
||||
F: ServerServiceFactory<TcpStream>,
|
||||
U: ToSocketAddrs,
|
||||
N: AsRef<str>,
|
||||
{
|
||||
let sockets = bind_addr(addr, self.backlog)?;
|
||||
let sockets = bind_addr(addrs, self.backlog, &self.mptcp)?;
|
||||
|
||||
tracing::trace!("binding server to: {sockets:?}");
|
||||
|
||||
for lst in sockets {
|
||||
let token = self.token.next();
|
||||
self.services.push(StreamNewService::create(
|
||||
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)));
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Add new unix domain service to the server.
|
||||
#[cfg(unix)]
|
||||
pub fn bind_uds<F, U, N>(self, name: N, addr: U, factory: F) -> io::Result<Self>
|
||||
where
|
||||
F: ServiceFactory<actix_rt::net::UnixStream>,
|
||||
N: AsRef<str>,
|
||||
U: AsRef<std::path::Path>,
|
||||
{
|
||||
// The path must not exist when we try to bind.
|
||||
// Try to remove it to avoid bind error.
|
||||
if let Err(e) = std::fs::remove_file(addr.as_ref()) {
|
||||
// NotFound is expected and not an issue. Anything else is.
|
||||
if e.kind() != std::io::ErrorKind::NotFound {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
let lst = crate::socket::StdUnixListener::bind(addr)?;
|
||||
self.listen_uds(name, lst, factory)
|
||||
}
|
||||
|
||||
/// Add new unix domain service to the server.
|
||||
/// Useful when running as a systemd service and
|
||||
/// a socket FD can be acquired using the systemd crate.
|
||||
#[cfg(unix)]
|
||||
pub fn listen_uds<F, N: AsRef<str>>(
|
||||
mut self,
|
||||
name: N,
|
||||
lst: crate::socket::StdUnixListener,
|
||||
factory: F,
|
||||
) -> io::Result<Self>
|
||||
where
|
||||
F: ServiceFactory<actix_rt::net::UnixStream>,
|
||||
{
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
lst.set_nonblocking(true)?;
|
||||
let token = self.token.next();
|
||||
let addr = StdSocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
|
||||
self.services.push(StreamNewService::create(
|
||||
name.as_ref().to_string(),
|
||||
token,
|
||||
factory,
|
||||
addr,
|
||||
));
|
||||
self.sockets
|
||||
.push((token, name.as_ref().to_string(), MioListener::from(lst)));
|
||||
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,
|
||||
@ -253,13 +245,13 @@ impl ServerBuilder {
|
||||
factory: F,
|
||||
) -> io::Result<Self>
|
||||
where
|
||||
F: ServiceFactory<TcpStream>,
|
||||
F: ServerServiceFactory<TcpStream>,
|
||||
{
|
||||
lst.set_nonblocking(true)?;
|
||||
let addr = lst.local_addr()?;
|
||||
|
||||
let token = self.token.next();
|
||||
self.services.push(StreamNewService::create(
|
||||
let token = self.next_token();
|
||||
self.factories.push(StreamNewService::create(
|
||||
name.as_ref().to_string(),
|
||||
token,
|
||||
factory,
|
||||
@ -268,236 +260,119 @@ impl ServerBuilder {
|
||||
|
||||
self.sockets
|
||||
.push((token, name.as_ref().to_string(), MioListener::from(lst)));
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Starts processing incoming connections and return server controller.
|
||||
pub fn run(mut self) -> Server {
|
||||
pub fn run(self) -> Server {
|
||||
if self.sockets.is_empty() {
|
||||
panic!("Server should have at least one bound socket");
|
||||
} else {
|
||||
info!("Starting {} workers", self.threads);
|
||||
|
||||
// start workers
|
||||
let handles = (0..self.threads)
|
||||
.map(|idx| {
|
||||
let handle = self.start_worker(idx, self.accept.waker_owned());
|
||||
self.handles.push((idx, handle.clone()));
|
||||
|
||||
handle
|
||||
})
|
||||
.collect();
|
||||
|
||||
// start accept thread
|
||||
for sock in &self.sockets {
|
||||
info!("Starting \"{}\" service on {}", sock.1, sock.2);
|
||||
}
|
||||
self.accept.start(
|
||||
mem::take(&mut self.sockets)
|
||||
.into_iter()
|
||||
.map(|t| (t.0, t.2))
|
||||
.collect(),
|
||||
handles,
|
||||
);
|
||||
|
||||
// handle signals
|
||||
if !self.no_signals {
|
||||
Signals::start(self.server.clone());
|
||||
}
|
||||
|
||||
// start http server actor
|
||||
let server = self.server.clone();
|
||||
rt::spawn(self);
|
||||
server
|
||||
tracing::info!("starting {} workers", self.threads);
|
||||
Server::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
fn start_worker(&self, idx: usize, waker: WakerQueue) -> WorkerHandle {
|
||||
let avail = WorkerAvailability::new(waker);
|
||||
let services = self.services.iter().map(|v| v.clone_factory()).collect();
|
||||
|
||||
ServerWorker::start(idx, services, avail, self.worker_config)
|
||||
}
|
||||
|
||||
fn handle_cmd(&mut self, item: ServerCommand) {
|
||||
match item {
|
||||
ServerCommand::Pause(tx) => {
|
||||
self.accept.wake(WakerInterest::Pause);
|
||||
let _ = tx.send(());
|
||||
}
|
||||
ServerCommand::Resume(tx) => {
|
||||
self.accept.wake(WakerInterest::Resume);
|
||||
let _ = tx.send(());
|
||||
}
|
||||
ServerCommand::Signal(sig) => {
|
||||
// Signals support
|
||||
// Handle `SIGINT`, `SIGTERM`, `SIGQUIT` signals and stop actix system
|
||||
match sig {
|
||||
Signal::Int => {
|
||||
info!("SIGINT received, exiting");
|
||||
self.exit = true;
|
||||
self.handle_cmd(ServerCommand::Stop {
|
||||
graceful: false,
|
||||
completion: None,
|
||||
})
|
||||
}
|
||||
Signal::Term => {
|
||||
info!("SIGTERM received, stopping");
|
||||
self.exit = true;
|
||||
self.handle_cmd(ServerCommand::Stop {
|
||||
graceful: true,
|
||||
completion: None,
|
||||
})
|
||||
}
|
||||
Signal::Quit => {
|
||||
info!("SIGQUIT received, exiting");
|
||||
self.exit = true;
|
||||
self.handle_cmd(ServerCommand::Stop {
|
||||
graceful: false,
|
||||
completion: None,
|
||||
})
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
ServerCommand::Notify(tx) => {
|
||||
self.notify.push(tx);
|
||||
}
|
||||
ServerCommand::Stop {
|
||||
graceful,
|
||||
completion,
|
||||
} => {
|
||||
let exit = self.exit;
|
||||
|
||||
// stop accept thread
|
||||
self.accept.wake(WakerInterest::Stop);
|
||||
let notify = std::mem::take(&mut self.notify);
|
||||
|
||||
// stop workers
|
||||
if !self.handles.is_empty() && graceful {
|
||||
let iter = self
|
||||
.handles
|
||||
.iter()
|
||||
.map(move |worker| worker.1.stop(graceful))
|
||||
.collect();
|
||||
|
||||
let fut = join_all(iter);
|
||||
|
||||
rt::spawn(async move {
|
||||
let _ = fut.await;
|
||||
if let Some(tx) = completion {
|
||||
let _ = tx.send(());
|
||||
}
|
||||
for tx in notify {
|
||||
let _ = tx.send(());
|
||||
}
|
||||
if exit {
|
||||
rt::spawn(async {
|
||||
sleep_until(Instant::now() + Duration::from_millis(300)).await;
|
||||
System::current().stop();
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// we need to stop system if server was spawned
|
||||
if self.exit {
|
||||
rt::spawn(async {
|
||||
sleep_until(Instant::now() + Duration::from_millis(300)).await;
|
||||
System::current().stop();
|
||||
});
|
||||
}
|
||||
if let Some(tx) = completion {
|
||||
let _ = tx.send(());
|
||||
}
|
||||
for tx in notify {
|
||||
let _ = tx.send(());
|
||||
}
|
||||
}
|
||||
}
|
||||
ServerCommand::WorkerFaulted(idx) => {
|
||||
let mut found = false;
|
||||
for i in 0..self.handles.len() {
|
||||
if self.handles[i].0 == idx {
|
||||
self.handles.swap_remove(i);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
error!("Worker has died {:?}, restarting", idx);
|
||||
|
||||
let mut new_idx = self.handles.len();
|
||||
'found: loop {
|
||||
for i in 0..self.handles.len() {
|
||||
if self.handles[i].0 == new_idx {
|
||||
new_idx += 1;
|
||||
continue 'found;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
let handle = self.start_worker(new_idx, self.accept.waker_owned());
|
||||
self.handles.push((new_idx, handle.clone()));
|
||||
self.accept.wake(WakerInterest::Worker(handle));
|
||||
}
|
||||
}
|
||||
}
|
||||
fn next_token(&mut self) -> usize {
|
||||
let token = self.token;
|
||||
self.token += 1;
|
||||
token
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for ServerBuilder {
|
||||
type Output = ();
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
loop {
|
||||
match Pin::new(&mut self.cmd).poll_recv(cx) {
|
||||
Poll::Ready(Some(it)) => self.as_mut().get_mut().handle_cmd(it),
|
||||
_ => return Poll::Pending,
|
||||
#[cfg(unix)]
|
||||
impl ServerBuilder {
|
||||
/// 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: 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(err) = std::fs::remove_file(addr.as_ref()) {
|
||||
// NotFound is expected and not an issue. Anything else is.
|
||||
if err.kind() != std::io::ErrorKind::NotFound {
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
|
||||
let lst = crate::socket::StdUnixListener::bind(addr)?;
|
||||
self.listen_uds(name, lst, factory)
|
||||
}
|
||||
|
||||
/// 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,
|
||||
lst: crate::socket::StdUnixListener,
|
||||
factory: F,
|
||||
) -> io::Result<Self>
|
||||
where
|
||||
F: ServerServiceFactory<actix_rt::net::UnixStream>,
|
||||
{
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
|
||||
lst.set_nonblocking(true)?;
|
||||
|
||||
let token = self.next_token();
|
||||
let addr = crate::socket::StdSocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn bind_addr<S: ToSocketAddrs>(
|
||||
addr: S,
|
||||
backlog: u32,
|
||||
mptcp: &MpTcp,
|
||||
) -> io::Result<Vec<MioTcpListener>> {
|
||||
let mut err = None;
|
||||
let mut succ = false;
|
||||
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) => {
|
||||
succ = true;
|
||||
success = true;
|
||||
sockets.push(lst);
|
||||
}
|
||||
Err(e) => err = Some(e),
|
||||
Err(err) => opt_err = Some(err),
|
||||
}
|
||||
}
|
||||
|
||||
if !succ {
|
||||
if let Some(e) = err.take() {
|
||||
Err(e)
|
||||
} else {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Can not bind to address.",
|
||||
))
|
||||
}
|
||||
} else {
|
||||
if success {
|
||||
Ok(sockets)
|
||||
} else if let Some(err) = opt_err.take() {
|
||||
Err(err)
|
||||
} else {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Can not bind to address.",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
@ -1,287 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
use std::future::Future;
|
||||
use std::{fmt, io};
|
||||
|
||||
use actix_rt::net::TcpStream;
|
||||
use actix_service::{
|
||||
fn_service, IntoServiceFactory as IntoBaseServiceFactory,
|
||||
ServiceFactory as BaseServiceFactory,
|
||||
};
|
||||
use actix_utils::counter::CounterGuard;
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use log::error;
|
||||
|
||||
use crate::builder::bind_addr;
|
||||
use crate::service::{BoxedServerService, InternalServiceFactory, StreamService};
|
||||
use crate::socket::{MioStream, MioTcpListener, StdSocketAddr, StdTcpListener, ToSocketAddrs};
|
||||
use crate::{ready, Token};
|
||||
|
||||
pub struct ServiceConfig {
|
||||
pub(crate) services: Vec<(String, MioTcpListener)>,
|
||||
pub(crate) apply: Option<Box<dyn ServiceRuntimeConfiguration>>,
|
||||
pub(crate) threads: usize,
|
||||
pub(crate) backlog: u32,
|
||||
}
|
||||
|
||||
impl ServiceConfig {
|
||||
pub(super) fn new(threads: usize, backlog: u32) -> ServiceConfig {
|
||||
ServiceConfig {
|
||||
threads,
|
||||
backlog,
|
||||
services: Vec::new(),
|
||||
apply: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set number of workers to start.
|
||||
///
|
||||
/// By default server uses number of available logical cpu as workers
|
||||
/// count.
|
||||
pub fn workers(&mut self, num: usize) {
|
||||
self.threads = num;
|
||||
}
|
||||
|
||||
/// Add new service to server
|
||||
pub fn bind<U, N: AsRef<str>>(&mut self, name: N, addr: U) -> io::Result<&mut Self>
|
||||
where
|
||||
U: ToSocketAddrs,
|
||||
{
|
||||
let sockets = bind_addr(addr, self.backlog)?;
|
||||
|
||||
for lst in sockets {
|
||||
self._listen(name.as_ref(), lst);
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Add new service to server
|
||||
pub fn listen<N: AsRef<str>>(&mut self, name: N, lst: StdTcpListener) -> &mut Self {
|
||||
self._listen(name, MioTcpListener::from_std(lst))
|
||||
}
|
||||
|
||||
/// Register service configuration function. This function get called
|
||||
/// during worker runtime configuration. It get executed in worker thread.
|
||||
pub fn apply<F>(&mut self, f: F) -> io::Result<()>
|
||||
where
|
||||
F: Fn(&mut ServiceRuntime) + Send + Clone + 'static,
|
||||
{
|
||||
self.apply = Some(Box::new(f));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn _listen<N: AsRef<str>>(&mut self, name: N, lst: MioTcpListener) -> &mut Self {
|
||||
if self.apply.is_none() {
|
||||
self.apply = Some(Box::new(not_configured));
|
||||
}
|
||||
self.services.push((name.as_ref().to_string(), lst));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct ConfiguredService {
|
||||
rt: Box<dyn ServiceRuntimeConfiguration>,
|
||||
names: HashMap<Token, (String, StdSocketAddr)>,
|
||||
topics: HashMap<String, Token>,
|
||||
services: Vec<Token>,
|
||||
}
|
||||
|
||||
impl ConfiguredService {
|
||||
pub(super) fn new(rt: Box<dyn ServiceRuntimeConfiguration>) -> Self {
|
||||
ConfiguredService {
|
||||
rt,
|
||||
names: HashMap::new(),
|
||||
topics: HashMap::new(),
|
||||
services: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn stream(&mut self, token: Token, name: String, addr: StdSocketAddr) {
|
||||
self.names.insert(token, (name.clone(), addr));
|
||||
self.topics.insert(name, token);
|
||||
self.services.push(token);
|
||||
}
|
||||
}
|
||||
|
||||
impl InternalServiceFactory for ConfiguredService {
|
||||
fn name(&self, token: Token) -> &str {
|
||||
&self.names[&token].0
|
||||
}
|
||||
|
||||
fn clone_factory(&self) -> Box<dyn InternalServiceFactory> {
|
||||
Box::new(Self {
|
||||
rt: self.rt.clone(),
|
||||
names: self.names.clone(),
|
||||
topics: self.topics.clone(),
|
||||
services: self.services.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn create(&self) -> LocalBoxFuture<'static, Result<Vec<(Token, BoxedServerService)>, ()>> {
|
||||
// configure services
|
||||
let mut rt = ServiceRuntime::new(self.topics.clone());
|
||||
self.rt.configure(&mut rt);
|
||||
rt.validate();
|
||||
let mut names = self.names.clone();
|
||||
let tokens = self.services.clone();
|
||||
|
||||
// construct services
|
||||
Box::pin(async move {
|
||||
let mut services = rt.services;
|
||||
// TODO: Proper error handling here
|
||||
for f in rt.onstart.into_iter() {
|
||||
f.await;
|
||||
}
|
||||
let mut res = vec![];
|
||||
for token in tokens {
|
||||
if let Some(srv) = services.remove(&token) {
|
||||
let newserv = srv.new_service(());
|
||||
match newserv.await {
|
||||
Ok(serv) => {
|
||||
res.push((token, serv));
|
||||
}
|
||||
Err(_) => {
|
||||
error!("Can not construct service");
|
||||
return Err(());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let name = names.remove(&token).unwrap().0;
|
||||
res.push((
|
||||
token,
|
||||
Box::new(StreamService::new(fn_service(move |_: TcpStream| {
|
||||
error!("Service {:?} is not configured", name);
|
||||
ready::<Result<_, ()>>(Ok(()))
|
||||
}))),
|
||||
));
|
||||
};
|
||||
}
|
||||
Ok(res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) trait ServiceRuntimeConfiguration: Send {
|
||||
fn clone(&self) -> Box<dyn ServiceRuntimeConfiguration>;
|
||||
|
||||
fn configure(&self, rt: &mut ServiceRuntime);
|
||||
}
|
||||
|
||||
impl<F> ServiceRuntimeConfiguration for F
|
||||
where
|
||||
F: Fn(&mut ServiceRuntime) + Send + Clone + 'static,
|
||||
{
|
||||
fn clone(&self) -> Box<dyn ServiceRuntimeConfiguration> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn configure(&self, rt: &mut ServiceRuntime) {
|
||||
(self)(rt)
|
||||
}
|
||||
}
|
||||
|
||||
fn not_configured(_: &mut ServiceRuntime) {
|
||||
error!("Service is not configured");
|
||||
}
|
||||
|
||||
pub struct ServiceRuntime {
|
||||
names: HashMap<String, Token>,
|
||||
services: HashMap<Token, BoxedNewService>,
|
||||
onstart: Vec<LocalBoxFuture<'static, ()>>,
|
||||
}
|
||||
|
||||
impl ServiceRuntime {
|
||||
fn new(names: HashMap<String, Token>) -> Self {
|
||||
ServiceRuntime {
|
||||
names,
|
||||
services: HashMap::new(),
|
||||
onstart: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn validate(&self) {
|
||||
for (name, token) in &self.names {
|
||||
if !self.services.contains_key(&token) {
|
||||
error!("Service {:?} is not configured", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Register service.
|
||||
///
|
||||
/// Name of the service must be registered during configuration stage with
|
||||
/// *ServiceConfig::bind()* or *ServiceConfig::listen()* methods.
|
||||
pub fn service<T, F>(&mut self, name: &str, service: F)
|
||||
where
|
||||
F: IntoBaseServiceFactory<T, TcpStream>,
|
||||
T: BaseServiceFactory<TcpStream, Config = ()> + 'static,
|
||||
T::Future: 'static,
|
||||
T::Service: 'static,
|
||||
T::InitError: fmt::Debug,
|
||||
{
|
||||
// let name = name.to_owned();
|
||||
if let Some(token) = self.names.get(name) {
|
||||
self.services.insert(
|
||||
*token,
|
||||
Box::new(ServiceFactory {
|
||||
inner: service.into_factory(),
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
panic!("Unknown service: {:?}", name);
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute future before services initialization.
|
||||
pub fn on_start<F>(&mut self, fut: F)
|
||||
where
|
||||
F: Future<Output = ()> + 'static,
|
||||
{
|
||||
self.onstart.push(Box::pin(fut))
|
||||
}
|
||||
}
|
||||
|
||||
type BoxedNewService = Box<
|
||||
dyn BaseServiceFactory<
|
||||
(Option<CounterGuard>, MioStream),
|
||||
Response = (),
|
||||
Error = (),
|
||||
InitError = (),
|
||||
Config = (),
|
||||
Service = BoxedServerService,
|
||||
Future = LocalBoxFuture<'static, Result<BoxedServerService, ()>>,
|
||||
>,
|
||||
>;
|
||||
|
||||
struct ServiceFactory<T> {
|
||||
inner: T,
|
||||
}
|
||||
|
||||
impl<T> BaseServiceFactory<(Option<CounterGuard>, MioStream)> for ServiceFactory<T>
|
||||
where
|
||||
T: BaseServiceFactory<TcpStream, Config = ()>,
|
||||
T::Future: 'static,
|
||||
T::Service: 'static,
|
||||
T::Error: 'static,
|
||||
T::InitError: fmt::Debug + 'static,
|
||||
{
|
||||
type Response = ();
|
||||
type Error = ();
|
||||
type Config = ();
|
||||
type Service = BoxedServerService;
|
||||
type InitError = ();
|
||||
type Future = LocalBoxFuture<'static, Result<BoxedServerService, ()>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
let fut = self.inner.new_service(());
|
||||
Box::pin(async move {
|
||||
match fut.await {
|
||||
Ok(s) => Ok(Box::new(StreamService::new(s)) as BoxedServerService),
|
||||
Err(e) => {
|
||||
error!("Can not construct service: {:?}", e);
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
56
actix-server/src/handle.rs
Normal file
56
actix-server/src/handle.rs
Normal file
@ -0,0 +1,56 @@
|
||||
use std::future::Future;
|
||||
|
||||
use tokio::sync::{mpsc::UnboundedSender, oneshot};
|
||||
|
||||
use crate::server::ServerCommand;
|
||||
|
||||
/// Server handle.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ServerHandle {
|
||||
cmd_tx: UnboundedSender<ServerCommand>,
|
||||
}
|
||||
|
||||
impl ServerHandle {
|
||||
pub(crate) fn new(cmd_tx: UnboundedSender<ServerCommand>) -> Self {
|
||||
ServerHandle { cmd_tx }
|
||||
}
|
||||
|
||||
pub(crate) fn worker_faulted(&self, idx: usize) {
|
||||
let _ = self.cmd_tx.send(ServerCommand::WorkerFaulted(idx));
|
||||
}
|
||||
|
||||
/// Pause accepting incoming connections.
|
||||
///
|
||||
/// May drop socket pending connection. All open connections remain active.
|
||||
pub fn pause(&self) -> impl Future<Output = ()> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let _ = self.cmd_tx.send(ServerCommand::Pause(tx));
|
||||
async {
|
||||
let _ = rx.await;
|
||||
}
|
||||
}
|
||||
|
||||
/// Resume accepting incoming connections.
|
||||
pub fn resume(&self) -> impl Future<Output = ()> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let _ = self.cmd_tx.send(ServerCommand::Resume(tx));
|
||||
async {
|
||||
let _ = rx.await;
|
||||
}
|
||||
}
|
||||
|
||||
/// 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;
|
||||
}
|
||||
}
|
||||
}
|
78
actix-server/src/join_all.rs
Normal file
78
actix-server/src/join_all.rs
Normal file
@ -0,0 +1,78 @@
|
||||
use std::{
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
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.
|
||||
pub(crate) struct JoinAll<T> {
|
||||
fut: Vec<JoinFuture<T>>,
|
||||
}
|
||||
|
||||
pub(crate) fn join_all<T>(fut: Vec<impl Future<Output = T> + Send + 'static>) -> JoinAll<T> {
|
||||
let fut = fut
|
||||
.into_iter()
|
||||
.map(|f| JoinFuture::Future(Box::pin(f)))
|
||||
.collect();
|
||||
|
||||
JoinAll { fut }
|
||||
}
|
||||
|
||||
enum JoinFuture<T> {
|
||||
Future(BoxFuture<'static, T>),
|
||||
Result(Option<T>),
|
||||
}
|
||||
|
||||
impl<T> Unpin for JoinAll<T> {}
|
||||
|
||||
impl<T> Future for JoinAll<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 JoinFuture::Future(f) = fut {
|
||||
match f.as_mut().poll(cx) {
|
||||
Poll::Ready(t) => {
|
||||
*fut = JoinFuture::Result(Some(t));
|
||||
}
|
||||
Poll::Pending => ready = false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ready {
|
||||
let mut res = Vec::new();
|
||||
for fut in this.fut.iter_mut() {
|
||||
if let JoinFuture::Result(f) = fut {
|
||||
res.push(f.take().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
Poll::Ready(res)
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use actix_utils::future::ready;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_join_all() {
|
||||
let futs = vec![ready(Ok(1)), ready(Err(3)), ready(Ok(9))];
|
||||
let mut res = join_all(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,12 +1,13 @@
|
||||
//! General purpose TCP server.
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
|
||||
mod accept;
|
||||
mod availability;
|
||||
mod builder;
|
||||
mod config;
|
||||
mod handle;
|
||||
mod join_all;
|
||||
mod server;
|
||||
mod service;
|
||||
mod signals;
|
||||
@ -15,129 +16,19 @@ mod test_server;
|
||||
mod waker_queue;
|
||||
mod worker;
|
||||
|
||||
pub use self::builder::ServerBuilder;
|
||||
pub use self::config::{ServiceConfig, ServiceRuntime};
|
||||
pub use self::server::Server;
|
||||
pub use self::service::ServiceFactory;
|
||||
pub use self::test_server::TestServer;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use self::socket::FromStream;
|
||||
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
/// Socket ID token
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct Token(usize);
|
||||
|
||||
impl Default for Token {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Token {
|
||||
fn new() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
|
||||
pub(crate) fn next(&mut self) -> Token {
|
||||
let token = Token(self.0);
|
||||
self.0 += 1;
|
||||
token
|
||||
}
|
||||
}
|
||||
pub use self::{
|
||||
builder::{MpTcp, ServerBuilder},
|
||||
handle::ServerHandle,
|
||||
server::Server,
|
||||
service::ServerServiceFactory,
|
||||
test_server::TestServer,
|
||||
};
|
||||
|
||||
/// Start server building process
|
||||
#[doc(hidden)]
|
||||
#[deprecated(since = "2.0.0", note = "Use `Server::build()`.")]
|
||||
pub fn new() -> ServerBuilder {
|
||||
ServerBuilder::default()
|
||||
}
|
||||
|
||||
// temporary Ready type for std::future::{ready, Ready}; Can be removed when MSRV surpass 1.48
|
||||
#[doc(hidden)]
|
||||
pub struct Ready<T>(Option<T>);
|
||||
|
||||
pub(crate) fn ready<T>(t: T) -> Ready<T> {
|
||||
Ready(Some(t))
|
||||
}
|
||||
|
||||
impl<T> Unpin for Ready<T> {}
|
||||
|
||||
impl<T> Future for Ready<T> {
|
||||
type Output = T;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
Poll::Ready(self.get_mut().0.take().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
// 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 JoinAll<T> {
|
||||
fut: Vec<JoinFuture<T>>,
|
||||
}
|
||||
|
||||
pub(crate) fn join_all<T>(fut: Vec<impl Future<Output = T> + 'static>) -> JoinAll<T> {
|
||||
let fut = fut
|
||||
.into_iter()
|
||||
.map(|f| JoinFuture::Future(Box::pin(f)))
|
||||
.collect();
|
||||
|
||||
JoinAll { fut }
|
||||
}
|
||||
|
||||
enum JoinFuture<T> {
|
||||
Future(Pin<Box<dyn Future<Output = T>>>),
|
||||
Result(Option<T>),
|
||||
}
|
||||
|
||||
impl<T> Unpin for JoinAll<T> {}
|
||||
|
||||
impl<T> Future for JoinAll<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 JoinFuture::Future(f) = fut {
|
||||
match f.as_mut().poll(cx) {
|
||||
Poll::Ready(t) => {
|
||||
*fut = JoinFuture::Result(Some(t));
|
||||
}
|
||||
Poll::Pending => ready = false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ready {
|
||||
let mut res = Vec::new();
|
||||
for fut in this.fut.iter_mut() {
|
||||
if let JoinFuture::Result(f) = fut {
|
||||
res.push(f.take().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
Poll::Ready(res)
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_join_all() {
|
||||
let futs = vec![ready(Ok(1)), ready(Err(3)), ready(Ok(9))];
|
||||
let mut res = join_all(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,112 +1,368 @@
|
||||
use std::future::Future;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use std::{
|
||||
future::Future,
|
||||
io, mem,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
use tokio::sync::oneshot;
|
||||
use actix_rt::{time::sleep, System};
|
||||
use futures_core::{future::BoxFuture, Stream};
|
||||
use futures_util::stream::StreamExt as _;
|
||||
use tokio::sync::{mpsc::UnboundedReceiver, oneshot};
|
||||
use tracing::{error, info};
|
||||
|
||||
use crate::builder::ServerBuilder;
|
||||
use crate::signals::Signal;
|
||||
use crate::{
|
||||
accept::Accept,
|
||||
builder::ServerBuilder,
|
||||
join_all::join_all,
|
||||
service::InternalServiceFactory,
|
||||
signals::{SignalKind, Signals},
|
||||
waker_queue::{WakerInterest, WakerQueue},
|
||||
worker::{ServerWorker, ServerWorkerConfig, WorkerHandleServer},
|
||||
ServerHandle,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ServerCommand {
|
||||
/// 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<()>),
|
||||
Signal(Signal),
|
||||
/// Whether to try and shut down gracefully
|
||||
|
||||
/// 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,
|
||||
},
|
||||
/// Notify of server stop
|
||||
Notify(oneshot::Sender<()>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Server(
|
||||
UnboundedSender<ServerCommand>,
|
||||
Option<oneshot::Receiver<()>>,
|
||||
);
|
||||
/// General purpose TCP server that runs services receiving Tokio `TcpStream`s.
|
||||
///
|
||||
/// Handles creating worker threads, restarting faulted workers, connection accepting, and
|
||||
/// back-pressure logic.
|
||||
///
|
||||
/// Creates a worker per CPU core (or the number specified in [`ServerBuilder::workers`]) and
|
||||
/// distributes connections with a round-robin strategy.
|
||||
///
|
||||
/// The [Server] must be awaited or polled in order to start running. It will resolve when the
|
||||
/// server has fully shut down.
|
||||
///
|
||||
/// # Shutdown Signals
|
||||
/// 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.
|
||||
///
|
||||
/// # Examples
|
||||
/// The following is a TCP echo server. Test using `telnet 127.0.0.1 8080`.
|
||||
///
|
||||
/// ```no_run
|
||||
/// use std::io;
|
||||
///
|
||||
/// use actix_rt::net::TcpStream;
|
||||
/// use actix_server::Server;
|
||||
/// use actix_service::{fn_service, ServiceFactoryExt as _};
|
||||
/// use bytes::BytesMut;
|
||||
/// use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};
|
||||
///
|
||||
/// #[actix_rt::main]
|
||||
/// async fn main() -> io::Result<()> {
|
||||
/// let bind_addr = ("127.0.0.1", 8080);
|
||||
///
|
||||
/// Server::build()
|
||||
/// .bind("echo", bind_addr, move || {
|
||||
/// fn_service(move |mut stream: TcpStream| {
|
||||
/// async move {
|
||||
/// let mut size = 0;
|
||||
/// let mut buf = BytesMut::new();
|
||||
///
|
||||
/// loop {
|
||||
/// match stream.read_buf(&mut buf).await {
|
||||
/// // end of stream; bail from loop
|
||||
/// Ok(0) => break,
|
||||
///
|
||||
/// // write bytes back to stream
|
||||
/// Ok(bytes_read) => {
|
||||
/// stream.write_all(&buf[size..]).await.unwrap();
|
||||
/// size += bytes_read;
|
||||
/// }
|
||||
///
|
||||
/// Err(err) => {
|
||||
/// eprintln!("Stream Error: {:?}", err);
|
||||
/// return Err(());
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// })
|
||||
/// .map_err(|err| eprintln!("Service Error: {:?}", err))
|
||||
/// })?
|
||||
/// .run()
|
||||
/// .await
|
||||
/// }
|
||||
/// ```
|
||||
#[must_use = "Server does nothing unless you `.await` or poll it"]
|
||||
pub struct Server {
|
||||
handle: ServerHandle,
|
||||
fut: BoxFuture<'static, io::Result<()>>,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
pub(crate) fn new(tx: UnboundedSender<ServerCommand>) -> Self {
|
||||
Server(tx, None)
|
||||
}
|
||||
|
||||
/// Start server building process
|
||||
/// Create server build.
|
||||
pub fn build() -> ServerBuilder {
|
||||
ServerBuilder::default()
|
||||
}
|
||||
|
||||
pub(crate) fn signal(&self, sig: Signal) {
|
||||
let _ = self.0.send(ServerCommand::Signal(sig));
|
||||
pub(crate) fn new(builder: ServerBuilder) -> Self {
|
||||
Server {
|
||||
handle: ServerHandle::new(builder.cmd_tx.clone()),
|
||||
fut: Box::pin(ServerInner::run(builder)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn worker_faulted(&self, idx: usize) {
|
||||
let _ = self.0.send(ServerCommand::WorkerFaulted(idx));
|
||||
}
|
||||
|
||||
/// Pause accepting incoming connections
|
||||
/// Get a `Server` handle that can be used issue commands and change it's state.
|
||||
///
|
||||
/// If socket contains some pending connection, they might be dropped.
|
||||
/// All opened connection remains active.
|
||||
pub fn pause(&self) -> impl Future<Output = ()> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let _ = self.0.send(ServerCommand::Pause(tx));
|
||||
async {
|
||||
let _ = rx.await;
|
||||
}
|
||||
}
|
||||
|
||||
/// Resume accepting incoming connections
|
||||
pub fn resume(&self) -> impl Future<Output = ()> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let _ = self.0.send(ServerCommand::Resume(tx));
|
||||
async {
|
||||
let _ = rx.await;
|
||||
}
|
||||
}
|
||||
|
||||
/// Stop incoming connection processing, stop all workers and exit.
|
||||
///
|
||||
/// If server starts with `spawn()` method, then spawned thread get terminated.
|
||||
pub fn stop(&self, graceful: bool) -> impl Future<Output = ()> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let _ = self.0.send(ServerCommand::Stop {
|
||||
graceful,
|
||||
completion: Some(tx),
|
||||
});
|
||||
async {
|
||||
let _ = rx.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Server {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone(), None)
|
||||
/// See [ServerHandle](ServerHandle) for usage.
|
||||
pub fn handle(&self) -> ServerHandle {
|
||||
self.handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for Server {
|
||||
type Output = io::Result<()>;
|
||||
|
||||
#[inline]
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.get_mut();
|
||||
Pin::new(&mut Pin::into_inner(self).fut).poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
if this.1.is_none() {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
if this.0.send(ServerCommand::Notify(tx)).is_err() {
|
||||
return Poll::Ready(Ok(()));
|
||||
pub struct ServerInner {
|
||||
worker_handles: Vec<WorkerHandleServer>,
|
||||
accept_handle: Option<thread::JoinHandle<()>>,
|
||||
worker_config: ServerWorkerConfig,
|
||||
services: Vec<Box<dyn InternalServiceFactory>>,
|
||||
waker_queue: WakerQueue,
|
||||
system_stop: bool,
|
||||
stopping: bool,
|
||||
}
|
||||
|
||||
impl ServerInner {
|
||||
async fn run(builder: ServerBuilder) -> io::Result<()> {
|
||||
let (mut this, mut mux) = Self::run_sync(builder)?;
|
||||
|
||||
while let Some(cmd) = mux.next().await {
|
||||
this.handle_cmd(cmd).await;
|
||||
|
||||
if this.stopping {
|
||||
break;
|
||||
}
|
||||
this.1 = Some(rx);
|
||||
}
|
||||
|
||||
match Pin::new(this.1.as_mut().unwrap()).poll(cx) {
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(_) => Poll::Ready(Ok(())),
|
||||
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(());
|
||||
}
|
||||
|
||||
ServerCommand::Resume(tx) => {
|
||||
self.waker_queue.wake(WakerInterest::Resume);
|
||||
let _ = tx.send(());
|
||||
}
|
||||
|
||||
ServerCommand::Stop {
|
||||
graceful,
|
||||
completion,
|
||||
force_system_stop,
|
||||
} => {
|
||||
self.stopping = true;
|
||||
|
||||
// Signal accept thread to stop.
|
||||
// Signal is non-blocking; we wait for thread to stop later.
|
||||
self.waker_queue.wake(WakerInterest::Stop);
|
||||
|
||||
// send stop signal to workers
|
||||
let workers_stop = self
|
||||
.worker_handles
|
||||
.iter()
|
||||
.map(|worker| worker.stop(graceful))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if graceful {
|
||||
// wait for all workers to shut down
|
||||
let _ = join_all(workers_stop).await;
|
||||
}
|
||||
|
||||
// wait for accept thread stop
|
||||
self.accept_handle
|
||||
.take()
|
||||
.unwrap()
|
||||
.join()
|
||||
.expect("Accept thread must not panic in any case");
|
||||
|
||||
if let Some(tx) = completion {
|
||||
let _ = tx.send(());
|
||||
}
|
||||
|
||||
if 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);
|
||||
|
||||
let factories = self
|
||||
.services
|
||||
.iter()
|
||||
.map(|service| service.clone_factory())
|
||||
.collect();
|
||||
|
||||
match ServerWorker::start(
|
||||
idx,
|
||||
factories,
|
||||
self.waker_queue.clone(),
|
||||
self.worker_config,
|
||||
) {
|
||||
Ok((handle_accept, handle_server)) => {
|
||||
*self
|
||||
.worker_handles
|
||||
.iter_mut()
|
||||
.find(|wrk| wrk.idx == idx)
|
||||
.unwrap() = handle_server;
|
||||
|
||||
self.waker_queue.wake(WakerInterest::Worker(handle_accept));
|
||||
}
|
||||
|
||||
Err(err) => error!("can not restart worker {}: {}", idx, err),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn map_signal(signal: SignalKind) -> ServerCommand {
|
||||
match signal {
|
||||
SignalKind::Int => {
|
||||
info!("SIGINT received; starting forced shutdown");
|
||||
ServerCommand::Stop {
|
||||
graceful: false,
|
||||
completion: None,
|
||||
force_system_stop: true,
|
||||
}
|
||||
}
|
||||
|
||||
SignalKind::Term => {
|
||||
info!("SIGTERM received; starting graceful shutdown");
|
||||
ServerCommand::Stop {
|
||||
graceful: true,
|
||||
completion: None,
|
||||
force_system_stop: true,
|
||||
}
|
||||
}
|
||||
|
||||
SignalKind::Quit => {
|
||||
info!("SIGQUIT received; starting forced shutdown");
|
||||
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,32 +1,37 @@
|
||||
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::counter::CounterGuard;
|
||||
use actix_utils::future::{ready, Ready};
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use log::error;
|
||||
use tracing::error;
|
||||
|
||||
use crate::socket::{FromStream, MioStream};
|
||||
use crate::{ready, Ready, Token};
|
||||
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;
|
||||
}
|
||||
|
||||
pub(crate) trait InternalServiceFactory: Send {
|
||||
fn name(&self, token: Token) -> &str;
|
||||
fn name(&self, token: usize) -> &str;
|
||||
|
||||
fn clone_factory(&self) -> Box<dyn InternalServiceFactory>;
|
||||
|
||||
fn create(&self) -> LocalBoxFuture<'static, Result<Vec<(Token, BoxedServerService)>, ()>>;
|
||||
fn create(&self) -> LocalBoxFuture<'static, Result<(usize, BoxedServerService), ()>>;
|
||||
}
|
||||
|
||||
pub(crate) type BoxedServerService = Box<
|
||||
dyn Service<
|
||||
(Option<CounterGuard>, MioStream),
|
||||
(WorkerCounterGuard, MioStream),
|
||||
Response = (),
|
||||
Error = (),
|
||||
Future = Ready<Result<(), ()>>,
|
||||
@ -47,7 +52,7 @@ impl<S, I> StreamService<S, I> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, I> Service<(Option<CounterGuard>, MioStream)> for StreamService<S, I>
|
||||
impl<S, I> Service<(WorkerCounterGuard, MioStream)> for StreamService<S, I>
|
||||
where
|
||||
S: Service<I>,
|
||||
S::Future: 'static,
|
||||
@ -62,7 +67,7 @@ where
|
||||
self.service.poll_ready(ctx).map_err(|_| ())
|
||||
}
|
||||
|
||||
fn call(&self, (guard, req): (Option<CounterGuard>, MioStream)) -> Self::Future {
|
||||
fn call(&self, (guard, req): (WorkerCounterGuard, MioStream)) -> Self::Future {
|
||||
ready(match FromStream::from_mio(req) {
|
||||
Ok(stream) => {
|
||||
let f = self.service.call(stream);
|
||||
@ -72,30 +77,30 @@ 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: Token,
|
||||
token: usize,
|
||||
addr: SocketAddr,
|
||||
_t: PhantomData<Io>,
|
||||
}
|
||||
|
||||
impl<F, Io> StreamNewService<F, Io>
|
||||
where
|
||||
F: ServiceFactory<Io>,
|
||||
F: ServerServiceFactory<Io>,
|
||||
Io: FromStream + Send + 'static,
|
||||
{
|
||||
pub(crate) fn create(
|
||||
name: String,
|
||||
token: Token,
|
||||
token: usize,
|
||||
inner: F,
|
||||
addr: SocketAddr,
|
||||
) -> Box<dyn InternalServiceFactory> {
|
||||
@ -111,10 +116,10 @@ where
|
||||
|
||||
impl<F, Io> InternalServiceFactory for StreamNewService<F, Io>
|
||||
where
|
||||
F: ServiceFactory<Io>,
|
||||
F: ServerServiceFactory<Io>,
|
||||
Io: FromStream + Send + 'static,
|
||||
{
|
||||
fn name(&self, _: Token) -> &str {
|
||||
fn name(&self, _: usize) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
@ -128,14 +133,14 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
fn create(&self) -> LocalBoxFuture<'static, Result<Vec<(Token, BoxedServerService)>, ()>> {
|
||||
fn create(&self) -> LocalBoxFuture<'static, Result<(usize, BoxedServerService), ()>> {
|
||||
let token = self.token;
|
||||
let fut = self.inner.create().new_service(());
|
||||
Box::pin(async move {
|
||||
match fut.await {
|
||||
Ok(inner) => {
|
||||
let service = Box::new(StreamService::new(inner)) as _;
|
||||
Ok(vec![(token, service)])
|
||||
Ok((token, service))
|
||||
}
|
||||
Err(_) => Err(()),
|
||||
}
|
||||
@ -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 = ()>,
|
||||
|
@ -1,49 +1,66 @@
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use std::{
|
||||
fmt,
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use crate::server::Server;
|
||||
use tracing::trace;
|
||||
|
||||
/// Different types of process signals
|
||||
#[allow(dead_code)]
|
||||
#[derive(PartialEq, Clone, Copy, Debug)]
|
||||
pub(crate) enum Signal {
|
||||
/// SIGHUP
|
||||
Hup,
|
||||
/// SIGINT
|
||||
/// Types of process signals.
|
||||
// #[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[allow(dead_code)] // variants are never constructed on non-unix
|
||||
pub(crate) enum SignalKind {
|
||||
/// `SIGINT`
|
||||
Int,
|
||||
/// SIGTERM
|
||||
|
||||
/// `SIGTERM`
|
||||
Term,
|
||||
/// SIGQUIT
|
||||
|
||||
/// `SIGQUIT`
|
||||
Quit,
|
||||
}
|
||||
|
||||
impl fmt::Display for SignalKind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(match self {
|
||||
SignalKind::Int => "SIGINT",
|
||||
SignalKind::Term => "SIGTERM",
|
||||
SignalKind::Quit => "SIGQUIT",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Process signal listener.
|
||||
pub(crate) struct Signals {
|
||||
srv: Server,
|
||||
#[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 {
|
||||
pub(crate) fn start(srv: Server) {
|
||||
/// Constructs an OS signal listening future.
|
||||
pub(crate) fn new() -> Self {
|
||||
trace!("setting up OS signal listener");
|
||||
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
actix_rt::spawn(Signals {
|
||||
srv,
|
||||
Signals {
|
||||
signals: Box::pin(actix_rt::signal::ctrl_c()),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use actix_rt::signal::unix;
|
||||
|
||||
let sig_map = [
|
||||
(unix::SignalKind::interrupt(), Signal::Int),
|
||||
(unix::SignalKind::hangup(), Signal::Hup),
|
||||
(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
|
||||
@ -52,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
|
||||
)
|
||||
@ -62,32 +79,29 @@ impl Signals {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
actix_rt::spawn(Signals { srv, signals });
|
||||
Signals { signals }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for Signals {
|
||||
type Output = ();
|
||||
type Output = SignalKind;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
#[cfg(not(unix))]
|
||||
match self.signals.as_mut().poll(cx) {
|
||||
Poll::Ready(_) => {
|
||||
self.srv.signal(Signal::Int);
|
||||
Poll::Ready(())
|
||||
}
|
||||
Poll::Pending => Poll::Pending,
|
||||
{
|
||||
self.signals.as_mut().poll(cx).map(|_| SignalKind::Int)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
for (sig, fut) in self.signals.iter_mut() {
|
||||
if Pin::new(fut).poll_recv(cx).is_ready() {
|
||||
let sig = *sig;
|
||||
self.srv.signal(sig);
|
||||
return Poll::Ready(());
|
||||
if fut.poll_recv(cx).is_ready() {
|
||||
trace!("{} received", sig);
|
||||
return Poll::Ready(*sig);
|
||||
}
|
||||
}
|
||||
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +1,18 @@
|
||||
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;
|
||||
use mio::event::Source;
|
||||
use mio::net::TcpStream as MioTcpStream;
|
||||
use mio::{Interest, Registry, Token};
|
||||
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::io::{FromRawSocket, IntoRawSocket};
|
||||
pub(crate) use mio::net::TcpListener as MioTcpListener;
|
||||
use mio::{event::Source, Interest, Registry, Token};
|
||||
#[cfg(unix)]
|
||||
use {
|
||||
actix_rt::net::UnixStream,
|
||||
mio::net::{SocketAddr as MioSocketAddr, UnixStream as MioUnixStream},
|
||||
std::os::unix::io::{FromRawFd, IntoRawFd},
|
||||
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),
|
||||
#[cfg(unix)]
|
||||
@ -34,21 +22,23 @@ pub(crate) enum MioListener {
|
||||
impl MioListener {
|
||||
pub(crate) fn local_addr(&self) -> SocketAddr {
|
||||
match *self {
|
||||
MioListener::Tcp(ref lst) => SocketAddr::Tcp(lst.local_addr().unwrap()),
|
||||
MioListener::Tcp(ref lst) => lst
|
||||
.local_addr()
|
||||
.map(SocketAddr::Tcp)
|
||||
.unwrap_or(SocketAddr::Unknown),
|
||||
#[cfg(unix)]
|
||||
MioListener::Uds(ref lst) => SocketAddr::Uds(lst.local_addr().unwrap()),
|
||||
MioListener::Uds(ref lst) => lst
|
||||
.local_addr()
|
||||
.map(SocketAddr::Uds)
|
||||
.unwrap_or(SocketAddr::Unknown),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn accept(&self) -> io::Result<Option<(MioStream, SocketAddr)>> {
|
||||
pub(crate) fn accept(&self) -> io::Result<MioStream> {
|
||||
match *self {
|
||||
MioListener::Tcp(ref lst) => lst
|
||||
.accept()
|
||||
.map(|(stream, addr)| Some((MioStream::Tcp(stream), SocketAddr::Tcp(addr)))),
|
||||
MioListener::Tcp(ref lst) => lst.accept().map(|(stream, _)| MioStream::Tcp(stream)),
|
||||
#[cfg(unix)]
|
||||
MioListener::Uds(ref lst) => lst
|
||||
.accept()
|
||||
.map(|(stream, addr)| Some((MioStream::Uds(stream), SocketAddr::Uds(addr)))),
|
||||
MioListener::Uds(ref lst) => lst.accept().map(|(stream, _)| MioStream::Uds(stream)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -116,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),
|
||||
}
|
||||
}
|
||||
@ -125,25 +115,27 @@ impl fmt::Debug for MioListener {
|
||||
impl fmt::Display for MioListener {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
MioListener::Tcp(ref lst) => write!(f, "{}", lst.local_addr().ok().unwrap()),
|
||||
MioListener::Tcp(ref lst) => write!(f, "{:?}", lst),
|
||||
#[cfg(unix)]
|
||||
MioListener::Uds(ref lst) => write!(f, "{:?}", lst.local_addr().ok().unwrap()),
|
||||
MioListener::Uds(ref lst) => write!(f, "{:?}", lst),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum SocketAddr {
|
||||
Unknown,
|
||||
Tcp(StdSocketAddr),
|
||||
#[cfg(unix)]
|
||||
Uds(MioSocketAddr),
|
||||
Uds(std::os::unix::net::SocketAddr),
|
||||
}
|
||||
|
||||
impl fmt::Display for SocketAddr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
SocketAddr::Tcp(ref addr) => write!(f, "{}", addr),
|
||||
Self::Unknown => write!(f, "Unknown SocketAddr"),
|
||||
Self::Tcp(ref addr) => write!(f, "{}", addr),
|
||||
#[cfg(unix)]
|
||||
SocketAddr::Uds(ref addr) => write!(f, "{:?}", addr),
|
||||
Self::Uds(ref addr) => write!(f, "{:?}", addr),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -151,69 +143,120 @@ impl fmt::Display for SocketAddr {
|
||||
impl fmt::Debug for SocketAddr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
SocketAddr::Tcp(ref addr) => write!(f, "{:?}", addr),
|
||||
Self::Unknown => write!(f, "Unknown SocketAddr"),
|
||||
Self::Tcp(ref addr) => write!(f, "{:?}", addr),
|
||||
#[cfg(unix)]
|
||||
SocketAddr::Uds(ref addr) => write!(f, "{:?}", addr),
|
||||
Self::Uds(ref addr) => write!(f, "{:?}", addr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MioStream {
|
||||
Tcp(MioTcpStream),
|
||||
Tcp(mio::net::TcpStream),
|
||||
#[cfg(unix)]
|
||||
Uds(MioUnixStream),
|
||||
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>;
|
||||
}
|
||||
|
||||
// FIXME: This is a workaround and we need an efficient way to convert between mio and tokio stream
|
||||
#[cfg(unix)]
|
||||
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.
|
||||
TcpStream::from_std(unsafe { FromRawFd::from_raw_fd(raw) })
|
||||
}
|
||||
MioStream::Uds(_) => {
|
||||
panic!("Should not happen, bug in server impl");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: This is a workaround and we need an efficient way to convert between mio and tokio stream
|
||||
#[cfg(windows)]
|
||||
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.
|
||||
TcpStream::from_std(unsafe { FromRawSocket::from_raw_socket(raw) })
|
||||
mod win_impl {
|
||||
use std::os::windows::io::{FromRawSocket, IntoRawSocket};
|
||||
|
||||
use super::*;
|
||||
|
||||
// TODO: This is a workaround and we need an efficient way to convert between Mio and Tokio stream
|
||||
impl FromStream for TcpStream {
|
||||
fn from_mio(sock: MioStream) -> io::Result<Self> {
|
||||
match sock {
|
||||
MioStream::Tcp(mio) => {
|
||||
let raw = IntoRawSocket::into_raw_socket(mio);
|
||||
// SAFETY: This is an in-place conversion from Mio stream to Tokio stream.
|
||||
TcpStream::from_std(unsafe { FromRawSocket::from_raw_socket(raw) })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: This is a workaround and we need an efficient way to convert between mio and tokio stream
|
||||
#[cfg(unix)]
|
||||
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.
|
||||
UnixStream::from_std(unsafe { FromRawFd::from_raw_fd(raw) })
|
||||
mod unix_impl {
|
||||
use std::os::unix::io::{FromRawFd, IntoRawFd};
|
||||
|
||||
use actix_rt::net::UnixStream;
|
||||
|
||||
use super::*;
|
||||
|
||||
// HACK: This is a workaround and we need an efficient way to convert between Mio and Tokio stream
|
||||
impl FromStream for TcpStream {
|
||||
fn from_mio(sock: MioStream) -> io::Result<Self> {
|
||||
match sock {
|
||||
MioStream::Tcp(mio) => {
|
||||
let raw = IntoRawFd::into_raw_fd(mio);
|
||||
// SAFETY: This is an in-place conversion from Mio stream to Tokio stream.
|
||||
TcpStream::from_std(unsafe { FromRawFd::from_raw_fd(raw) })
|
||||
}
|
||||
MioStream::Uds(_) => {
|
||||
panic!("Should not happen, bug in server impl");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 an in-place conversion from Mio stream to Tokio stream.
|
||||
UnixStream::from_std(unsafe { FromRawFd::from_raw_fd(raw) })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)]
|
||||
@ -227,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,24 +1,22 @@
|
||||
use std::sync::mpsc;
|
||||
use std::{net, thread};
|
||||
use std::{io, net, sync::mpsc, thread};
|
||||
|
||||
use actix_rt::{net::TcpStream, System};
|
||||
|
||||
use crate::{Server, ServerBuilder, ServiceFactory};
|
||||
use crate::{Server, ServerBuilder, ServerHandle, ServerServiceFactory};
|
||||
|
||||
/// The `TestServer` type.
|
||||
/// A testing server.
|
||||
///
|
||||
/// `TestServer` is very simple test server that simplify process of writing
|
||||
/// integration tests for actix-net applications.
|
||||
/// `TestServer` is very simple test server that simplify process of writing integration tests for
|
||||
/// network applications.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use actix_service::fn_service;
|
||||
/// use actix_server::TestServer;
|
||||
///
|
||||
/// #[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::<_, ()>(())
|
||||
@ -30,115 +28,128 @@ use crate::{Server, ServerBuilder, ServiceFactory};
|
||||
/// ```
|
||||
pub struct TestServer;
|
||||
|
||||
/// Test server runtime
|
||||
pub struct TestServerRuntime {
|
||||
/// Test server handle.
|
||||
pub struct TestServerHandle {
|
||||
addr: net::SocketAddr,
|
||||
host: String,
|
||||
port: u16,
|
||||
system: System,
|
||||
server_handle: ServerHandle,
|
||||
thread_handle: Option<thread::JoinHandle<io::Result<()>>>,
|
||||
}
|
||||
|
||||
impl TestServer {
|
||||
/// Start new server with server builder
|
||||
pub fn start<F>(mut factory: F) -> TestServerRuntime
|
||||
where
|
||||
F: FnMut(ServerBuilder) -> ServerBuilder + Send + 'static,
|
||||
{
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
// run server in separate thread
|
||||
thread::spawn(move || {
|
||||
let sys = System::new();
|
||||
factory(Server::build()).workers(1).disable_signals().run();
|
||||
|
||||
tx.send(System::current()).unwrap();
|
||||
sys.run()
|
||||
});
|
||||
let system = rx.recv().unwrap();
|
||||
|
||||
TestServerRuntime {
|
||||
system,
|
||||
addr: "127.0.0.1:0".parse().unwrap(),
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 0,
|
||||
}
|
||||
/// Start new `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
|
||||
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 thread_handle = thread::spawn(move || {
|
||||
let lst = net::TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let local_addr = lst.local_addr().unwrap();
|
||||
|
||||
sys.block_on(async {
|
||||
Server::build()
|
||||
.listen("test", tcp, factory)
|
||||
System::new().block_on(async {
|
||||
let server = server_builder
|
||||
.listen("test", lst, factory)
|
||||
.unwrap()
|
||||
.workers(1)
|
||||
.disable_signals()
|
||||
.run();
|
||||
tx.send((System::current(), local_addr)).unwrap();
|
||||
});
|
||||
sys.run()
|
||||
|
||||
tx.send((server.handle(), local_addr)).unwrap();
|
||||
server.await
|
||||
})
|
||||
});
|
||||
|
||||
let (system, addr) = rx.recv().unwrap();
|
||||
let (server_handle, addr) = rx.recv().unwrap();
|
||||
|
||||
let host = format!("{}", addr.ip());
|
||||
let port = addr.port();
|
||||
|
||||
TestServerRuntime {
|
||||
system,
|
||||
TestServerHandle {
|
||||
addr,
|
||||
host,
|
||||
port,
|
||||
server_handle,
|
||||
thread_handle: Some(thread_handle),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get first available unused local address
|
||||
/// 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 {
|
||||
/// Test server host
|
||||
impl TestServerHandle {
|
||||
/// Test server host.
|
||||
pub fn host(&self) -> &str {
|
||||
&self.host
|
||||
}
|
||||
|
||||
/// Test server port
|
||||
/// Test server port.
|
||||
pub fn port(&self) -> u16 {
|
||||
self.port
|
||||
}
|
||||
|
||||
/// Get test server address
|
||||
/// Get test server address.
|
||||
pub fn addr(&self) -> net::SocketAddr {
|
||||
self.addr
|
||||
}
|
||||
|
||||
/// Stop http server
|
||||
/// Stop server.
|
||||
fn stop(&mut self) {
|
||||
self.system.stop();
|
||||
drop(self.server_handle.stop(false));
|
||||
self.thread_handle.take().unwrap().join().unwrap().unwrap();
|
||||
}
|
||||
|
||||
/// Connect to server, return tokio TcpStream
|
||||
pub fn connect(&self) -> std::io::Result<TcpStream> {
|
||||
TcpStream::from_std(net::TcpStream::connect(self.addr)?)
|
||||
/// Connect to server, returning a Tokio `TcpStream`.
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
@ -6,9 +6,9 @@ use std::{
|
||||
|
||||
use mio::{Registry, Token as MioToken, Waker};
|
||||
|
||||
use crate::worker::WorkerHandle;
|
||||
use crate::worker::WorkerHandleAccept;
|
||||
|
||||
/// waker token for `mio::Poll` instance
|
||||
/// Waker token for `mio::Poll` instance.
|
||||
pub(crate) const WAKER_TOKEN: MioToken = MioToken(usize::MAX);
|
||||
|
||||
/// `mio::Waker` with a queue for waking up the `Accept`'s `Poll` and contains the `WakerInterest`
|
||||
@ -30,7 +30,7 @@ impl Deref for WakerQueue {
|
||||
}
|
||||
|
||||
impl WakerQueue {
|
||||
/// construct a waker queue with given `Poll`'s `Registry` and capacity.
|
||||
/// Construct a waker queue with given `Poll`'s `Registry` and capacity.
|
||||
///
|
||||
/// A fixed `WAKER_TOKEN` is used to identify the wake interest and the `Poll` needs to match
|
||||
/// event's token for it to properly handle `WakerInterest`.
|
||||
@ -41,7 +41,7 @@ impl WakerQueue {
|
||||
Ok(Self(Arc::new((waker, queue))))
|
||||
}
|
||||
|
||||
/// push a new interest to the queue and wake up the accept poll afterwards.
|
||||
/// Push a new interest to the queue and wake up the accept poll afterwards.
|
||||
pub(crate) fn wake(&self, interest: WakerInterest) {
|
||||
let (waker, queue) = self.deref();
|
||||
|
||||
@ -55,35 +55,30 @@ impl WakerQueue {
|
||||
.unwrap_or_else(|e| panic!("can not wake up Accept Poll: {}", e));
|
||||
}
|
||||
|
||||
/// get a MutexGuard of the waker queue.
|
||||
/// Get a MutexGuard of the waker queue.
|
||||
pub(crate) fn guard(&self) -> MutexGuard<'_, VecDeque<WakerInterest>> {
|
||||
self.deref().1.lock().expect("Failed to lock WakerQueue")
|
||||
}
|
||||
|
||||
/// reset the waker queue so it does not grow infinitely.
|
||||
/// Reset the waker queue so it does not grow infinitely.
|
||||
pub(crate) fn reset(queue: &mut VecDeque<WakerInterest>) {
|
||||
std::mem::swap(&mut VecDeque::<WakerInterest>::with_capacity(16), queue);
|
||||
}
|
||||
}
|
||||
|
||||
/// types of interests we would look into when `Accept`'s `Poll` is waked up by waker.
|
||||
/// Types of interests we would look into when `Accept`'s `Poll` is waked up by waker.
|
||||
///
|
||||
/// *. These interests should not be confused with `mio::Interest` and mostly not I/O related
|
||||
/// These interests should not be confused with `mio::Interest` and mostly not I/O related
|
||||
pub(crate) enum WakerInterest {
|
||||
/// `WorkerAvailable` is an interest from `Worker` notifying `Accept` there is a worker
|
||||
/// available and can accept new tasks.
|
||||
WorkerAvailable,
|
||||
WorkerAvailable(usize),
|
||||
/// `Pause`, `Resume`, `Stop` Interest are from `ServerBuilder` future. It listens to
|
||||
/// `ServerCommand` and notify `Accept` to do exactly these tasks.
|
||||
Pause,
|
||||
Resume,
|
||||
Stop,
|
||||
/// `Timer` is an interest sent as a delayed future. When an error happens on accepting
|
||||
/// connection `Accept` would deregister socket listener temporary and wake up the poll and
|
||||
/// register them again after the delayed future resolve.
|
||||
Timer,
|
||||
/// `Worker` is an interest happen after a worker runs into faulted state(This is determined
|
||||
/// by if work can be sent to it successfully).`Accept` would be waked up and add the new
|
||||
/// `WorkerHandle`.
|
||||
Worker(WorkerHandle),
|
||||
/// `Worker` is an interest that is triggered after a worker faults. This is determined by
|
||||
/// trying to send work to it. `Accept` would be waked up and add the new `WorkerHandleAccept`.
|
||||
Worker(WorkerHandleAccept),
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
537
actix-server/tests/server.rs
Normal file
537
actix-server/tests/server.rs
Normal file
@ -0,0 +1,537 @@
|
||||
#![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, TestServer};
|
||||
use actix_service::fn_service;
|
||||
|
||||
fn unused_addr() -> net::SocketAddr {
|
||||
TestServer::unused_addr()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bind() {
|
||||
let addr = unused_addr();
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
let h = thread::spawn(move || {
|
||||
actix_rt::System::new().block_on(async {
|
||||
let srv = Server::build()
|
||||
.workers(1)
|
||||
.disable_signals()
|
||||
.shutdown_timeout(3600)
|
||||
.bind("test", addr, move || {
|
||||
fn_service(|_| async { Ok::<_, ()>(()) })
|
||||
})?
|
||||
.run();
|
||||
|
||||
tx.send(srv.handle()).unwrap();
|
||||
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 test_listen() {
|
||||
let addr = unused_addr();
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let lst = net::TcpListener::bind(addr).unwrap();
|
||||
|
||||
let h = thread::spawn(move || {
|
||||
actix_rt::System::new().block_on(async {
|
||||
let srv = Server::build()
|
||||
.workers(1)
|
||||
.disable_signals()
|
||||
.shutdown_timeout(3600)
|
||||
.listen("test", lst, move || {
|
||||
fn_service(|_| async { Ok::<_, ()>(()) })
|
||||
})?
|
||||
.run();
|
||||
|
||||
tx.send(srv.handle()).unwrap();
|
||||
srv.await
|
||||
})
|
||||
});
|
||||
|
||||
let srv = rx.recv().unwrap();
|
||||
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
|
||||
net::TcpStream::connect(addr).unwrap();
|
||||
|
||||
let _ = srv.stop(true);
|
||||
h.join().unwrap().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_tokio_runtime() {
|
||||
let addr = unused_addr();
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
let h = thread::spawn(move || {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
rt.block_on(async {
|
||||
let srv = Server::build()
|
||||
.workers(1)
|
||||
.disable_signals()
|
||||
.bind("test", addr, move || {
|
||||
fn_service(|_| async { Ok::<_, ()>(()) })
|
||||
})?
|
||||
.run();
|
||||
|
||||
tx.send(srv.handle()).unwrap();
|
||||
|
||||
srv.await
|
||||
})
|
||||
});
|
||||
|
||||
let srv = rx.recv().unwrap();
|
||||
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
assert!(net::TcpStream::connect(addr).is_ok());
|
||||
|
||||
let _ = srv.stop(true);
|
||||
h.join().unwrap().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_start() {
|
||||
use std::io::Read;
|
||||
|
||||
use actix_codec::{BytesCodec, Framed};
|
||||
use bytes::Bytes;
|
||||
use futures_util::sink::SinkExt;
|
||||
|
||||
let addr = unused_addr();
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
let h = thread::spawn(move || {
|
||||
actix_rt::System::new().block_on(async {
|
||||
let srv = Server::build()
|
||||
.backlog(100)
|
||||
.disable_signals()
|
||||
.bind("test", addr, move || {
|
||||
fn_service(|io: TcpStream| async move {
|
||||
let mut f = Framed::new(io, BytesCodec);
|
||||
f.send(Bytes::from_static(b"test")).await.unwrap();
|
||||
Ok::<_, ()>(())
|
||||
})
|
||||
})?
|
||||
.run();
|
||||
|
||||
let _ = tx.send((srv.handle(), actix_rt::System::current()));
|
||||
|
||||
srv.await
|
||||
})
|
||||
});
|
||||
|
||||
let (srv, sys) = rx.recv().unwrap();
|
||||
|
||||
let mut buf = [1u8; 4];
|
||||
let mut conn = net::TcpStream::connect(addr).unwrap();
|
||||
let _ = conn.read_exact(&mut buf);
|
||||
assert_eq!(buf, b"test"[..]);
|
||||
|
||||
// pause
|
||||
let _ = srv.pause();
|
||||
thread::sleep(Duration::from_millis(200));
|
||||
let mut conn = net::TcpStream::connect(addr).unwrap();
|
||||
conn.set_read_timeout(Some(Duration::from_millis(100)))
|
||||
.unwrap();
|
||||
let res = conn.read_exact(&mut buf);
|
||||
assert!(res.is_err());
|
||||
|
||||
// resume
|
||||
let _ = srv.resume();
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
assert!(net::TcpStream::connect(addr).is_ok());
|
||||
assert!(net::TcpStream::connect(addr).is_ok());
|
||||
assert!(net::TcpStream::connect(addr).is_ok());
|
||||
|
||||
let mut buf = [0u8; 4];
|
||||
let mut conn = net::TcpStream::connect(addr).unwrap();
|
||||
let _ = conn.read_exact(&mut buf);
|
||||
assert_eq!(buf, b"test"[..]);
|
||||
|
||||
// stop
|
||||
let _ = srv.stop(false);
|
||||
sys.stop();
|
||||
h.join().unwrap().unwrap();
|
||||
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
assert!(net::TcpStream::connect(addr).is_err());
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_max_concurrent_connections() {
|
||||
// Note:
|
||||
// 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
|
||||
// thread accept.
|
||||
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
let addr = unused_addr();
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
let counter = Arc::new(AtomicUsize::new(0));
|
||||
let counter_clone = counter.clone();
|
||||
|
||||
let max_conn = 3;
|
||||
|
||||
let h = thread::spawn(move || {
|
||||
actix_rt::System::new().block_on(async {
|
||||
let srv = Server::build()
|
||||
// Set a relative higher backlog.
|
||||
.backlog(12)
|
||||
// max connection for a worker is 3.
|
||||
.max_concurrent_connections(max_conn)
|
||||
.workers(1)
|
||||
.disable_signals()
|
||||
.bind("test", addr, move || {
|
||||
let counter = counter.clone();
|
||||
fn_service(move |_io: TcpStream| {
|
||||
let counter = counter.clone();
|
||||
async move {
|
||||
counter.fetch_add(1, Ordering::SeqCst);
|
||||
sleep(Duration::from_secs(20)).await;
|
||||
counter.fetch_sub(1, Ordering::SeqCst);
|
||||
Ok::<(), ()>(())
|
||||
}
|
||||
})
|
||||
})?
|
||||
.run();
|
||||
|
||||
let _ = tx.send((srv.handle(), actix_rt::System::current()));
|
||||
|
||||
srv.await
|
||||
})
|
||||
});
|
||||
|
||||
let (srv, sys) = rx.recv().unwrap();
|
||||
|
||||
let mut conns = vec![];
|
||||
|
||||
for _ in 0..12 {
|
||||
let conn = tokio::net::TcpStream::connect(addr).await.unwrap();
|
||||
conns.push(conn);
|
||||
}
|
||||
|
||||
sleep(Duration::from_secs(5)).await;
|
||||
|
||||
// counter would remain at 3 even with 12 successful connection.
|
||||
// and 9 of them remain in backlog.
|
||||
assert_eq!(max_conn, counter_clone.load(Ordering::SeqCst));
|
||||
|
||||
for mut conn in conns {
|
||||
conn.shutdown().await.unwrap();
|
||||
}
|
||||
|
||||
srv.stop(false).await;
|
||||
sys.stop();
|
||||
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};
|
||||
|
||||
use actix_service::{fn_factory, Service};
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
struct TestService(Arc<AtomicUsize>);
|
||||
|
||||
impl Service<TcpStream> for TestService {
|
||||
type Response = ();
|
||||
type Error = ();
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||
|
||||
fn poll_ready(&self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
let TestService(ref counter) = self;
|
||||
let c = counter.fetch_add(1, Ordering::SeqCst);
|
||||
// Force the service to restart on first readiness check.
|
||||
if c > 0 {
|
||||
Poll::Ready(Ok(()))
|
||||
} else {
|
||||
Poll::Ready(Err(()))
|
||||
}
|
||||
}
|
||||
|
||||
fn call(&self, _: TcpStream) -> Self::Future {
|
||||
Box::pin(async { Ok(()) })
|
||||
}
|
||||
}
|
||||
|
||||
let addr1 = unused_addr();
|
||||
let addr2 = unused_addr();
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let num = Arc::new(AtomicUsize::new(0));
|
||||
let num2 = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let num_clone = num.clone();
|
||||
let num2_clone = num2.clone();
|
||||
|
||||
let h = thread::spawn(move || {
|
||||
let num = num.clone();
|
||||
actix_rt::System::new().block_on(async {
|
||||
let srv = Server::build()
|
||||
.backlog(1)
|
||||
.disable_signals()
|
||||
.bind("addr1", addr1, move || {
|
||||
let num = num.clone();
|
||||
fn_factory(move || {
|
||||
let num = num.clone();
|
||||
async move { Ok::<_, ()>(TestService(num)) }
|
||||
})
|
||||
})?
|
||||
.bind("addr2", addr2, move || {
|
||||
let num2 = num2.clone();
|
||||
fn_factory(move || {
|
||||
let num2 = num2.clone();
|
||||
async move { Ok::<_, ()>(TestService(num2)) }
|
||||
})
|
||||
})?
|
||||
.workers(1)
|
||||
.run();
|
||||
|
||||
let _ = tx.send(srv.handle());
|
||||
srv.await
|
||||
})
|
||||
});
|
||||
|
||||
let srv = rx.recv().unwrap();
|
||||
|
||||
for _ in 0..5 {
|
||||
TcpStream::connect(addr1)
|
||||
.await
|
||||
.unwrap()
|
||||
.shutdown()
|
||||
.await
|
||||
.unwrap();
|
||||
TcpStream::connect(addr2)
|
||||
.await
|
||||
.unwrap()
|
||||
.shutdown()
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
sleep(Duration::from_secs(3)).await;
|
||||
|
||||
assert!(num_clone.load(Ordering::SeqCst) > 5);
|
||||
assert!(num2_clone.load(Ordering::SeqCst) > 5);
|
||||
|
||||
let _ = srv.stop(false);
|
||||
h.join().unwrap().unwrap();
|
||||
}
|
||||
|
||||
#[ignore] // non-deterministic on CI
|
||||
#[actix_rt::test]
|
||||
async fn worker_restart() {
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
|
||||
struct TestServiceFactory(Arc<AtomicUsize>);
|
||||
|
||||
impl ServiceFactory<TcpStream> for TestServiceFactory {
|
||||
type Response = ();
|
||||
type Error = ();
|
||||
type Config = ();
|
||||
type Service = TestService;
|
||||
type InitError = ();
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: Self::Config) -> Self::Future {
|
||||
let counter = self.0.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
Box::pin(async move { Ok(TestService(counter)) })
|
||||
}
|
||||
}
|
||||
|
||||
struct TestService(usize);
|
||||
|
||||
impl Service<TcpStream> for TestService {
|
||||
type Response = ();
|
||||
type Error = ();
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||
|
||||
actix_service::always_ready!();
|
||||
|
||||
fn call(&self, stream: TcpStream) -> Self::Future {
|
||||
let counter = self.0;
|
||||
|
||||
let mut stream = stream.into_std().unwrap();
|
||||
use std::io::Write;
|
||||
let str = counter.to_string();
|
||||
let buf = str.as_bytes();
|
||||
|
||||
let mut written = 0;
|
||||
|
||||
while written < buf.len() {
|
||||
if let Ok(n) = stream.write(&buf[written..]) {
|
||||
written += n;
|
||||
}
|
||||
}
|
||||
stream.flush().unwrap();
|
||||
stream.shutdown(net::Shutdown::Write).unwrap();
|
||||
|
||||
// force worker 2 to restart service once.
|
||||
if counter == 2 {
|
||||
panic!("panic on purpose")
|
||||
} else {
|
||||
Box::pin(async { Ok(()) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let addr = unused_addr();
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
let counter = Arc::new(AtomicUsize::new(1));
|
||||
let h = thread::spawn(move || {
|
||||
let counter = counter.clone();
|
||||
actix_rt::System::new().block_on(async {
|
||||
let srv = Server::build()
|
||||
.disable_signals()
|
||||
.bind("addr", addr, move || TestServiceFactory(counter.clone()))?
|
||||
.workers(2)
|
||||
.run();
|
||||
|
||||
let _ = tx.send(srv.handle());
|
||||
|
||||
srv.await
|
||||
})
|
||||
});
|
||||
|
||||
let srv = rx.recv().unwrap();
|
||||
|
||||
sleep(Duration::from_secs(3)).await;
|
||||
|
||||
let mut buf = [0; 8];
|
||||
|
||||
// worker 1 would not restart and return it's id consistently.
|
||||
let mut stream = TcpStream::connect(addr).await.unwrap();
|
||||
let n = stream.read(&mut buf).await.unwrap();
|
||||
let id = String::from_utf8_lossy(&buf[0..n]);
|
||||
assert_eq!("1", id);
|
||||
stream.shutdown().await.unwrap();
|
||||
|
||||
// worker 2 dead after return response.
|
||||
let mut stream = TcpStream::connect(addr).await.unwrap();
|
||||
let n = stream.read(&mut buf).await.unwrap();
|
||||
let id = String::from_utf8_lossy(&buf[0..n]);
|
||||
assert_eq!("2", id);
|
||||
stream.shutdown().await.unwrap();
|
||||
|
||||
// request to worker 1
|
||||
let mut stream = TcpStream::connect(addr).await.unwrap();
|
||||
let n = stream.read(&mut buf).await.unwrap();
|
||||
let id = String::from_utf8_lossy(&buf[0..n]);
|
||||
assert_eq!("1", id);
|
||||
stream.shutdown().await.unwrap();
|
||||
|
||||
// TODO: Remove sleep if it can pass CI.
|
||||
sleep(Duration::from_secs(3)).await;
|
||||
|
||||
// worker 2 restarting and work goes to worker 1.
|
||||
let mut stream = TcpStream::connect(addr).await.unwrap();
|
||||
let n = stream.read(&mut buf).await.unwrap();
|
||||
let id = String::from_utf8_lossy(&buf[0..n]);
|
||||
assert_eq!("1", id);
|
||||
stream.shutdown().await.unwrap();
|
||||
|
||||
// TODO: Remove sleep if it can pass CI.
|
||||
sleep(Duration::from_secs(3)).await;
|
||||
|
||||
// worker 2 restarted but worker 1 was still the next to accept connection.
|
||||
let mut stream = TcpStream::connect(addr).await.unwrap();
|
||||
let n = stream.read(&mut buf).await.unwrap();
|
||||
let id = String::from_utf8_lossy(&buf[0..n]);
|
||||
assert_eq!("1", id);
|
||||
stream.shutdown().await.unwrap();
|
||||
|
||||
// TODO: Remove sleep if it can pass CI.
|
||||
sleep(Duration::from_secs(3)).await;
|
||||
|
||||
// worker 2 accept connection again but it's id is 3.
|
||||
let mut stream = TcpStream::connect(addr).await.unwrap();
|
||||
let n = stream.read(&mut buf).await.unwrap();
|
||||
let id = String::from_utf8_lossy(&buf[0..n]);
|
||||
assert_eq!("3", id);
|
||||
stream.shutdown().await.unwrap();
|
||||
|
||||
let _ = srv.stop(false);
|
||||
h.join().unwrap().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_runtime_on_init() {
|
||||
use std::{thread::sleep, time::Duration};
|
||||
|
||||
let addr = unused_addr();
|
||||
let counter = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let mut srv = Server::build()
|
||||
.workers(2)
|
||||
.disable_signals()
|
||||
.bind("test", addr, {
|
||||
let counter = counter.clone();
|
||||
move || {
|
||||
counter.fetch_add(1, Ordering::SeqCst);
|
||||
fn_service(|_| async { Ok::<_, ()>(()) })
|
||||
}
|
||||
})
|
||||
.unwrap()
|
||||
.run();
|
||||
|
||||
fn is_send<T: Send>(_: &T) {}
|
||||
is_send(&srv);
|
||||
is_send(&srv.handle());
|
||||
|
||||
sleep(Duration::from_millis(1_000));
|
||||
assert_eq!(counter.load(Ordering::SeqCst), 0);
|
||||
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
rt.block_on(async move {
|
||||
let _ = futures_util::poll!(&mut srv);
|
||||
|
||||
// available after the first poll
|
||||
sleep(Duration::from_millis(500));
|
||||
assert_eq!(counter.load(Ordering::SeqCst), 2);
|
||||
|
||||
let _ = srv.handle().stop(true);
|
||||
srv.await
|
||||
})
|
||||
.unwrap();
|
||||
}
|
@ -1,190 +0,0 @@
|
||||
use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
|
||||
use std::sync::{mpsc, Arc};
|
||||
use std::{net, thread, time};
|
||||
|
||||
use actix_server::Server;
|
||||
use actix_service::fn_service;
|
||||
use futures_util::future::{lazy, ok};
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bind() {
|
||||
let addr = unused_addr();
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
let h = thread::spawn(move || {
|
||||
let sys = actix_rt::System::new();
|
||||
let srv = sys.block_on(lazy(|_| {
|
||||
Server::build()
|
||||
.workers(1)
|
||||
.disable_signals()
|
||||
.bind("test", addr, move || fn_service(|_| ok::<_, ()>(())))
|
||||
.unwrap()
|
||||
.run()
|
||||
}));
|
||||
let _ = tx.send((srv, actix_rt::System::current()));
|
||||
let _ = sys.run();
|
||||
});
|
||||
let (_, sys) = rx.recv().unwrap();
|
||||
|
||||
thread::sleep(time::Duration::from_millis(500));
|
||||
assert!(net::TcpStream::connect(addr).is_ok());
|
||||
sys.stop();
|
||||
let _ = h.join();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_listen() {
|
||||
let addr = unused_addr();
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
let h = thread::spawn(move || {
|
||||
let sys = actix_rt::System::new();
|
||||
let lst = net::TcpListener::bind(addr).unwrap();
|
||||
sys.block_on(async {
|
||||
Server::build()
|
||||
.disable_signals()
|
||||
.workers(1)
|
||||
.listen("test", lst, move || fn_service(|_| ok::<_, ()>(())))
|
||||
.unwrap()
|
||||
.run();
|
||||
let _ = tx.send(actix_rt::System::current());
|
||||
});
|
||||
let _ = sys.run();
|
||||
});
|
||||
let sys = rx.recv().unwrap();
|
||||
|
||||
thread::sleep(time::Duration::from_millis(500));
|
||||
assert!(net::TcpStream::connect(addr).is_ok());
|
||||
sys.stop();
|
||||
let _ = h.join();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_start() {
|
||||
use actix_codec::{BytesCodec, Framed};
|
||||
use actix_rt::net::TcpStream;
|
||||
use bytes::Bytes;
|
||||
use futures_util::sink::SinkExt;
|
||||
use std::io::Read;
|
||||
|
||||
let addr = unused_addr();
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
let h = thread::spawn(move || {
|
||||
let sys = actix_rt::System::new();
|
||||
let srv = sys.block_on(lazy(|_| {
|
||||
Server::build()
|
||||
.backlog(100)
|
||||
.disable_signals()
|
||||
.bind("test", addr, move || {
|
||||
fn_service(|io: TcpStream| async move {
|
||||
let mut f = Framed::new(io, BytesCodec);
|
||||
f.send(Bytes::from_static(b"test")).await.unwrap();
|
||||
Ok::<_, ()>(())
|
||||
})
|
||||
})
|
||||
.unwrap()
|
||||
.run()
|
||||
}));
|
||||
|
||||
let _ = tx.send((srv, actix_rt::System::current()));
|
||||
let _ = sys.run();
|
||||
});
|
||||
|
||||
let (srv, sys) = rx.recv().unwrap();
|
||||
|
||||
let mut buf = [1u8; 4];
|
||||
let mut conn = net::TcpStream::connect(addr).unwrap();
|
||||
let _ = conn.read_exact(&mut buf);
|
||||
assert_eq!(buf, b"test"[..]);
|
||||
|
||||
// pause
|
||||
let _ = srv.pause();
|
||||
thread::sleep(time::Duration::from_millis(200));
|
||||
let mut conn = net::TcpStream::connect(addr).unwrap();
|
||||
conn.set_read_timeout(Some(time::Duration::from_millis(100)))
|
||||
.unwrap();
|
||||
let res = conn.read_exact(&mut buf);
|
||||
assert!(res.is_err());
|
||||
|
||||
// resume
|
||||
let _ = srv.resume();
|
||||
thread::sleep(time::Duration::from_millis(100));
|
||||
assert!(net::TcpStream::connect(addr).is_ok());
|
||||
assert!(net::TcpStream::connect(addr).is_ok());
|
||||
assert!(net::TcpStream::connect(addr).is_ok());
|
||||
|
||||
let mut buf = [0u8; 4];
|
||||
let mut conn = net::TcpStream::connect(addr).unwrap();
|
||||
let _ = conn.read_exact(&mut buf);
|
||||
assert_eq!(buf, b"test"[..]);
|
||||
|
||||
// stop
|
||||
let _ = srv.stop(false);
|
||||
thread::sleep(time::Duration::from_millis(100));
|
||||
assert!(net::TcpStream::connect(addr).is_err());
|
||||
|
||||
thread::sleep(time::Duration::from_millis(100));
|
||||
sys.stop();
|
||||
let _ = h.join();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_configure() {
|
||||
let addr1 = unused_addr();
|
||||
let addr2 = unused_addr();
|
||||
let addr3 = unused_addr();
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let num = Arc::new(AtomicUsize::new(0));
|
||||
let num2 = num.clone();
|
||||
|
||||
let h = thread::spawn(move || {
|
||||
let num = num2.clone();
|
||||
let sys = actix_rt::System::new();
|
||||
let srv = sys.block_on(lazy(|_| {
|
||||
Server::build()
|
||||
.disable_signals()
|
||||
.configure(move |cfg| {
|
||||
let num = num.clone();
|
||||
let lst = net::TcpListener::bind(addr3).unwrap();
|
||||
cfg.bind("addr1", addr1)
|
||||
.unwrap()
|
||||
.bind("addr2", addr2)
|
||||
.unwrap()
|
||||
.listen("addr3", lst)
|
||||
.apply(move |rt| {
|
||||
let num = num.clone();
|
||||
rt.service("addr1", fn_service(|_| ok::<_, ()>(())));
|
||||
rt.service("addr3", fn_service(|_| ok::<_, ()>(())));
|
||||
rt.on_start(lazy(move |_| {
|
||||
let _ = num.fetch_add(1, Relaxed);
|
||||
}))
|
||||
})
|
||||
})
|
||||
.unwrap()
|
||||
.workers(1)
|
||||
.run()
|
||||
}));
|
||||
let _ = tx.send((srv, actix_rt::System::current()));
|
||||
let _ = sys.run();
|
||||
});
|
||||
let (_, sys) = rx.recv().unwrap();
|
||||
thread::sleep(time::Duration::from_millis(500));
|
||||
|
||||
assert!(net::TcpStream::connect(addr1).is_ok());
|
||||
assert!(net::TcpStream::connect(addr2).is_ok());
|
||||
assert!(net::TcpStream::connect(addr3).is_ok());
|
||||
assert_eq!(num.load(Relaxed), 1);
|
||||
sys.stop();
|
||||
let _ = h.join();
|
||||
}
|
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,266 +1,193 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
## Unreleased
|
||||
|
||||
## 2.0.3
|
||||
|
||||
## 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]
|
||||
- Minimum supported Rust version (MSRV) is now 1.71.
|
||||
|
||||
## 2.0.2
|
||||
|
||||
- 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
|
||||
|
||||
- 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
|
||||
|
||||
- `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;
|
||||
@ -269,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,29 +1,23 @@
|
||||
[package]
|
||||
name = "actix-service"
|
||||
version = "2.0.0-beta.4"
|
||||
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"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-net.git"
|
||||
documentation = "https://docs.rs/actix-service"
|
||||
readme = "README.md"
|
||||
categories = ["network-programming", "asynchronous"]
|
||||
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 }
|
||||
futures-core = { version = "0.3.17", default-features = false }
|
||||
pin-project-lite = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "2.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,11 +3,11 @@
|
||||
> Service trait and combinators for representing asynchronous request/response operations.
|
||||
|
||||
[](https://crates.io/crates/actix-service)
|
||||
[](https://docs.rs/actix-service/2.0.0-beta.4)
|
||||
[](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.0-beta.4)
|
||||
[](https://crates.io/crates/actix-service)
|
||||

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

|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
See documentation for detailed explanations of these components: https://docs.rs/actix-service.
|
||||
|
35
actix-service/examples/clone.rs
Normal file
35
actix-service/examples/clone.rs
Normal file
@ -0,0 +1,35 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use std::{future::Future, sync::mpsc, time::Duration};
|
||||
|
||||
async fn oracle<F, Fut>(f: F) -> (u32, u32)
|
||||
where
|
||||
F: FnOnce() -> Fut + Clone + Send + 'static,
|
||||
Fut: Future<Output = u32> + 'static,
|
||||
{
|
||||
let f1 = actix_rt::spawn(f.clone()());
|
||||
let f2 = actix_rt::spawn(f());
|
||||
|
||||
(f1.await.unwrap(), f2.await.unwrap())
|
||||
}
|
||||
|
||||
#[actix_rt::main]
|
||||
async fn main() {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
let (r1, r2) = oracle({
|
||||
let tx = tx.clone();
|
||||
|
||||
|| async move {
|
||||
tx.send(()).unwrap();
|
||||
4 * 4
|
||||
}
|
||||
})
|
||||
.await;
|
||||
assert_eq!(r1, r2);
|
||||
|
||||
tx.send(()).unwrap();
|
||||
|
||||
rx.recv_timeout(Duration::from_millis(100)).unwrap();
|
||||
rx.recv_timeout(Duration::from_millis(100)).unwrap();
|
||||
}
|
@ -11,11 +11,11 @@ use pin_project_lite::pin_project;
|
||||
|
||||
use super::{Service, ServiceFactory};
|
||||
|
||||
/// Service for the `and_then` combinator, chaining a computation onto the end
|
||||
/// of another service which completes successfully.
|
||||
/// Service for the `and_then` combinator, chaining a computation onto the end of another service
|
||||
/// which completes successfully.
|
||||
///
|
||||
/// This is created by the `Pipeline::and_then` method.
|
||||
pub(crate) struct AndThenService<A, B, Req>(Rc<(A, B)>, PhantomData<Req>);
|
||||
pub struct AndThenService<A, B, Req>(Rc<(A, B)>, PhantomData<Req>);
|
||||
|
||||
impl<A, B, Req> AndThenService<A, B, Req> {
|
||||
/// Create new `AndThen` combinator
|
||||
@ -64,7 +64,7 @@ where
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
pub(crate) struct AndThenServiceResponse<A, B, Req>
|
||||
pub struct AndThenServiceResponse<A, B, Req>
|
||||
where
|
||||
A: Service<Req>,
|
||||
B: Service<A::Response, Error = A::Error>,
|
||||
@ -117,16 +117,11 @@ where
|
||||
}
|
||||
|
||||
/// `.and_then()` service factory combinator
|
||||
pub(crate) struct AndThenServiceFactory<A, B, Req>
|
||||
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 {
|
||||
@ -200,7 +180,7 @@ where
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
pub(crate) struct AndThenServiceFactoryResponse<A, B, Req>
|
||||
pub struct AndThenServiceFactoryResponse<A, B, Req>
|
||||
where
|
||||
A: ServiceFactory<Req>,
|
||||
B: ServiceFactory<A::Response>,
|
||||
@ -272,7 +252,9 @@ mod tests {
|
||||
use futures_util::future::lazy;
|
||||
|
||||
use crate::{
|
||||
fn_factory, ok, pipeline, pipeline_factory, ready, Ready, Service, ServiceFactory,
|
||||
fn_factory, ok,
|
||||
pipeline::{pipeline, pipeline_factory},
|
||||
ready, Ready, Service, ServiceFactory,
|
||||
};
|
||||
|
||||
struct Srv1(Rc<Cell<usize>>);
|
||||
@ -332,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,12 +208,14 @@ where
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use core::task::Poll;
|
||||
|
||||
use futures_util::future::lazy;
|
||||
|
||||
use super::*;
|
||||
use crate::{ok, pipeline, pipeline_factory, Ready, Service, ServiceFactory};
|
||||
use crate::{
|
||||
ok,
|
||||
pipeline::{pipeline, pipeline_factory},
|
||||
Ready,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Srv;
|
||||
|
@ -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>,
|
||||
|
@ -1,21 +1,71 @@
|
||||
use alloc::boxed::Box;
|
||||
use core::{
|
||||
future::Future,
|
||||
marker::PhantomData,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
//! Trait object forms of services and service factories.
|
||||
|
||||
use alloc::{boxed::Box, rc::Rc};
|
||||
use core::{future::Future, pin::Pin};
|
||||
|
||||
use crate::{Service, ServiceFactory};
|
||||
|
||||
/// A boxed future with no send bound or lifetime parameters.
|
||||
pub type BoxFuture<T> = Pin<Box<dyn Future<Output = T>>>;
|
||||
|
||||
/// 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>>>>;
|
||||
|
||||
/// 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))
|
||||
}
|
||||
|
||||
/// 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,
|
||||
}
|
||||
|
||||
impl<S> ServiceWrapper<S> {
|
||||
fn new(inner: S) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, Req, Res, Err> Service<Req> for ServiceWrapper<S>
|
||||
where
|
||||
S: Service<Req, Response = Res, Error = Err>,
|
||||
S::Future: 'static,
|
||||
{
|
||||
type Response = Res;
|
||||
type Error = Err;
|
||||
type Future = BoxFuture<Result<Res, Err>>;
|
||||
|
||||
crate::forward_ready!(inner);
|
||||
|
||||
fn call(&self, req: Req) -> Self::Future {
|
||||
Box::pin(self.inner.call(req))
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper for a service factory that will map it's services to boxed trait object services.
|
||||
pub struct BoxServiceFactory<Cfg, Req, Res, Err, InitErr>(Inner<Cfg, Req, Res, Err, InitErr>);
|
||||
|
||||
/// Create boxed service factory
|
||||
/// Wraps a service factory that returns service trait objects.
|
||||
pub fn factory<SF, Req>(
|
||||
factory: SF,
|
||||
) -> BoxServiceFactory<SF::Config, Req, SF::Response, SF::Error, SF::InitError>
|
||||
@ -28,20 +78,7 @@ where
|
||||
SF::Error: 'static,
|
||||
SF::InitError: 'static,
|
||||
{
|
||||
BoxServiceFactory(Box::new(FactoryWrapper {
|
||||
factory,
|
||||
_t: PhantomData,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Create boxed service
|
||||
pub fn service<S, Req>(service: S) -> BoxService<Req, S::Response, S::Error>
|
||||
where
|
||||
S: Service<Req> + 'static,
|
||||
Req: 'static,
|
||||
S::Future: 'static,
|
||||
{
|
||||
Box::new(ServiceWrapper(service, PhantomData))
|
||||
BoxServiceFactory(Box::new(FactoryWrapper(factory)))
|
||||
}
|
||||
|
||||
type Inner<C, Req, Res, Err, InitErr> = Box<
|
||||
@ -56,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,
|
||||
@ -66,9 +102,9 @@ where
|
||||
{
|
||||
type Response = Res;
|
||||
type Error = Err;
|
||||
type InitError = InitErr;
|
||||
type Config = C;
|
||||
type Service = BoxService<Req, Res, Err>;
|
||||
type InitError = InitErr;
|
||||
|
||||
type Future = BoxFuture<Result<Self::Service, InitErr>>;
|
||||
|
||||
@ -77,12 +113,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
struct FactoryWrapper<SF, Req, Cfg> {
|
||||
factory: SF,
|
||||
_t: PhantomData<(Req, Cfg)>,
|
||||
}
|
||||
struct FactoryWrapper<SF>(SF);
|
||||
|
||||
impl<SF, Req, Cfg, Res, Err, InitErr> ServiceFactory<Req> for FactoryWrapper<SF, Req, Cfg>
|
||||
impl<SF, Req, Cfg, Res, Err, InitErr> ServiceFactory<Req> for FactoryWrapper<SF>
|
||||
where
|
||||
Req: 'static,
|
||||
Res: 'static,
|
||||
@ -95,47 +128,13 @@ where
|
||||
{
|
||||
type Response = Res;
|
||||
type Error = Err;
|
||||
type InitError = InitErr;
|
||||
type Config = Cfg;
|
||||
type Service = BoxService<Req, Res, Err>;
|
||||
type InitError = InitErr;
|
||||
type Future = BoxFuture<Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, cfg: Cfg) -> Self::Future {
|
||||
let fut = self.factory.new_service(cfg);
|
||||
Box::pin(async {
|
||||
let res = fut.await;
|
||||
res.map(ServiceWrapper::boxed)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct ServiceWrapper<S: Service<Req>, Req>(S, PhantomData<Req>);
|
||||
|
||||
impl<S, Req> ServiceWrapper<S, Req>
|
||||
where
|
||||
S: Service<Req> + 'static,
|
||||
Req: 'static,
|
||||
S::Future: 'static,
|
||||
{
|
||||
fn boxed(service: S) -> BoxService<Req, S::Response, S::Error> {
|
||||
Box::new(ServiceWrapper(service, PhantomData))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, Req, Res, Err> Service<Req> for ServiceWrapper<S, Req>
|
||||
where
|
||||
S: Service<Req, Response = Res, Error = Err>,
|
||||
S::Future: 'static,
|
||||
{
|
||||
type Response = Res;
|
||||
type Error = Err;
|
||||
type Future = BoxFuture<Result<Res, Err>>;
|
||||
|
||||
fn poll_ready(&self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.0.poll_ready(ctx)
|
||||
}
|
||||
|
||||
fn call(&self, req: Req) -> Self::Future {
|
||||
Box::pin(self.0.call(req))
|
||||
let f = self.0.new_service(cfg);
|
||||
Box::pin(async { f.await.map(|s| Box::new(ServiceWrapper::new(s)) as _) })
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,12 @@
|
||||
use crate::{dev, Service, ServiceFactory};
|
||||
use crate::{
|
||||
and_then::{AndThenService, AndThenServiceFactory},
|
||||
map::Map,
|
||||
map_err::MapErr,
|
||||
transform_err::TransformMapInitErr,
|
||||
IntoService, IntoServiceFactory, Service, ServiceFactory, Transform,
|
||||
};
|
||||
|
||||
/// An extension trait for [`Service`]s that provides a variety of convenient adapters.
|
||||
pub trait ServiceExt<Req>: Service<Req> {
|
||||
/// Map this service's output to a different type, returning a new service
|
||||
/// of the resulting type.
|
||||
@ -10,12 +17,12 @@ pub trait ServiceExt<Req>: Service<Req> {
|
||||
/// Note that this function consumes the receiving service and returns a
|
||||
/// wrapped version of it, similar to the existing `map` methods in the
|
||||
/// standard library.
|
||||
fn map<F, R>(self, f: F) -> dev::Map<Self, F, Req, R>
|
||||
fn map<F, R>(self, f: F) -> Map<Self, F, Req, R>
|
||||
where
|
||||
Self: Sized,
|
||||
F: FnMut(Self::Response) -> R,
|
||||
{
|
||||
dev::Map::new(self, f)
|
||||
Map::new(self, f)
|
||||
}
|
||||
|
||||
/// Map this service's error to a different error, returning a new service.
|
||||
@ -26,17 +33,34 @@ pub trait ServiceExt<Req>: Service<Req> {
|
||||
///
|
||||
/// Note that this function consumes the receiving service and returns a
|
||||
/// wrapped version of it.
|
||||
fn map_err<F, E>(self, f: F) -> dev::MapErr<Self, Req, F, E>
|
||||
fn map_err<F, E>(self, f: F) -> MapErr<Self, Req, F, E>
|
||||
where
|
||||
Self: Sized,
|
||||
F: Fn(Self::Error) -> E,
|
||||
{
|
||||
dev::MapErr::new(self, f)
|
||||
MapErr::new(self, f)
|
||||
}
|
||||
|
||||
/// 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 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.
|
||||
fn and_then<I, S1>(self, service: I) -> AndThenService<Self, S1, Req>
|
||||
where
|
||||
Self: Sized,
|
||||
I: IntoService<S1, Self::Response>,
|
||||
S1: Service<Self::Response, Error = Self::Error>,
|
||||
{
|
||||
AndThenService::new(self, service.into_service())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, Req> ServiceExt<Req> for S where S: Service<Req> {}
|
||||
|
||||
/// An extension trait for [`ServiceFactory`]s that provides a variety of convenient adapters.
|
||||
pub trait ServiceFactoryExt<Req>: ServiceFactory<Req> {
|
||||
/// Map this service's output to a different type, returning a new service
|
||||
/// of the resulting type.
|
||||
@ -65,6 +89,36 @@ pub trait ServiceFactoryExt<Req>: ServiceFactory<Req> {
|
||||
{
|
||||
crate::map_init_err::MapInitErr::new(self, f)
|
||||
}
|
||||
|
||||
/// Call another service after call to this one has resolved successfully.
|
||||
fn and_then<I, SF1>(self, factory: I) -> AndThenServiceFactory<Self, SF1, Req>
|
||||
where
|
||||
Self: Sized,
|
||||
Self::Config: Clone,
|
||||
I: IntoServiceFactory<SF1, Self::Response>,
|
||||
SF1: ServiceFactory<
|
||||
Self::Response,
|
||||
Config = Self::Config,
|
||||
Error = Self::Error,
|
||||
InitError = Self::InitError,
|
||||
>,
|
||||
{
|
||||
AndThenServiceFactory::new(self, factory.into_factory())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, Req> ServiceFactoryExt<Req> for S where S: ServiceFactory<Req> {}
|
||||
impl<SF, Req> ServiceFactoryExt<Req> for SF where SF: ServiceFactory<Req> {}
|
||||
|
||||
/// An extension trait for [`Transform`]s that provides a variety of convenient adapters.
|
||||
pub trait TransformExt<S, Req>: Transform<S, Req> {
|
||||
/// Return a new `Transform` whose init error is mapped to to a different type.
|
||||
fn map_init_err<F, E>(self, f: F) -> TransformMapInitErr<Self, S, Req, F, E>
|
||||
where
|
||||
Self: Sized,
|
||||
F: Fn(Self::InitError) -> E + Clone,
|
||||
{
|
||||
TransformMapInitErr::new(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Req> TransformExt<T, Req> for T where T: Transform<T, Req> {}
|
||||
|
@ -1,11 +1,9 @@
|
||||
use core::{future::Future, marker::PhantomData, task::Poll};
|
||||
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>>,
|
||||
@ -15,8 +13,7 @@ where
|
||||
|
||||
/// Create `ServiceFactory` for function that can produce services
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use std::io;
|
||||
/// use actix_service::{fn_factory, fn_service, Service, ServiceFactory};
|
||||
@ -49,24 +46,21 @@ 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
|
||||
Srv: Service<Req>,
|
||||
F: Fn() -> Fut,
|
||||
Fut: Future<Output = Result<Srv, Err>>,
|
||||
Srv: Service<Req>,
|
||||
{
|
||||
FnServiceNoConfig::new(f)
|
||||
}
|
||||
|
||||
/// Create `ServiceFactory` for function that accepts config argument and can produce services
|
||||
///
|
||||
/// Any function that has following form `Fn(Config) -> Future<Output = Service>` could
|
||||
/// act as a `ServiceFactory`.
|
||||
///
|
||||
/// # Example
|
||||
/// Any function that has following form `Fn(Config) -> Future<Output = Service>` could act as
|
||||
/// a `ServiceFactory`.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use std::io;
|
||||
/// use actix_service::{fn_factory_with_config, fn_service, Service, ServiceFactory};
|
||||
@ -107,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>
|
||||
@ -162,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>
|
||||
@ -239,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>
|
||||
@ -267,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>>,
|
||||
@ -295,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>
|
||||
@ -358,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() {
|
||||
@ -393,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,7 +1,6 @@
|
||||
//! See [`Service`] docs for information on this crate's foundational trait.
|
||||
|
||||
#![no_std]
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![allow(clippy::type_complexity)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
@ -21,6 +20,7 @@ mod apply_cfg;
|
||||
pub mod boxed;
|
||||
mod ext;
|
||||
mod fn_service;
|
||||
mod macros;
|
||||
mod map;
|
||||
mod map_config;
|
||||
mod map_err;
|
||||
@ -31,16 +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};
|
||||
pub use self::fn_service::{fn_factory, fn_factory_with_config, fn_service};
|
||||
pub use self::map_config::{map_config, unit_config};
|
||||
pub use self::pipeline::{pipeline, pipeline_factory, Pipeline, PipelineFactory};
|
||||
pub use self::transform::{apply, Transform};
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use self::ready::{err, ok, ready, Ready};
|
||||
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`.
|
||||
///
|
||||
@ -52,8 +52,14 @@ use self::ready::{err, ok, ready, Ready};
|
||||
/// async fn(Request) -> Result<Response, Err>
|
||||
/// ```
|
||||
///
|
||||
/// The `Service` trait just generalizes this form where each parameter is described as an
|
||||
/// associated type on the trait. Services can also have mutable state that influence computation.
|
||||
/// The `Service` trait just generalizes this form. Requests are defined as a generic type parameter
|
||||
/// and responses and other details are defined as associated types on the trait impl. Notice that
|
||||
/// this design means that services can receive many request types and converge them to a single
|
||||
/// response type.
|
||||
///
|
||||
/// Services can also have mutable state that influence computation by using a `Cell`, `RefCell`
|
||||
/// or `Mutex`. Services intentionally do not take `&mut self` to reduce overhead in the
|
||||
/// common cases.
|
||||
///
|
||||
/// `Service` provides a symmetric and uniform API; the same abstractions can be used to represent
|
||||
/// both clients and servers. Services describe only _transformation_ operations which encourage
|
||||
@ -63,23 +69,25 @@ use self::ready::{err, ok, ready, Ready};
|
||||
/// ```ignore
|
||||
/// struct MyService;
|
||||
///
|
||||
/// impl Service for MyService {
|
||||
/// type Request = u8;
|
||||
/// impl Service<u8> for MyService {
|
||||
/// type Response = u64;
|
||||
/// type Error = MyError;
|
||||
/// type Future = Pin<Box<Future<Output=Result<Self::Response, Self::Error>>>>;
|
||||
/// type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
|
||||
///
|
||||
/// fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { ... }
|
||||
///
|
||||
/// fn call(&self, req: Self::Request) -> Self::Future { ... }
|
||||
/// fn call(&self, req: u8) -> Self::Future { ... }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Sometimes it is not necessary to implement the Service trait. For example, the above service
|
||||
/// could be rewritten as a simple function and passed to [fn_service](fn_service()).
|
||||
/// could be rewritten as a simple function and passed to [`fn_service`](fn_service()).
|
||||
///
|
||||
/// ```ignore
|
||||
/// async fn my_service(req: u8) -> Result<u64, MyError>;
|
||||
///
|
||||
/// let svc = fn_service(my_service)
|
||||
/// svc.call(123)
|
||||
/// ```
|
||||
pub trait Service<Req> {
|
||||
/// Responses given by the service.
|
||||
@ -93,40 +101,40 @@ pub trait Service<Req> {
|
||||
|
||||
/// Returns `Ready` when the service is able to process requests.
|
||||
///
|
||||
/// If the service is at capacity, then `Pending` is returned and the task
|
||||
/// is notified when the service becomes ready again. This function is
|
||||
/// expected to be called while on a task.
|
||||
/// If the service is at capacity, then `Pending` is returned and the task is notified when the
|
||||
/// service becomes ready again. This function is expected to be called while on a task.
|
||||
///
|
||||
/// This is a **best effort** implementation. False positives are permitted.
|
||||
/// It is permitted for the service to return `Ready` from a `poll_ready`
|
||||
/// call and the next invocation of `call` results in an error.
|
||||
/// This is a best effort implementation. False positives are permitted. It is permitted for
|
||||
/// the service to return `Ready` from a `poll_ready` call and the next invocation of `call`
|
||||
/// results in an error.
|
||||
///
|
||||
/// # Notes
|
||||
/// 1. `.poll_ready()` might be called on different task from actual service call.
|
||||
/// 1. In case of chained services, `.poll_ready()` get called for all services at once.
|
||||
/// 1. `poll_ready` might be called on a different task to `call`.
|
||||
/// 1. In cases of chained services, `.poll_ready()` is called for all services at once.
|
||||
fn poll_ready(&self, ctx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>>;
|
||||
|
||||
/// Process the request and return the response asynchronously.
|
||||
///
|
||||
/// This function is expected to be callable off task. As such,
|
||||
/// implementations should take care to not call `poll_ready`. If the
|
||||
/// service is at capacity and the request is unable to be handled, the
|
||||
/// returned `Future` should resolve to an error.
|
||||
/// This function is expected to be callable off-task. As such, implementations of `call` should
|
||||
/// take care to not call `poll_ready`. If the service is at capacity and the request is unable
|
||||
/// to be handled, the returned `Future` should resolve to an error.
|
||||
///
|
||||
/// Calling `call` without calling `poll_ready` is permitted. The
|
||||
/// implementation must be resilient to this fact.
|
||||
/// Invoking `call` without first invoking `poll_ready` is permitted. Implementations must be
|
||||
/// resilient to this fact.
|
||||
fn call(&self, req: Req) -> Self::Future;
|
||||
}
|
||||
|
||||
/// Factory for creating `Service`s.
|
||||
///
|
||||
/// Acts as a service factory. This is useful for cases where new `Service`s
|
||||
/// must be produced. One case is a TCP server listener. The listener
|
||||
/// accepts new TCP streams, obtains a new `Service` using the
|
||||
/// `ServiceFactory` trait, and uses the new `Service` to process inbound
|
||||
/// requests on that new TCP stream.
|
||||
/// This is useful for cases where new `Service`s must be produced. One case is a TCP
|
||||
/// server listener: a listener accepts new connections, constructs a new `Service` for each using
|
||||
/// the `ServiceFactory` trait, and uses the new `Service` to process inbound requests on that new
|
||||
/// connection.
|
||||
///
|
||||
/// `Config` is a service factory configuration type.
|
||||
///
|
||||
/// Simple factories may be able to use [`fn_factory`] or [`fn_factory_with_config`] to
|
||||
/// reduce boilerplate.
|
||||
pub trait ServiceFactory<Req> {
|
||||
/// Responses given by the created services.
|
||||
type Response;
|
||||
@ -143,13 +151,14 @@ pub trait ServiceFactory<Req> {
|
||||
/// Errors potentially raised while building a service.
|
||||
type InitError;
|
||||
|
||||
/// The future of the `Service` instance.
|
||||
/// The future of the `Service` instance.g
|
||||
type Future: Future<Output = Result<Self::Service, Self::InitError>>;
|
||||
|
||||
/// Create and return a new service asynchronously.
|
||||
fn new_service(&self, cfg: Self::Config) -> Self::Future;
|
||||
}
|
||||
|
||||
// TODO: remove implement on mut reference.
|
||||
impl<'a, S, Req> Service<Req> for &'a mut S
|
||||
where
|
||||
S: Service<Req> + 'a,
|
||||
@ -167,6 +176,23 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S, Req> Service<Req> for &'a S
|
||||
where
|
||||
S: Service<Req> + 'a,
|
||||
{
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type Future = S::Future;
|
||||
|
||||
fn poll_ready(&self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
(**self).poll_ready(ctx)
|
||||
}
|
||||
|
||||
fn call(&self, request: Req) -> S::Future {
|
||||
(**self).call(request)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, Req> Service<Req> for Box<S>
|
||||
where
|
||||
S: Service<Req> + ?Sized,
|
||||
@ -184,24 +210,25 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, Req> Service<Req> for RefCell<S>
|
||||
impl<S, Req> Service<Req> for Rc<S>
|
||||
where
|
||||
S: Service<Req>,
|
||||
S: Service<Req> + ?Sized,
|
||||
{
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type Future = S::Future;
|
||||
|
||||
fn poll_ready(&self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.borrow().poll_ready(ctx)
|
||||
(**self).poll_ready(ctx)
|
||||
}
|
||||
|
||||
fn call(&self, request: Req) -> S::Future {
|
||||
self.borrow().call(request)
|
||||
(**self).call(request)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, Req> Service<Req> for Rc<RefCell<S>>
|
||||
/// This impl is deprecated since v2 because the `Service` trait now receives shared reference.
|
||||
impl<S, Req> Service<Req> for RefCell<S>
|
||||
where
|
||||
S: Service<Req>,
|
||||
{
|
||||
@ -294,44 +321,3 @@ where
|
||||
{
|
||||
tp.into_service()
|
||||
}
|
||||
|
||||
pub mod dev {
|
||||
pub use crate::apply::{Apply, ApplyFactory};
|
||||
pub use crate::fn_service::{
|
||||
FnService, FnServiceConfig, FnServiceFactory, FnServiceNoConfig,
|
||||
};
|
||||
pub use crate::map::{Map, MapServiceFactory};
|
||||
pub use crate::map_config::{MapConfig, UnitConfig};
|
||||
pub use crate::map_err::{MapErr, MapErrServiceFactory};
|
||||
pub use crate::map_init_err::MapInitErr;
|
||||
pub use crate::transform::ApplyTransform;
|
||||
pub use crate::transform_err::TransformMapInitErr;
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! always_ready {
|
||||
() => {
|
||||
#[inline]
|
||||
fn poll_ready(
|
||||
&self,
|
||||
_: &mut ::core::task::Context<'_>,
|
||||
) -> ::core::task::Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! forward_ready {
|
||||
($field:ident) => {
|
||||
#[inline]
|
||||
fn poll_ready(
|
||||
&self,
|
||||
cx: &mut ::core::task::Context<'_>,
|
||||
) -> ::core::task::Poll<Result<(), Self::Error>> {
|
||||
self.$field
|
||||
.poll_ready(cx)
|
||||
.map_err(::core::convert::Into::into)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
187
actix-service/src/macros.rs
Normal file
187
actix-service/src/macros.rs
Normal file
@ -0,0 +1,187 @@
|
||||
/// An implementation of [`poll_ready`]() that always signals readiness.
|
||||
///
|
||||
/// This should only be used for basic leaf services that have no concept of un-readiness.
|
||||
/// For wrapper or other service types, use [`forward_ready!`] for simple cases or write a bespoke
|
||||
/// `poll_ready` implementation.
|
||||
///
|
||||
/// [`poll_ready`]: crate::Service::poll_ready
|
||||
///
|
||||
/// # Examples
|
||||
/// ```no_run
|
||||
/// use actix_service::Service;
|
||||
/// use futures_util::future::{ready, Ready};
|
||||
///
|
||||
/// struct IdentityService;
|
||||
///
|
||||
/// impl Service<u32> for IdentityService {
|
||||
/// type Response = u32;
|
||||
/// type Error = ();
|
||||
/// type Future = Ready<Result<Self::Response, Self::Error>>;
|
||||
///
|
||||
/// actix_service::always_ready!();
|
||||
///
|
||||
/// fn call(&self, req: u32) -> Self::Future {
|
||||
/// ready(Ok(req))
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`forward_ready!`]: crate::forward_ready
|
||||
#[macro_export]
|
||||
macro_rules! always_ready {
|
||||
() => {
|
||||
#[inline]
|
||||
fn poll_ready(
|
||||
&self,
|
||||
_: &mut ::core::task::Context<'_>,
|
||||
) -> ::core::task::Poll<Result<(), Self::Error>> {
|
||||
::core::task::Poll::Ready(Ok(()))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// An implementation of [`poll_ready`] that forwards readiness checks to a
|
||||
/// named struct field.
|
||||
///
|
||||
/// Tuple structs are not supported.
|
||||
///
|
||||
/// [`poll_ready`]: crate::Service::poll_ready
|
||||
///
|
||||
/// # Examples
|
||||
/// ```no_run
|
||||
/// use actix_service::Service;
|
||||
/// use futures_util::future::{ready, Ready};
|
||||
///
|
||||
/// struct WrapperService<S> {
|
||||
/// inner: S,
|
||||
/// }
|
||||
///
|
||||
/// impl<S> Service<()> for WrapperService<S>
|
||||
/// where
|
||||
/// S: Service<()>,
|
||||
/// {
|
||||
/// type Response = S::Response;
|
||||
/// type Error = S::Error;
|
||||
/// type Future = S::Future;
|
||||
///
|
||||
/// actix_service::forward_ready!(inner);
|
||||
///
|
||||
/// fn call(&self, req: ()) -> Self::Future {
|
||||
/// self.inner.call(req)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! forward_ready {
|
||||
($field:ident) => {
|
||||
#[inline]
|
||||
fn poll_ready(
|
||||
&self,
|
||||
cx: &mut ::core::task::Context<'_>,
|
||||
) -> ::core::task::Poll<Result<(), Self::Error>> {
|
||||
self.$field
|
||||
.poll_ready(cx)
|
||||
.map_err(::core::convert::Into::into)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use core::{
|
||||
cell::Cell,
|
||||
convert::Infallible,
|
||||
task::{self, Context, Poll},
|
||||
};
|
||||
|
||||
use futures_util::{
|
||||
future::{ready, Ready},
|
||||
task::noop_waker,
|
||||
};
|
||||
|
||||
use crate::Service;
|
||||
|
||||
struct IdentityService;
|
||||
|
||||
impl Service<u32> for IdentityService {
|
||||
type Response = u32;
|
||||
type Error = Infallible;
|
||||
type Future = Ready<Result<Self::Response, Self::Error>>;
|
||||
|
||||
always_ready!();
|
||||
|
||||
fn call(&self, req: u32) -> Self::Future {
|
||||
ready(Ok(req))
|
||||
}
|
||||
}
|
||||
|
||||
struct CountdownService(Cell<u32>);
|
||||
|
||||
impl Service<()> for CountdownService {
|
||||
type Response = ();
|
||||
type Error = Infallible;
|
||||
type Future = Ready<Result<Self::Response, Self::Error>>;
|
||||
|
||||
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
let count = self.0.get();
|
||||
|
||||
if count == 0 {
|
||||
Poll::Ready(Ok(()))
|
||||
} else {
|
||||
self.0.set(count - 1);
|
||||
cx.waker().wake_by_ref();
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
fn call(&self, _: ()) -> Self::Future {
|
||||
ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
struct WrapperService<S> {
|
||||
inner: S,
|
||||
}
|
||||
|
||||
impl<S> Service<()> for WrapperService<S>
|
||||
where
|
||||
S: Service<()>,
|
||||
{
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type Future = S::Future;
|
||||
|
||||
forward_ready!(inner);
|
||||
|
||||
fn call(&self, _: ()) -> Self::Future {
|
||||
self.inner.call(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_always_ready_macro() {
|
||||
let waker = noop_waker();
|
||||
let mut cx = task::Context::from_waker(&waker);
|
||||
|
||||
let svc = IdentityService;
|
||||
|
||||
assert!(svc.poll_ready(&mut cx).is_ready());
|
||||
assert!(svc.poll_ready(&mut cx).is_ready());
|
||||
assert!(svc.poll_ready(&mut cx).is_ready());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_forward_ready_macro() {
|
||||
let waker = noop_waker();
|
||||
let mut cx = task::Context::from_waker(&waker);
|
||||
|
||||
let svc = WrapperService {
|
||||
inner: CountdownService(Cell::new(3)),
|
||||
};
|
||||
|
||||
assert!(svc.poll_ready(&mut cx).is_pending());
|
||||
assert!(svc.poll_ready(&mut cx).is_pending());
|
||||
assert!(svc.poll_ready(&mut cx).is_pending());
|
||||
assert!(svc.poll_ready(&mut cx).is_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;
|
||||
|
||||
|
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