mirror of
https://github.com/fafhrd91/actix-net
synced 2025-09-03 00:46:38 +02:00
Compare commits
162 Commits
tls-v3.4.0
...
proxy-prot
Author | SHA1 | Date | |
---|---|---|---|
|
e67d4521e5 | ||
|
567e9ad2ca | ||
|
d78e238415 | ||
|
26a28606d3 | ||
|
2daf68dc49 | ||
|
4d4367f5f2 | ||
|
b8ff2a47a6 | ||
|
160fdc5efc | ||
|
c856fcdcd5 | ||
|
4ee45b24b5 | ||
|
43f9da3c4e | ||
|
d24e5b4e95 | ||
|
0d45bf9b43 | ||
|
e85ae87b98 | ||
|
dc19b196da | ||
|
e22bca03d5 | ||
|
19ab9cc97a | ||
|
1ea0f773b6 | ||
|
ee3d54f574 | ||
|
75e56d56d8 | ||
|
222527a407 | ||
|
cfddd3ea2e | ||
|
d869455251 | ||
|
3e925d8f80 | ||
|
0c8337f1c9 | ||
|
20ae763da8 | ||
|
6c6f7f19f5 | ||
|
95d569d8d7 | ||
|
347fc17035 | ||
|
4cb04042f5 | ||
|
e6ddf0ccff | ||
|
21de88385e | ||
|
a91bdcb80c | ||
|
64a2d1c1eb | ||
|
637bd709ae | ||
|
bbac996677 | ||
|
c9cea86445 | ||
|
05065186ff | ||
|
3d54a00d5b | ||
|
120e0206eb | ||
|
1ad8a98f1b | ||
|
da1c0024f0 | ||
|
0be1dd8156 | ||
|
9c646e4998 | ||
|
758c37b80c | ||
|
1a1af68bf0 | ||
|
3ebed4701d | ||
|
15c13c3e32 | ||
|
3356038c80 | ||
|
7834d76d4f | ||
|
064da0e3ff | ||
|
270360e095 | ||
|
d9203797be | ||
|
67616902cc | ||
|
809071b857 | ||
|
8e7de17221 | ||
|
2972a5ac08 | ||
|
69041c4f6c | ||
|
d61b33928d | ||
|
d95eea8fbe | ||
|
53036ce9c4 | ||
|
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 |
4
.clippy.toml
Normal file
4
.clippy.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
disallowed-names = [
|
||||
"..", # defaults
|
||||
"e", # prefer `err`
|
||||
]
|
23
.cspell.yml
Normal file
23
.cspell.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
version: "0.2"
|
||||
words:
|
||||
- actix
|
||||
- addrs
|
||||
- ALPN
|
||||
- arrayvec
|
||||
- bitflags
|
||||
- clippy
|
||||
- deque
|
||||
- itertools
|
||||
- itoa
|
||||
- mptcp
|
||||
- MSRV
|
||||
- nonblocking
|
||||
- oneshot
|
||||
- pemfile
|
||||
- rcgen
|
||||
- Rustls
|
||||
- rustup
|
||||
- smallvec
|
||||
- spki
|
||||
- uring
|
||||
- webpki
|
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 -->
|
||||
|
1
.github/dependabot.yml
vendored
1
.github/dependabot.yml
vendored
@@ -8,3 +8,4 @@ updates:
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
versioning-strategy: lockfile-only
|
||||
|
62
.github/workflows/ci-post-merge.yml
vendored
62
.github/workflows/ci-post-merge.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: CI (master only)
|
||||
name: CI (post-merge)
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -35,15 +35,19 @@ jobs:
|
||||
if: matrix.target.os == 'macos-latest'
|
||||
run: sudo ifconfig lo0 alias 127.0.0.3
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- 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.1
|
||||
uses: ilammy/setup-nasm@v1.5.2
|
||||
|
||||
- name: Install OpenSSL
|
||||
if: matrix.target.os == 'windows-latest'
|
||||
@@ -55,12 +59,12 @@ jobs:
|
||||
echo "RUSTFLAGS=-C target-feature=+crt-static" >> $GITHUB_ENV
|
||||
|
||||
- name: Install Rust (${{ matrix.version }})
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.14.0
|
||||
with:
|
||||
toolchain: ${{ matrix.version }}
|
||||
|
||||
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
|
||||
uses: taiki-e/install-action@v2.33.22
|
||||
uses: taiki-e/install-action@v2.58.21
|
||||
with:
|
||||
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
|
||||
|
||||
@@ -87,19 +91,20 @@ jobs:
|
||||
run: cargo ci-check-linux
|
||||
|
||||
- name: tests
|
||||
if: matrix.target.os != 'ubuntu-latest'
|
||||
run: just test
|
||||
- name: tests
|
||||
if: matrix.target.os == 'ubuntu-latest'
|
||||
run: >-
|
||||
sudo bash -c "
|
||||
ulimit -Sl 512
|
||||
&& ulimit -Hl 512
|
||||
&& PATH=$PATH:/usr/share/rust/.cargo/bin
|
||||
&& RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo ci-test-rustls-020
|
||||
&& RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo ci-test-rustls-021
|
||||
&& RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo ci-test-linux
|
||||
"
|
||||
|
||||
# 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
|
||||
@@ -108,34 +113,17 @@ jobs:
|
||||
name: minimal versions
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Install Rust (nightly)
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.14.0
|
||||
with:
|
||||
toolchain: nightly
|
||||
|
||||
- name: Install cargo-hack & cargo-minimal-versions
|
||||
uses: taiki-e/install-action@v2.33.22
|
||||
uses: taiki-e/install-action@v2.58.21
|
||||
with:
|
||||
tool: cargo-hack,cargo-minimal-versions
|
||||
|
||||
- name: Check With Minimal Versions
|
||||
run: cargo minimal-versions check
|
||||
|
||||
nextest:
|
||||
name: nextest
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
|
||||
- name: Install cargo-nextest
|
||||
uses: taiki-e/install-action@v2.33.22
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
|
||||
- name: Test with cargo-nextest
|
||||
run: cargo nextest run
|
||||
|
22
.github/workflows/ci.yml
vendored
22
.github/workflows/ci.yml
vendored
@@ -15,7 +15,7 @@ concurrency:
|
||||
jobs:
|
||||
read_msrv:
|
||||
name: Read MSRV
|
||||
uses: actions-rust-lang/msrv/.github/workflows/msrv.yml@main
|
||||
uses: actions-rust-lang/msrv/.github/workflows/msrv.yml@v0.1.0
|
||||
|
||||
build_and_test:
|
||||
needs:
|
||||
@@ -44,15 +44,19 @@ jobs:
|
||||
if: matrix.target.os == 'macos-latest'
|
||||
run: sudo ifconfig lo0 alias 127.0.0.3
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- 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.1
|
||||
uses: ilammy/setup-nasm@v1.5.2
|
||||
|
||||
- name: Install OpenSSL
|
||||
if: matrix.target.os == 'windows-latest'
|
||||
@@ -64,12 +68,12 @@ jobs:
|
||||
echo "RUSTFLAGS=-C target-feature=+crt-static" >> $GITHUB_ENV
|
||||
|
||||
- name: Install Rust (${{ matrix.version.name }})
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.14.0
|
||||
with:
|
||||
toolchain: ${{ matrix.version.version }}
|
||||
|
||||
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
|
||||
uses: taiki-e/install-action@v2.33.22
|
||||
uses: taiki-e/install-action@v2.58.21
|
||||
with:
|
||||
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
|
||||
|
||||
@@ -105,8 +109,6 @@ jobs:
|
||||
- name: tests
|
||||
run: just test
|
||||
|
||||
# TODO: re-instate some io-uring tests for PRs
|
||||
|
||||
- name: CI cache clean
|
||||
run: cargo-ci-cache-clean
|
||||
|
||||
@@ -115,15 +117,15 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Install Rust (nightly)
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.14.0
|
||||
with:
|
||||
toolchain: nightly
|
||||
|
||||
- name: Install just
|
||||
uses: taiki-e/install-action@v2.33.22
|
||||
uses: taiki-e/install-action@v2.58.21
|
||||
with:
|
||||
tool: just
|
||||
|
||||
|
8
.github/workflows/coverage.yml
vendored
8
.github/workflows/coverage.yml
vendored
@@ -15,15 +15,15 @@ jobs:
|
||||
coverage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.14.0
|
||||
with:
|
||||
components: llvm-tools-preview
|
||||
|
||||
- name: Install cargo-llvm-cov
|
||||
uses: taiki-e/install-action@v2.33.22
|
||||
uses: taiki-e/install-action@v2.58.21
|
||||
with:
|
||||
tool: cargo-llvm-cov
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
run: cargo llvm-cov --workspace --all-features --codecov --output-path codecov.json
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v4.3.1
|
||||
uses: codecov/codecov-action@v5.5.0
|
||||
with:
|
||||
files: codecov.json
|
||||
fail_ci_if_error: true
|
||||
|
27
.github/workflows/lint.yml
vendored
27
.github/workflows/lint.yml
vendored
@@ -16,9 +16,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1.14.0
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: rustfmt
|
||||
@@ -33,36 +33,37 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1.14.0
|
||||
with: { components: clippy }
|
||||
|
||||
- uses: giraffate/clippy-action@v1.0.1
|
||||
with:
|
||||
reporter: 'github-pr-check'
|
||||
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
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Install Rust (nightly-2023-10-10)
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
- name: Install Rust (${{ vars.RUST_VERSION_EXTERNAL_TYPES }})
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.14.0
|
||||
with:
|
||||
toolchain: nightly-2023-10-10
|
||||
toolchain: ${{ vars.RUST_VERSION_EXTERNAL_TYPES }}
|
||||
|
||||
- name: Install just
|
||||
uses: taiki-e/install-action@v2.33.22
|
||||
uses: taiki-e/install-action@v2.58.21
|
||||
with:
|
||||
tool: just
|
||||
|
||||
- name: Install cargo-check-external-types
|
||||
uses: taiki-e/cache-cargo-install-action@v2.0.0
|
||||
uses: taiki-e/cache-cargo-install-action@v2.3.0
|
||||
with:
|
||||
tool: cargo-check-external-types@0.1.10
|
||||
tool: cargo-check-external-types
|
||||
|
||||
- name: check external types
|
||||
run: just check-external-types-all +nightly-2023-10-10
|
||||
run: just check-external-types-all +${{ vars.RUST_VERSION_EXTERNAL_TYPES }}
|
||||
|
36
.github/workflows/upload-doc.yml
vendored
36
.github/workflows/upload-doc.yml
vendored
@@ -1,36 +0,0 @@
|
||||
name: Upload documentation
|
||||
|
||||
on:
|
||||
push: { branches: [master] }
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
with: { toolchain: nightly }
|
||||
|
||||
- name: Build Docs
|
||||
run: cargo doc --workspace --all-features --no-deps
|
||||
|
||||
- name: Tweak HTML
|
||||
run: echo '<meta http-equiv="refresh" content="0;url=actix_server/index.html">' > target/doc/index.html
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
uses: JamesIves/github-pages-deploy-action@v4.6.0
|
||||
with:
|
||||
folder: target/doc
|
||||
single-commit: true
|
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
|
||||
|
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
|
32
.vscode/settings.json
vendored
Normal file
32
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"rust-analyzer.cargo.features": [
|
||||
"accept",
|
||||
"actix-macros",
|
||||
"connect",
|
||||
"default",
|
||||
"macros",
|
||||
"native-tls",
|
||||
"openssl",
|
||||
"rustls",
|
||||
"rustls-021",
|
||||
"rustls-0_20",
|
||||
"rustls-0_20-native-roots",
|
||||
"rustls-0_20-webpki-roots",
|
||||
"rustls-0_21",
|
||||
"rustls-0_21-native-roots",
|
||||
"rustls-0_21-webpki-roots",
|
||||
"rustls-0_22",
|
||||
"rustls-0_22-native-roots",
|
||||
"rustls-0_22-webpki-roots",
|
||||
"rustls-0_23",
|
||||
"rustls-0_23-native-roots",
|
||||
"rustls-0_23-webpki-roots",
|
||||
"rustls-webpki-0101",
|
||||
"serde",
|
||||
"tokio-rustls-023",
|
||||
"tokio-rustls-024",
|
||||
"uri",
|
||||
"webpki-roots-022",
|
||||
"webpki-roots-025",
|
||||
]
|
||||
}
|
3162
Cargo.lock
generated
Normal file
3162
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
14
Cargo.toml
14
Cargo.toml
@@ -3,6 +3,7 @@ resolver = "2"
|
||||
members = [
|
||||
"actix-codec",
|
||||
"actix-macros",
|
||||
"actix-proxy-protocol",
|
||||
"actix-rt",
|
||||
"actix-server",
|
||||
"actix-service",
|
||||
@@ -17,11 +18,12 @@ members = [
|
||||
[workspace.package]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.70"
|
||||
rust-version = "1.75"
|
||||
|
||||
[patch.crates-io]
|
||||
actix-codec = { path = "actix-codec" }
|
||||
actix-macros = { path = "actix-macros" }
|
||||
actix-proxy-protocol = { path = "actix-proxy-protocol" }
|
||||
actix-rt = { path = "actix-rt" }
|
||||
actix-server = { path = "actix-server" }
|
||||
actix-service = { path = "actix-service" }
|
||||
@@ -36,3 +38,13 @@ local-waker = { path = "local-waker" }
|
||||
lto = true
|
||||
opt-level = 3
|
||||
codegen-units = 1
|
||||
|
||||
[workspace.lints.rust]
|
||||
rust-2018-idioms = "deny"
|
||||
nonstandard-style = "deny"
|
||||
future-incompatible = "deny"
|
||||
missing-docs = { level = "warn", priority = -1 }
|
||||
|
||||
[workspace.lints.clippy]
|
||||
uninlined-format-args = "warn"
|
||||
disallowed-names = "warn"
|
||||
|
@@ -3,7 +3,7 @@
|
||||
> A collection of lower-level libraries for composable network services.
|
||||
|
||||
[](https://github.com/actix/actix-net/actions/workflows/ci.yml)
|
||||
[](https://codecov.io/gh/actix/actix-net)
|
||||
[](https://codecov.io/gh/actix/actix-net)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
[](https://deps.rs/repo/github/actix/actix-net)
|
||||
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.70.
|
||||
- Minimum supported Rust version (MSRV) is now 1.75.
|
||||
|
||||
## 0.5.2
|
||||
|
||||
|
@@ -1,10 +1,7 @@
|
||||
[package]
|
||||
name = "actix-codec"
|
||||
version = "0.5.2"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
]
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>", "Rob Ede <robjtede@icloud.com>"]
|
||||
description = "Codec utilities for working with framed protocols"
|
||||
keywords = ["network", "framework", "async", "futures"]
|
||||
repository = "https://github.com/actix/actix-net"
|
||||
@@ -14,13 +11,7 @@ edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[package.metadata.cargo_check_external_types]
|
||||
allowed_external_types = [
|
||||
"bytes::*",
|
||||
"futures_core::*",
|
||||
"futures_sink::*",
|
||||
"tokio::*",
|
||||
"tokio_util::*",
|
||||
]
|
||||
allowed_external_types = ["bytes::*", "futures_core::*", "futures_sink::*", "tokio::*", "tokio_util::*"]
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2"
|
||||
@@ -29,7 +20,7 @@ futures-core = { version = "0.3.7", default-features = false }
|
||||
futures-sink = { version = "0.3.7", default-features = false }
|
||||
memchr = "2.3"
|
||||
pin-project-lite = "0.2"
|
||||
tokio = "1.23.1"
|
||||
tokio = "1.44.2"
|
||||
tokio-util = { version = "0.7", features = ["codec", "io"] }
|
||||
tracing = { version = "0.1.30", default-features = false, features = ["log"] }
|
||||
|
||||
@@ -40,3 +31,6 @@ tokio-test = "0.4.2"
|
||||
[[bench]]
|
||||
name = "lines"
|
||||
harness = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
@@ -1,3 +1,5 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use bytes::BytesMut;
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
|
@@ -6,8 +6,6 @@
|
||||
//! [`Sink`]: futures_sink::Sink
|
||||
//! [`Stream`]: futures_core::Stream
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible, missing_docs)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
|
||||
|
@@ -1,3 +1,5 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
io::{self, Write},
|
||||
@@ -55,7 +57,7 @@ impl Write for Bilateral {
|
||||
Ok(data.len())
|
||||
}
|
||||
Some(Err(err)) => Err(err),
|
||||
None => panic!("unexpected write; {:?}", src),
|
||||
None => panic!("unexpected write; {src:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.70.
|
||||
- Minimum supported Rust version (MSRV) is now 1.75.
|
||||
|
||||
## 0.2.4
|
||||
|
||||
|
@@ -2,9 +2,9 @@
|
||||
name = "actix-macros"
|
||||
version = "0.2.4"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Ibraheem Ahmed <ibrah1440@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Ibraheem Ahmed <ibrah1440@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
]
|
||||
description = "Macros for Actix system and runtime"
|
||||
repository = "https://github.com/actix/actix-net"
|
||||
@@ -15,7 +15,7 @@ rust-version.workspace = true
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = [
|
||||
"proc_macro2", # specified for minimal versions compat
|
||||
"proc_macro2", # specified for minimal versions compat
|
||||
]
|
||||
|
||||
[lib]
|
||||
@@ -32,5 +32,8 @@ proc-macro2 = "1.0.60"
|
||||
[dev-dependencies]
|
||||
actix-rt = "2"
|
||||
futures-util = { version = "0.3.17", default-features = false }
|
||||
rustversion-msrv = "0.99"
|
||||
rustversion-msrv = "0.100"
|
||||
trybuild = "1"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
@@ -8,8 +8,6 @@
|
||||
//! # Tests
|
||||
//! See docs for the [`#[test]`](macro@test) macro.
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
|
||||
|
@@ -1,3 +1,5 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
#[rustversion_msrv::msrv]
|
||||
#[test]
|
||||
fn compile_macros() {
|
||||
|
@@ -1,3 +1,5 @@
|
||||
#![allow(unused_imports)]
|
||||
|
||||
mod system {
|
||||
pub use actix_rt::System as MySystem;
|
||||
}
|
||||
|
7
actix-proxy-protocol/CHANGES.md
Normal file
7
actix-proxy-protocol/CHANGES.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2022-xx-xx
|
||||
|
||||
## 0.0.1 - 2022-xx-xx
|
||||
|
||||
- delete me
|
40
actix-proxy-protocol/Cargo.toml
Executable file
40
actix-proxy-protocol/Cargo.toml
Executable file
@@ -0,0 +1,40 @@
|
||||
[package]
|
||||
name = "actix-proxy-protocol"
|
||||
version = "0.0.1"
|
||||
authors = ["Rob Ede <robjtede@icloud.com>"]
|
||||
description = "PROXY protocol utilities"
|
||||
keywords = ["proxy", "protocol", "network", "haproxy", "tcp"]
|
||||
categories = ["network-programming", "asynchronous"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-net"
|
||||
license.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
actix-service = "2"
|
||||
actix-utils = "3"
|
||||
|
||||
arrayvec = "0.7"
|
||||
bitflags = "2"
|
||||
crc32fast = "1"
|
||||
futures-core = { version = "0.3.17", default-features = false, features = ["std"] }
|
||||
futures-util = { version = "0.3.17", default-features = false, features = ["std"] }
|
||||
itoa = "1"
|
||||
nom = "8"
|
||||
smallvec = "1"
|
||||
tokio = { version = "1.13.1", features = ["sync", "io-util"] }
|
||||
tracing = { version = "0.1.30", default-features = false, features = ["log"] }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-codec = "0.5"
|
||||
actix-rt = "2.6"
|
||||
actix-server = "2"
|
||||
bytes = "1"
|
||||
const-str = "0.5"
|
||||
futures-util = { version = "0.3.7", default-features = false, features = ["sink", "async-await-macro"] }
|
||||
hex = "0.4"
|
||||
once_cell = "1"
|
||||
pretty_assertions = "1"
|
||||
tokio = { version = "1.13.1", features = ["io-util", "rt-multi-thread", "macros", "fs"] }
|
||||
tracing-subscriber = "0.3"
|
1
actix-proxy-protocol/LICENSE-APACHE
Symbolic link
1
actix-proxy-protocol/LICENSE-APACHE
Symbolic link
@@ -0,0 +1 @@
|
||||
../LICENSE-APACHE
|
1
actix-proxy-protocol/LICENSE-MIT
Symbolic link
1
actix-proxy-protocol/LICENSE-MIT
Symbolic link
@@ -0,0 +1 @@
|
||||
../LICENSE-MIT
|
17
actix-proxy-protocol/README.md
Normal file
17
actix-proxy-protocol/README.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# actix-proxy-protocol
|
||||
|
||||
> Implementation of the [PROXY protocol].
|
||||
|
||||
[](https://crates.io/crates/actix-proxy-protocol)
|
||||
[](https://docs.rs/actix-proxy-protocol/0.1.0)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
||||

|
||||
[](https://deps.rs/crate/actix-proxy-protocol/0.1.0)
|
||||

|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
## Resources
|
||||
|
||||
- [Examples](./examples)
|
||||
|
||||
[proxy protocol]: https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
|
173
actix-proxy-protocol/examples/proxy-server.rs
Normal file
173
actix-proxy-protocol/examples/proxy-server.rs
Normal file
@@ -0,0 +1,173 @@
|
||||
//! Adds PROXY protocol v1 prelude to connections.
|
||||
|
||||
#![allow(unused)]
|
||||
|
||||
use std::{
|
||||
io, mem,
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
|
||||
use actix_proxy_protocol::{tlv, v1, v2, AddressFamily, Command, TransportProtocol};
|
||||
use actix_rt::net::TcpStream;
|
||||
use actix_server::Server;
|
||||
use actix_service::{fn_service, ServiceFactoryExt as _};
|
||||
use arrayvec::ArrayVec;
|
||||
use bytes::BytesMut;
|
||||
use const_str::concat_bytes;
|
||||
use once_cell::sync::Lazy;
|
||||
use tokio::io::{
|
||||
copy_bidirectional, AsyncBufReadExt as _, AsyncReadExt as _, AsyncWriteExt as _, BufReader,
|
||||
};
|
||||
|
||||
static UPSTREAM: Lazy<SocketAddr> = Lazy::new(|| SocketAddr::from(([127, 0, 0, 1], 8080)));
|
||||
|
||||
/*
|
||||
NOTES:
|
||||
108 byte buffer on receiver side is enough for any PROXY header
|
||||
after PROXY, receive until CRLF, *then* decode parts
|
||||
TLV = type-length-value
|
||||
|
||||
TO DO:
|
||||
handle UNKNOWN transport
|
||||
v2 UNSPEC mode
|
||||
AF_UNIX socket
|
||||
*/
|
||||
|
||||
fn extend_with_ip_bytes(buf: &mut Vec<u8>, ip: IpAddr) {
|
||||
match ip {
|
||||
IpAddr::V4(ip) => buf.extend_from_slice(&ip.octets()),
|
||||
IpAddr::V6(ip) => buf.extend_from_slice(&ip.octets()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn wrap_with_proxy_protocol_v1(mut stream: TcpStream) -> io::Result<()> {
|
||||
let mut upstream = TcpStream::connect(("127.0.0.1", 8080)).await?;
|
||||
|
||||
tracing::info!(
|
||||
"PROXYv1 {} -> {}",
|
||||
stream.peer_addr().unwrap(),
|
||||
UPSTREAM.to_string(),
|
||||
);
|
||||
|
||||
let proxy_header = v1::Header::new(
|
||||
AddressFamily::Inet,
|
||||
SocketAddr::from(([127, 0, 0, 1], 8081)),
|
||||
*UPSTREAM,
|
||||
);
|
||||
|
||||
proxy_header.write_to_tokio(&mut upstream).await?;
|
||||
|
||||
let (_bytes_read, _bytes_written) = copy_bidirectional(&mut stream, &mut upstream).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn wrap_with_proxy_protocol_v2(mut stream: TcpStream) -> io::Result<()> {
|
||||
let mut upstream = TcpStream::connect(("127.0.0.1", 8080)).await?;
|
||||
|
||||
tracing::info!(
|
||||
"PROXYv2 {} -> {}",
|
||||
stream.peer_addr().unwrap(),
|
||||
UPSTREAM.to_string(),
|
||||
);
|
||||
|
||||
let mut proxy_header = v2::Header::new_tcp_ipv4_proxy(([127, 0, 0, 1], 8082), *UPSTREAM);
|
||||
|
||||
proxy_header.add_typed_tlv(tlv::UniqueId::new("4269")); // UNIQUE_ID
|
||||
proxy_header.add_typed_tlv(tlv::Noop::new("NOOP m8")); // NOOP
|
||||
proxy_header.add_typed_tlv(tlv::Authority::new("localhost")); // NOOP
|
||||
proxy_header.add_typed_tlv(tlv::Alpn::new("http/1.1")); // NOOP
|
||||
proxy_header.add_crc23c_checksum();
|
||||
|
||||
proxy_header.write_to_tokio(&mut upstream).await?;
|
||||
|
||||
let (_bytes_read, _bytes_written) = copy_bidirectional(&mut stream, &mut upstream).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn unwrap_proxy_protocol(mut stream: TcpStream) -> io::Result<()> {
|
||||
let mut upstream = TcpStream::connect(("127.0.0.1", 8080)).await?;
|
||||
|
||||
tracing::info!(
|
||||
"PROXY unwrap {} -> {}",
|
||||
stream.peer_addr().unwrap(),
|
||||
UPSTREAM.to_string(),
|
||||
);
|
||||
|
||||
let mut header = [0; 12];
|
||||
stream.peek(&mut header).await?;
|
||||
|
||||
eprintln!("header: {}", String::from_utf8_lossy(&header));
|
||||
|
||||
if &header[..v1::SIGNATURE.len()] == v1::SIGNATURE.as_bytes() {
|
||||
tracing::info!("v1");
|
||||
|
||||
let mut stream = BufReader::new(stream);
|
||||
let mut buf = Vec::with_capacity(v1::MAX_HEADER_SIZE);
|
||||
let _len = stream.read_until(b'\n', &mut buf).await?;
|
||||
|
||||
eprintln!("{}", String::from_utf8_lossy(&buf));
|
||||
|
||||
let (rest, header) = match v1::Header::try_from_bytes(&buf) {
|
||||
Ok((rest, header)) => (rest, header),
|
||||
Err(err) => {
|
||||
match err {
|
||||
nom::Err::Incomplete(needed) => todo!(),
|
||||
nom::Err::Error(err) => {
|
||||
eprintln!(
|
||||
"err {:?}, input: {}",
|
||||
err.code,
|
||||
String::from_utf8_lossy(err.input)
|
||||
)
|
||||
}
|
||||
nom::Err::Failure(_) => todo!(),
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
eprintln!("{:02X?} - {:?}", rest, header);
|
||||
|
||||
let (_bytes_read, _bytes_written) = copy_bidirectional(&mut stream, &mut upstream).await?;
|
||||
} else if header == v2::SIGNATURE {
|
||||
tracing::info!("v2");
|
||||
let (_bytes_read, _bytes_written) = copy_bidirectional(&mut stream, &mut upstream).await?;
|
||||
} else {
|
||||
tracing::warn!("No proxy header; closing");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start_server() -> io::Result<Server> {
|
||||
tracing::info!("proxying to 127.0.0.1:8080");
|
||||
|
||||
Ok(Server::build()
|
||||
.bind("proxy-protocol-v1", ("127.0.0.1", 8081), move || {
|
||||
fn_service(wrap_with_proxy_protocol_v1)
|
||||
.map_err(|err| tracing::error!("service error: {err:?}"))
|
||||
})?
|
||||
.bind("proxy-protocol-v2", ("127.0.0.1", 8082), move || {
|
||||
fn_service(wrap_with_proxy_protocol_v2)
|
||||
.map_err(|err| tracing::error!("service error: {err:?}"))
|
||||
})?
|
||||
.bind("proxy-protocol-unwrap", ("127.0.0.1", 8083), move || {
|
||||
fn_service(unwrap_proxy_protocol)
|
||||
.map_err(|err| tracing::error!("service error: {err:?}"))
|
||||
})?
|
||||
.workers(2)
|
||||
.run())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> io::Result<()> {
|
||||
tracing_subscriber::fmt::fmt().without_time().init();
|
||||
|
||||
start_server()?.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
156
actix-proxy-protocol/src/lib.rs
Normal file
156
actix-proxy-protocol/src/lib.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
//! PROXY protocol.
|
||||
|
||||
#![expect(dead_code)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
|
||||
pub mod tlv;
|
||||
pub mod v1;
|
||||
pub mod v2;
|
||||
|
||||
/// PROXY Protocol Version.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum Version {
|
||||
/// Human-readable header format (Version 1)
|
||||
V1,
|
||||
|
||||
/// Binary header format (Version 2)
|
||||
V2,
|
||||
}
|
||||
|
||||
impl Version {
|
||||
const fn signature(&self) -> &'static [u8] {
|
||||
match self {
|
||||
Version::V1 => v1::SIGNATURE.as_bytes(),
|
||||
Version::V2 => v2::SIGNATURE.as_slice(),
|
||||
}
|
||||
}
|
||||
|
||||
const fn v2_hi(&self) -> u8 {
|
||||
(match self {
|
||||
Version::V1 => panic!("v1 not supported in PROXY v2"),
|
||||
Version::V2 => 0x2,
|
||||
}) << 4
|
||||
}
|
||||
}
|
||||
|
||||
/// Command
|
||||
///
|
||||
/// other values are unassigned and must not be emitted by senders. Receivers
|
||||
/// must drop connections presenting unexpected values here.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Command {
|
||||
/// \x0 : LOCAL : the connection was established on purpose by the proxy
|
||||
/// without being relayed. The connection endpoints are the sender and the
|
||||
/// receiver. Such connections exist when the proxy sends health-checks to the
|
||||
/// server. The receiver must accept this connection as valid and must use the
|
||||
/// real connection endpoints and discard the protocol block including the
|
||||
/// family which is ignored.
|
||||
Local,
|
||||
|
||||
/// \x1 : PROXY : the connection was established on behalf of another node,
|
||||
/// and reflects the original connection endpoints. The receiver must then use
|
||||
/// the information provided in the protocol block to get original the address.
|
||||
Proxy,
|
||||
}
|
||||
|
||||
impl Command {
|
||||
const fn v2_lo(&self) -> u8 {
|
||||
match self {
|
||||
Command::Local => 0x0,
|
||||
Command::Proxy => 0x1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Address Family.
|
||||
///
|
||||
/// maps to the original socket family without necessarily
|
||||
/// matching the values internally used by the system.
|
||||
///
|
||||
/// other values are unspecified and must not be emitted in version 2 of this
|
||||
/// protocol and must be rejected as invalid by receivers.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum AddressFamily {
|
||||
/// 0x0 : AF_UNSPEC : the connection is forwarded for an unknown, unspecified
|
||||
/// or unsupported protocol. The sender should use this family when sending
|
||||
/// LOCAL commands or when dealing with unsupported protocol families. The
|
||||
/// receiver is free to accept the connection anyway and use the real endpoint
|
||||
/// addresses or to reject it. The receiver should ignore address information.
|
||||
Unspecified,
|
||||
|
||||
/// 0x1 : AF_INET : the forwarded connection uses the AF_INET address family
|
||||
/// (IPv4). The addresses are exactly 4 bytes each in network byte order,
|
||||
/// followed by transport protocol information (typically ports).
|
||||
Inet,
|
||||
|
||||
/// 0x2 : AF_INET6 : the forwarded connection uses the AF_INET6 address family
|
||||
/// (IPv6). The addresses are exactly 16 bytes each in network byte order,
|
||||
/// followed by transport protocol information (typically ports).
|
||||
Inet6,
|
||||
|
||||
/// 0x3 : AF_UNIX : the forwarded connection uses the AF_UNIX address family
|
||||
/// (UNIX). The addresses are exactly 108 bytes each.
|
||||
Unix,
|
||||
}
|
||||
|
||||
impl AddressFamily {
|
||||
pub(crate) fn v1_str(&self) -> &'static str {
|
||||
match self {
|
||||
AddressFamily::Inet => "TCP4",
|
||||
AddressFamily::Inet6 => "TCP6",
|
||||
af => panic!("{:?} is not supported in PROXY v1", af),
|
||||
}
|
||||
}
|
||||
|
||||
const fn v2_hi(&self) -> u8 {
|
||||
(match self {
|
||||
AddressFamily::Unspecified => 0x0,
|
||||
AddressFamily::Inet => 0x1,
|
||||
AddressFamily::Inet6 => 0x2,
|
||||
AddressFamily::Unix => 0x3,
|
||||
}) << 4
|
||||
}
|
||||
}
|
||||
|
||||
/// Transport Protocol.
|
||||
///
|
||||
/// other values are unspecified and must not be emitted in version 2 of this
|
||||
/// protocol and must be rejected as invalid by receivers.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum TransportProtocol {
|
||||
/// 0x0 : UNSPEC : the connection is forwarded for an unknown, unspecified
|
||||
/// or unsupported protocol. The sender should use this family when sending
|
||||
/// LOCAL commands or when dealing with unsupported protocol families. The
|
||||
/// receiver is free to accept the connection anyway and use the real endpoint
|
||||
/// addresses or to reject it. The receiver should ignore address information.
|
||||
Unspecified,
|
||||
|
||||
/// 0x1 : STREAM : the forwarded connection uses a SOCK_STREAM protocol (eg:
|
||||
/// TCP or UNIX_STREAM). When used with AF_INET/AF_INET6 (TCP), the addresses
|
||||
/// are followed by the source and destination ports represented on 2 bytes
|
||||
/// each in network byte order.
|
||||
Stream,
|
||||
|
||||
/// 0x2 : DGRAM : the forwarded connection uses a SOCK_DGRAM protocol (eg:
|
||||
/// UDP or UNIX_DGRAM). When used with AF_INET/AF_INET6 (UDP), the addresses
|
||||
/// are followed by the source and destination ports represented on 2 bytes
|
||||
/// each in network byte order.
|
||||
Datagram,
|
||||
}
|
||||
|
||||
impl TransportProtocol {
|
||||
const fn v2_lo(&self) -> u8 {
|
||||
match self {
|
||||
TransportProtocol::Unspecified => 0x0,
|
||||
TransportProtocol::Stream => 0x1,
|
||||
TransportProtocol::Datagram => 0x2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ProxyProtocolHeader {
|
||||
V1(v1::Header),
|
||||
V2(v2::Header),
|
||||
}
|
292
actix-proxy-protocol/src/tlv.rs
Normal file
292
actix-proxy-protocol/src/tlv.rs
Normal file
@@ -0,0 +1,292 @@
|
||||
use std::{borrow::Cow, convert::TryFrom, str};
|
||||
|
||||
const PP2_TYPE_ALPN: u8 = 0x01; // done
|
||||
const PP2_TYPE_AUTHORITY: u8 = 0x02; // done
|
||||
const PP2_TYPE_CRC32C: u8 = 0x03; // done
|
||||
const PP2_TYPE_NOOP: u8 = 0x04; // done
|
||||
const PP2_TYPE_UNIQUE_ID: u8 = 0x05; // done
|
||||
const PP2_TYPE_SSL: u8 = 0x20;
|
||||
const PP2_SUBTYPE_SSL_VERSION: u8 = 0x21;
|
||||
const PP2_SUBTYPE_SSL_CN: u8 = 0x22;
|
||||
const PP2_SUBTYPE_SSL_CIPHER: u8 = 0x23;
|
||||
const PP2_SUBTYPE_SSL_SIG_ALG: u8 = 0x24;
|
||||
const PP2_SUBTYPE_SSL_KEY_ALG: u8 = 0x25;
|
||||
const PP2_TYPE_NETNS: u8 = 0x30;
|
||||
|
||||
pub trait Tlv: Sized {
|
||||
const TYPE: u8;
|
||||
|
||||
fn try_from_value(value: &[u8]) -> Option<Self>;
|
||||
|
||||
fn value_bytes(&self) -> Cow<'_, [u8]>;
|
||||
|
||||
fn try_from_parts(typ: u8, value: &[u8]) -> Option<Self> {
|
||||
if typ != Self::TYPE {
|
||||
return None;
|
||||
}
|
||||
|
||||
Self::try_from_value(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// Application-Layer Protocol Negotiation (ALPN). It is a byte sequence defining
|
||||
/// the upper layer protocol in use over the connection. The most common use case
|
||||
/// will be to pass the exact copy of the ALPN extension of the Transport Layer
|
||||
/// Security (TLS) protocol as defined by RFC7301 [9].
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Alpn {
|
||||
alpn: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Alpn {
|
||||
///
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if `alpn` is empty (i.e., has length of 0).
|
||||
pub fn new(alpn: impl Into<Vec<u8>>) -> Self {
|
||||
let alpn = alpn.into();
|
||||
|
||||
assert!(!alpn.is_empty(), "ALPN TLV value cannot be empty");
|
||||
|
||||
Self { alpn }
|
||||
}
|
||||
}
|
||||
|
||||
impl Tlv for Alpn {
|
||||
const TYPE: u8 = PP2_TYPE_ALPN;
|
||||
|
||||
fn try_from_value(value: &[u8]) -> Option<Self> {
|
||||
Some(Self {
|
||||
alpn: value.to_owned(),
|
||||
})
|
||||
}
|
||||
|
||||
fn value_bytes(&self) -> Cow<'_, [u8]> {
|
||||
Cow::Borrowed(&self.alpn)
|
||||
}
|
||||
}
|
||||
|
||||
/// Contains the host name value passed by the client, as an UTF8-encoded string.
|
||||
/// In case of TLS being used on the client connection, this is the exact copy of
|
||||
/// the "server_name" extension as defined by RFC3546 [10], section 3.1, often
|
||||
/// referred to as "SNI". There are probably other situations where an authority
|
||||
/// can be mentioned on a connection without TLS being involved at all.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Authority {
|
||||
authority: String,
|
||||
}
|
||||
|
||||
impl Authority {
|
||||
/// A UTF-8
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if `authority` is an empty string.
|
||||
pub fn new(authority: impl Into<String>) -> Self {
|
||||
let authority = authority.into();
|
||||
|
||||
assert!(!authority.is_empty(), "Authority TLV value cannot be empty");
|
||||
|
||||
Self { authority }
|
||||
}
|
||||
}
|
||||
|
||||
impl Tlv for Authority {
|
||||
const TYPE: u8 = PP2_TYPE_AUTHORITY;
|
||||
|
||||
fn try_from_value(value: &[u8]) -> Option<Self> {
|
||||
Some(Self {
|
||||
authority: str::from_utf8(value).ok()?.to_owned(),
|
||||
})
|
||||
}
|
||||
|
||||
fn value_bytes(&self) -> Cow<'_, [u8]> {
|
||||
Cow::Borrowed(self.authority.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
/// The value of the type PP2_TYPE_CRC32C is a 32-bit number storing the CRC32c
|
||||
/// checksum of the PROXY protocol header.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct Crc32c {
|
||||
pub(crate) checksum: u32,
|
||||
}
|
||||
|
||||
impl Tlv for Crc32c {
|
||||
const TYPE: u8 = PP2_TYPE_CRC32C;
|
||||
|
||||
fn try_from_value(value: &[u8]) -> Option<Self> {
|
||||
let checksum_bytes = <[u8; 4]>::try_from(value).ok()?;
|
||||
|
||||
Some(Self {
|
||||
checksum: u32::from_be_bytes(checksum_bytes),
|
||||
})
|
||||
}
|
||||
|
||||
fn value_bytes(&self) -> Cow<'_, [u8]> {
|
||||
Cow::Owned(self.checksum.to_be_bytes().to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
/// The TLV of this type should be ignored when parsed. The value is zero or more
|
||||
/// bytes. Can be used for data padding or alignment. Note that it can be used
|
||||
/// to align only by 3 or more bytes because a TLV can not be smaller than that.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Noop {
|
||||
value: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Noop {
|
||||
///
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if `value` is empty (i.e., has length of 0).
|
||||
pub fn new(value: impl Into<Vec<u8>>) -> Self {
|
||||
let value = value.into();
|
||||
|
||||
assert!(!value.is_empty(), "Noop TLV `value` cannot be empty");
|
||||
|
||||
Self { value }
|
||||
}
|
||||
}
|
||||
|
||||
impl Tlv for Noop {
|
||||
const TYPE: u8 = PP2_TYPE_NOOP;
|
||||
|
||||
fn try_from_value(value: &[u8]) -> Option<Self> {
|
||||
Some(Self {
|
||||
value: value.to_owned(),
|
||||
})
|
||||
}
|
||||
|
||||
fn value_bytes(&self) -> Cow<'_, [u8]> {
|
||||
Cow::Borrowed(&self.value)
|
||||
}
|
||||
}
|
||||
|
||||
/// The value of the type PP2_TYPE_UNIQUE_ID is an opaque byte sequence of up to
|
||||
/// 128 bytes generated by the upstream proxy that uniquely identifies the
|
||||
/// connection.
|
||||
///
|
||||
/// The unique ID can be used to easily correlate connections across multiple
|
||||
/// layers of proxies, without needing to look up IP addresses and port numbers.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct UniqueId {
|
||||
value: Vec<u8>,
|
||||
}
|
||||
|
||||
impl UniqueId {
|
||||
///
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if `value` is 0 bytes or larger than 128 bytes.
|
||||
pub fn new(id: impl Into<Vec<u8>>) -> Self {
|
||||
let value = id.into();
|
||||
|
||||
assert!(!value.is_empty(), "UniqueId TLV `value` cannot be empty");
|
||||
assert!(
|
||||
value.len() < 128,
|
||||
"UniqueId TLV `value` cannot be larger than 128 bytes"
|
||||
);
|
||||
|
||||
Self { value }
|
||||
}
|
||||
}
|
||||
|
||||
impl Tlv for UniqueId {
|
||||
const TYPE: u8 = PP2_TYPE_UNIQUE_ID;
|
||||
|
||||
fn try_from_value(value: &[u8]) -> Option<Self> {
|
||||
Some(Self {
|
||||
value: value.to_owned(),
|
||||
})
|
||||
}
|
||||
|
||||
fn value_bytes(&self) -> Cow<'_, [u8]> {
|
||||
Cow::Borrowed(&self.value)
|
||||
}
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct SslClientFlags: u8 {
|
||||
const PP2_CLIENT_SSL = 0x01;
|
||||
const PP2_CLIENT_CERT_CONN = 0x02;
|
||||
const PP2_CLIENT_CERT_SESS = 0x04;
|
||||
}
|
||||
}
|
||||
|
||||
/// TLS (SSL).
|
||||
///
|
||||
/// Very broken atm.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Ssl {
|
||||
/// The <client> field is made of a bit field indicating which element is present.
|
||||
///
|
||||
/// Note, that each of these elements may lead to extra data being appended to
|
||||
/// this TLV using a second level of TLV encapsulation. It is thus possible to
|
||||
/// find multiple TLV values after this field. The total length of the pp2_tlv_ssl
|
||||
/// TLV will reflect this.
|
||||
client: SslClientFlags,
|
||||
|
||||
/// The <verify> field will be zero if the client presented a certificate
|
||||
/// and it was successfully verified, and non-zero otherwise.
|
||||
verify: bool,
|
||||
|
||||
/// Sub-TLVs.
|
||||
tlvs: Vec<SslTlv>,
|
||||
}
|
||||
|
||||
impl Tlv for Ssl {
|
||||
const TYPE: u8 = PP2_TYPE_SSL;
|
||||
|
||||
fn try_from_value(_value: &[u8]) -> Option<Self> {
|
||||
/// The PP2_CLIENT_SSL flag indicates that the client connected over SSL/TLS. When
|
||||
/// this field is present, the US-ASCII string representation of the TLS version is
|
||||
/// appended at the end of the field in the TLV format using the type
|
||||
/// PP2_SUBTYPE_SSL_VERSION.
|
||||
const PP2_CLIENT_SSL: u8 = 0x01;
|
||||
|
||||
/// PP2_CLIENT_CERT_CONN indicates that the client provided a certificate over the
|
||||
/// current connection.
|
||||
const PP2_CLIENT_CERT_CONN: u8 = 0x02;
|
||||
|
||||
/// PP2_CLIENT_CERT_SESS indicates that the client provided a
|
||||
/// certificate at least once over the TLS session this connection belongs to.
|
||||
const PP2_CLIENT_CERT_SESS: u8 = 0x04;
|
||||
|
||||
// TODO: finish parsing
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn value_bytes(&self) -> Cow<'_, [u8]> {
|
||||
Cow::Borrowed(&[])
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct SslTlv {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// #[test]
|
||||
// #[should_panic]
|
||||
// fn tlv_zero_len() {
|
||||
// Tlv::new(0x00, vec![]);
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn tlv_as_crc32c() {
|
||||
// noop
|
||||
assert_eq!(Crc32c::try_from_parts(0x04, &[0x00]), None);
|
||||
|
||||
assert_eq!(
|
||||
Crc32c::try_from_parts(0x03, &[0x08, 0x70, 0x17, 0x7b]),
|
||||
Some(Crc32c {
|
||||
checksum: 141563771
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
148
actix-proxy-protocol/src/v1.rs
Normal file
148
actix-proxy-protocol/src/v1.rs
Normal file
@@ -0,0 +1,148 @@
|
||||
use std::{fmt, io, net::SocketAddr};
|
||||
|
||||
use arrayvec::ArrayVec;
|
||||
use nom::{IResult, Parser as _};
|
||||
use tokio::io::{AsyncWrite, AsyncWriteExt as _};
|
||||
|
||||
use crate::AddressFamily;
|
||||
|
||||
pub const SIGNATURE: &str = "PROXY";
|
||||
pub const MAX_HEADER_SIZE: usize = 107;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Header {
|
||||
/// Address family.
|
||||
af: AddressFamily,
|
||||
|
||||
/// Source address.
|
||||
src: SocketAddr,
|
||||
|
||||
/// Destination address.
|
||||
dst: SocketAddr,
|
||||
}
|
||||
|
||||
impl Header {
|
||||
pub const fn new(af: AddressFamily, src: SocketAddr, dst: SocketAddr) -> Self {
|
||||
Self { af, src, dst }
|
||||
}
|
||||
|
||||
pub const fn new_inet(src: SocketAddr, dst: SocketAddr) -> Self {
|
||||
Self::new(AddressFamily::Inet, src, dst)
|
||||
}
|
||||
|
||||
pub const fn new_inet6(src: SocketAddr, dst: SocketAddr) -> Self {
|
||||
Self::new(AddressFamily::Inet6, src, dst)
|
||||
}
|
||||
|
||||
pub fn write_to(&self, wrt: &mut impl io::Write) -> io::Result<()> {
|
||||
write!(wrt, "{self}")
|
||||
}
|
||||
|
||||
pub async fn write_to_tokio(&self, wrt: &mut (impl AsyncWrite + Unpin)) -> io::Result<()> {
|
||||
// max length of a V1 header is 107 bytes
|
||||
let mut buf = ArrayVec::<_, MAX_HEADER_SIZE>::new();
|
||||
self.write_to(&mut buf)?;
|
||||
wrt.write_all(&buf).await
|
||||
}
|
||||
|
||||
pub fn try_from_bytes(slice: &[u8]) -> IResult<&[u8], Self> {
|
||||
parsing::parse(slice)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Header {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{proto_sig} {af} {src_ip} {dst_ip} {src_port} {dst_port}\r\n",
|
||||
proto_sig = SIGNATURE,
|
||||
af = self.af.v1_str(),
|
||||
src_ip = self.src.ip(),
|
||||
dst_ip = self.dst.ip(),
|
||||
src_port = itoa::Buffer::new().format(self.src.port()),
|
||||
dst_port = itoa::Buffer::new().format(self.dst.port()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
mod parsing {
|
||||
use std::{
|
||||
net::{Ipv4Addr, SocketAddrV4},
|
||||
str::{self, FromStr},
|
||||
};
|
||||
|
||||
use nom::{
|
||||
branch::alt,
|
||||
bytes::complete::{tag, take_while},
|
||||
character::complete::char,
|
||||
combinator::{map, map_res},
|
||||
IResult,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Parses a number from serialized representation (as bytes).
|
||||
fn parse_number<T: FromStr>(input: &[u8]) -> IResult<&[u8], T> {
|
||||
map_res(take_while(|c: u8| c.is_ascii_digit()), |s: &[u8]| {
|
||||
let s = str::from_utf8(s).map_err(|_| "utf8 error")?;
|
||||
let val = s.parse::<T>().map_err(|_| "u8 parse error")?;
|
||||
Ok::<_, Box<dyn std::error::Error>>(val)
|
||||
})
|
||||
.parse(input)
|
||||
}
|
||||
|
||||
/// Parses an address family.
|
||||
fn parse_address_family(input: &[u8]) -> IResult<&[u8], AddressFamily> {
|
||||
map_res(alt((tag("TCP4"), tag("TCP6"))), |af: &[u8]| match af {
|
||||
b"TCP4" => Ok(AddressFamily::Inet),
|
||||
b"TCP6" => Ok(AddressFamily::Inet6),
|
||||
_ => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"invalid address family",
|
||||
)),
|
||||
})
|
||||
.parse(input)
|
||||
}
|
||||
|
||||
/// Parses an IPv4 address from serialized representation (as bytes).
|
||||
fn parse_ipv4(input: &[u8]) -> IResult<&[u8], Ipv4Addr> {
|
||||
map(
|
||||
(
|
||||
parse_number::<u8>,
|
||||
char('.'),
|
||||
parse_number::<u8>,
|
||||
char('.'),
|
||||
parse_number::<u8>,
|
||||
char('.'),
|
||||
parse_number::<u8>,
|
||||
),
|
||||
|(a, _, b, _, c, _, d)| Ipv4Addr::new(a, b, c, d),
|
||||
)
|
||||
.parse(input)
|
||||
}
|
||||
|
||||
/// Parses an IPv4 address from ASCII bytes.
|
||||
pub(super) fn parse(input: &[u8]) -> IResult<&[u8], Header> {
|
||||
map(
|
||||
(
|
||||
tag(SIGNATURE),
|
||||
char(' '),
|
||||
parse_address_family,
|
||||
char(' '),
|
||||
parse_ipv4,
|
||||
char(' '),
|
||||
parse_ipv4,
|
||||
char(' '),
|
||||
parse_number::<u16>,
|
||||
char(' '),
|
||||
parse_number::<u16>,
|
||||
),
|
||||
|(_, _, af, _, src_ip, _, dst_ip, _, src_port, _, dst_port)| Header {
|
||||
af,
|
||||
src: SocketAddr::V4(SocketAddrV4::new(src_ip, src_port)),
|
||||
dst: SocketAddr::V4(SocketAddrV4::new(dst_ip, dst_port)),
|
||||
},
|
||||
)
|
||||
.parse(input)
|
||||
}
|
||||
}
|
304
actix-proxy-protocol/src/v2.rs
Normal file
304
actix-proxy-protocol/src/v2.rs
Normal file
@@ -0,0 +1,304 @@
|
||||
use std::{
|
||||
io,
|
||||
net::{IpAddr, SocketAddr},
|
||||
};
|
||||
|
||||
use smallvec::{SmallVec, ToSmallVec as _};
|
||||
use tokio::io::{AsyncWrite, AsyncWriteExt as _};
|
||||
|
||||
use crate::{
|
||||
tlv::{Crc32c, Tlv},
|
||||
AddressFamily, Command, TransportProtocol, Version,
|
||||
};
|
||||
|
||||
pub const SIGNATURE: [u8; 12] = [
|
||||
0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A,
|
||||
];
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Header {
|
||||
command: Command,
|
||||
transport_protocol: TransportProtocol,
|
||||
address_family: AddressFamily,
|
||||
src: SocketAddr,
|
||||
dst: SocketAddr,
|
||||
tlvs: SmallVec<[(u8, SmallVec<[u8; 16]>); 4]>,
|
||||
}
|
||||
|
||||
impl Header {
|
||||
pub fn new(
|
||||
command: Command,
|
||||
transport_protocol: TransportProtocol,
|
||||
address_family: AddressFamily,
|
||||
src: impl Into<SocketAddr>,
|
||||
dst: impl Into<SocketAddr>,
|
||||
) -> Self {
|
||||
Self {
|
||||
command,
|
||||
transport_protocol,
|
||||
address_family,
|
||||
src: src.into(),
|
||||
dst: dst.into(),
|
||||
tlvs: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_tcp_ipv4_proxy(src: impl Into<SocketAddr>, dst: impl Into<SocketAddr>) -> Self {
|
||||
Self::new(
|
||||
Command::Proxy,
|
||||
TransportProtocol::Stream,
|
||||
AddressFamily::Inet,
|
||||
src,
|
||||
dst,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn add_tlv(&mut self, typ: u8, value: impl AsRef<[u8]>) {
|
||||
self.tlvs.push((typ, SmallVec::from_slice(value.as_ref())));
|
||||
}
|
||||
|
||||
pub fn add_typed_tlv<T: Tlv>(&mut self, tlv: T) {
|
||||
self.add_tlv(T::TYPE, tlv.value_bytes());
|
||||
}
|
||||
|
||||
fn v2_len(&self) -> u16 {
|
||||
let addr_len = if self.src.is_ipv4() {
|
||||
4 + 2 // 4b IPv4 + 2b port number
|
||||
} else {
|
||||
16 + 2 // 16b IPv6 + 2b port number
|
||||
};
|
||||
|
||||
(addr_len * 2)
|
||||
+ self
|
||||
.tlvs
|
||||
.iter()
|
||||
.map(|(_, value)| 1 + 2 + value.len() as u16)
|
||||
.sum::<u16>()
|
||||
}
|
||||
|
||||
pub fn write_to(&self, wrt: &mut impl io::Write) -> io::Result<()> {
|
||||
// PROXY v2 signature
|
||||
wrt.write_all(&SIGNATURE)?;
|
||||
|
||||
// version | command
|
||||
wrt.write_all(&[Version::V2.v2_hi() | self.command.v2_lo()])?;
|
||||
|
||||
// address family | transport protocol
|
||||
wrt.write_all(&[self.address_family.v2_hi() | self.transport_protocol.v2_lo()])?;
|
||||
|
||||
// rest-of-header length
|
||||
wrt.write_all(&self.v2_len().to_be_bytes())?;
|
||||
|
||||
tracing::debug!("proxy rest-of-header len: {}", self.v2_len());
|
||||
|
||||
fn write_ip_bytes_to(wrt: &mut impl io::Write, ip: IpAddr) -> io::Result<()> {
|
||||
match ip {
|
||||
IpAddr::V4(ip) => wrt.write_all(&ip.octets()),
|
||||
IpAddr::V6(ip) => wrt.write_all(&ip.octets()),
|
||||
}
|
||||
}
|
||||
|
||||
// L3 (IP) address
|
||||
write_ip_bytes_to(wrt, self.src.ip())?;
|
||||
write_ip_bytes_to(wrt, self.dst.ip())?;
|
||||
|
||||
// L4 ports
|
||||
wrt.write_all(&self.src.port().to_be_bytes())?;
|
||||
wrt.write_all(&self.dst.port().to_be_bytes())?;
|
||||
|
||||
// TLVs
|
||||
for (typ, value) in &self.tlvs {
|
||||
wrt.write_all(&[*typ])?;
|
||||
wrt.write_all(&(value.len() as u16).to_be_bytes())?;
|
||||
wrt.write_all(value)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn write_to_tokio(&self, wrt: &mut (impl AsyncWrite + Unpin)) -> io::Result<()> {
|
||||
let buf = self.to_vec();
|
||||
wrt.write_all(&buf).await
|
||||
}
|
||||
|
||||
fn to_vec(&self) -> Vec<u8> {
|
||||
// TODO: figure out cap
|
||||
let mut buf = Vec::with_capacity(64);
|
||||
self.write_to(&mut buf).unwrap();
|
||||
buf
|
||||
}
|
||||
|
||||
pub fn has_tlv<T: Tlv>(&self) -> bool {
|
||||
self.tlvs.iter().any(|&(typ, _)| typ == T::TYPE)
|
||||
}
|
||||
|
||||
/// Calculates and adds a crc32c TLV to the PROXY header.
|
||||
///
|
||||
/// Uses method defined in spec.
|
||||
///
|
||||
/// If this is not called last thing it will be wrong.
|
||||
pub fn add_crc23c_checksum(&mut self) {
|
||||
// don't add a checksum if it is already set
|
||||
if self.has_tlv::<Crc32c>() {
|
||||
return;
|
||||
}
|
||||
|
||||
// When the checksum is supported by the sender after constructing the header
|
||||
// the sender MUST:
|
||||
// - initialize the checksum field to '0's.
|
||||
// - calculate the CRC32c checksum of the PROXY header as described in RFC4960,
|
||||
// Appendix B [8].
|
||||
// - put the resultant value into the checksum field, and leave the rest of
|
||||
// the bits unchanged.
|
||||
|
||||
// add zeroed checksum field to TLVs
|
||||
self.add_typed_tlv(Crc32c::default());
|
||||
|
||||
// write PROXY header to buffer
|
||||
let mut buf = Vec::new();
|
||||
self.write_to(&mut buf).unwrap();
|
||||
|
||||
// calculate CRC on buffer and update CRC TLV
|
||||
let crc_calc = crc32fast::hash(&buf);
|
||||
self.tlvs.last_mut().unwrap().1 = crc_calc.to_be_bytes().to_smallvec();
|
||||
|
||||
tracing::debug!("checksum is {}", crc_calc);
|
||||
}
|
||||
|
||||
pub fn validate_crc32c_tlv(&self) -> Option<bool> {
|
||||
// extract crc32c TLV or exit early if none is present
|
||||
let crc_sent = self
|
||||
.tlvs
|
||||
.iter()
|
||||
.filter_map(|(typ, value)| Crc32c::try_from_parts(*typ, value))
|
||||
.next()?;
|
||||
|
||||
// If the checksum is provided as part of the PROXY header and the checksum
|
||||
// functionality is supported by the receiver, the receiver MUST:
|
||||
// - store the received CRC32c checksum value aside.
|
||||
// - replace the 32 bits of the checksum field in the received PROXY header with
|
||||
// all '0's and calculate a CRC32c checksum value of the whole PROXY header.
|
||||
// - verify that the calculated CRC32c checksum is the same as the received
|
||||
// CRC32c checksum. If it is not, the receiver MUST treat the TCP connection
|
||||
// providing the header as invalid.
|
||||
// The default procedure for handling an invalid TCP connection is to abort it.
|
||||
|
||||
let mut this = self.clone();
|
||||
for (typ, value) in this.tlvs.iter_mut() {
|
||||
if Crc32c::try_from_parts(*typ, value).is_some() {
|
||||
value.fill(0);
|
||||
}
|
||||
}
|
||||
|
||||
let mut buf = Vec::new();
|
||||
this.write_to(&mut buf).unwrap();
|
||||
let crc_calc = crc32fast::hash(&buf);
|
||||
|
||||
Some(crc_sent.checksum == crc_calc)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::net::Ipv6Addr;
|
||||
|
||||
use const_str::hex;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn write_v2_no_tlvs() {
|
||||
let mut exp = Vec::new();
|
||||
exp.extend_from_slice(&SIGNATURE); // 0-11
|
||||
exp.extend_from_slice(&[0x21, 0x11]); // 12-13
|
||||
exp.extend_from_slice(&[0x00, 0x0C]); // 14-15
|
||||
exp.extend_from_slice(&[127, 0, 0, 1, 127, 0, 0, 2]); // 16-23
|
||||
exp.extend_from_slice(&[0x04, 0xd2, 0x00, 80]); // 24-27
|
||||
|
||||
let header = Header::new(
|
||||
Command::Proxy,
|
||||
TransportProtocol::Stream,
|
||||
AddressFamily::Inet,
|
||||
SocketAddr::from(([127, 0, 0, 1], 1234)),
|
||||
SocketAddr::from(([127, 0, 0, 2], 80)),
|
||||
);
|
||||
|
||||
assert_eq!(header.v2_len(), 12);
|
||||
assert_eq!(header.to_vec(), exp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_v2_ipv6_tlv_noop() {
|
||||
let mut exp = Vec::new();
|
||||
exp.extend_from_slice(&SIGNATURE); // 0-11
|
||||
exp.extend_from_slice(&[0x20, 0x11]); // 12-13
|
||||
exp.extend_from_slice(&[0x00, 0x28]); // 14-15
|
||||
exp.extend_from_slice(&hex!("00000000000000000000000000000001")); // 16-31
|
||||
exp.extend_from_slice(&hex!("000102030405060708090A0B0C0D0E0F")); // 32-45
|
||||
exp.extend_from_slice(&[0x00, 80, 0xff, 0xff]); // 45-49
|
||||
exp.extend_from_slice(&[0x04, 0x00, 0x01, 0x00]); // 50-53 NOOP TLV
|
||||
|
||||
let mut header = Header::new(
|
||||
Command::Local,
|
||||
TransportProtocol::Stream,
|
||||
AddressFamily::Inet,
|
||||
SocketAddr::from((Ipv6Addr::LOCALHOST, 80)),
|
||||
SocketAddr::from((
|
||||
Ipv6Addr::from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]),
|
||||
65535,
|
||||
)),
|
||||
);
|
||||
|
||||
header.add_tlv(0x04, [0]);
|
||||
|
||||
assert_eq!(header.v2_len(), 36 + 4);
|
||||
assert_eq!(header.to_vec(), exp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_v2_tlv_c2c() {
|
||||
let mut exp = Vec::new();
|
||||
exp.extend_from_slice(&SIGNATURE); // 0-11
|
||||
exp.extend_from_slice(&[0x21, 0x11]); // 12-13
|
||||
exp.extend_from_slice(&[0x00, 0x13]); // 14-15
|
||||
exp.extend_from_slice(&[127, 0, 0, 1, 127, 0, 0, 1]); // 16-23
|
||||
exp.extend_from_slice(&[0x00, 80, 0x00, 80]); // 24-27
|
||||
exp.extend_from_slice(&[0x03, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00]); // 28-35 TLV crc32c
|
||||
|
||||
assert_eq!(
|
||||
crc32fast::hash(&exp),
|
||||
// correct checksum calculated manually
|
||||
u32::from_be_bytes([0x08, 0x70, 0x17, 0x7b]),
|
||||
);
|
||||
|
||||
// re-assign actual checksum to last 4 bytes of expected byte array
|
||||
exp[31..35].copy_from_slice(&[0x08, 0x70, 0x17, 0x7b]);
|
||||
|
||||
let mut header = Header::new(
|
||||
Command::Proxy,
|
||||
TransportProtocol::Stream,
|
||||
AddressFamily::Inet,
|
||||
SocketAddr::from(([127, 0, 0, 1], 80)),
|
||||
SocketAddr::from(([127, 0, 0, 1], 80)),
|
||||
);
|
||||
|
||||
assert!(
|
||||
header.validate_crc32c_tlv().is_none(),
|
||||
"header doesn't have CRC TLV added yet"
|
||||
);
|
||||
|
||||
// add crc32c TLV to header
|
||||
header.add_crc23c_checksum();
|
||||
|
||||
assert_eq!(header.v2_len(), 12 + 7);
|
||||
assert_eq!(header.to_vec(), exp);
|
||||
|
||||
// struct can self-validate checksum
|
||||
assert_eq!(header.validate_crc32c_tlv().unwrap(), true);
|
||||
|
||||
// mangle crc32c TLV and assert that validate now fails
|
||||
*header.tlvs.last_mut().unwrap().1.last_mut().unwrap() = 0x00;
|
||||
assert_eq!(header.validate_crc32c_tlv().unwrap(), false);
|
||||
}
|
||||
}
|
@@ -2,6 +2,12 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.75.
|
||||
|
||||
## 2.10.0
|
||||
|
||||
- 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
|
||||
|
@@ -1,10 +1,7 @@
|
||||
[package]
|
||||
name = "actix-rt"
|
||||
version = "2.9.0"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
]
|
||||
version = "2.10.0"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>", "Rob Ede <robjtede@icloud.com>"]
|
||||
description = "Tokio-based single-threaded async runtime for the Actix ecosystem"
|
||||
keywords = ["async", "futures", "io", "runtime"]
|
||||
homepage = "https://actix.rs"
|
||||
@@ -15,10 +12,7 @@ edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[package.metadata.cargo_check_external_types]
|
||||
allowed_external_types = [
|
||||
"actix_macros::*",
|
||||
"tokio::*",
|
||||
]
|
||||
allowed_external_types = ["actix_macros::*", "tokio::*"]
|
||||
|
||||
[features]
|
||||
default = ["macros"]
|
||||
@@ -29,11 +23,14 @@ io-uring = ["tokio-uring"]
|
||||
actix-macros = { version = "0.2.3", optional = true }
|
||||
|
||||
futures-core = { version = "0.3", default-features = false }
|
||||
tokio = { version = "1.23.1", features = ["rt", "net", "parking_lot", "signal", "sync", "time"] }
|
||||
tokio = { version = "1.44.2", features = ["rt", "net", "parking_lot", "signal", "sync", "time"] }
|
||||
|
||||
# runtime for `io-uring` feature
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
tokio-uring = { version = "0.4", optional = true }
|
||||
tokio-uring = { version = "0.5", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.23.1", features = ["full"] }
|
||||
tokio = { version = "1.44.2", features = ["full"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
@@ -3,11 +3,11 @@
|
||||
> Tokio-based single-threaded async runtime for the Actix ecosystem.
|
||||
|
||||
[](https://crates.io/crates/actix-rt)
|
||||
[](https://docs.rs/actix-rt/2.9.0)
|
||||
[](https://docs.rs/actix-rt/2.10.0)
|
||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
||||

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

|
||||
[](https://discord.gg/WghFtEH6Hb)
|
||||
|
||||
|
@@ -109,13 +109,13 @@ impl Arbiter {
|
||||
#[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();
|
||||
let arb_id = COUNT.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
let name = format!("actix-rt|system:{}|arbiter:{}", system_id, arb_id);
|
||||
let name = format!("actix-rt|system:{system_id}|arbiter:{arb_id}");
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
|
||||
let (ready_tx, ready_rx) = std::sync::mpsc::channel::<()>();
|
||||
|
@@ -41,8 +41,6 @@
|
||||
//! Note that there are currently some unimplemented parts of using `actix-rt` with `io-uring`.
|
||||
//! In particular, when running a `System`, only `System::block_on` is supported.
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible, missing_docs)]
|
||||
#![allow(clippy::type_complexity)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
|
@@ -48,7 +48,7 @@ impl System {
|
||||
/// [tokio-runtime]: tokio::runtime::Runtime
|
||||
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();
|
||||
@@ -87,7 +87,7 @@ impl System {
|
||||
#[doc(hidden)]
|
||||
pub fn with_tokio_rt<F>(_: F) -> SystemRunner
|
||||
where
|
||||
F: Fn() -> tokio::runtime::Runtime,
|
||||
F: FnOnce() -> tokio::runtime::Runtime,
|
||||
{
|
||||
unimplemented!("System::with_tokio_rt is not implemented for io-uring feature yet")
|
||||
}
|
||||
@@ -187,10 +187,7 @@ impl SystemRunner {
|
||||
|
||||
match exit_code {
|
||||
0 => Ok(()),
|
||||
nonzero => Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("Non-zero exit code: {}", nonzero),
|
||||
)),
|
||||
nonzero => Err(io::Error::other(format!("Non-zero exit code: {nonzero}"))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,8 +196,7 @@ impl SystemRunner {
|
||||
let SystemRunner { rt, stop_rx, .. } = self;
|
||||
|
||||
// run loop
|
||||
rt.block_on(stop_rx)
|
||||
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
|
||||
rt.block_on(stop_rx).map_err(io::Error::other)
|
||||
}
|
||||
|
||||
/// Retrieves a reference to the underlying [Actix runtime](crate::Runtime) associated with this
|
||||
|
@@ -1,3 +1,5 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use std::{
|
||||
future::Future,
|
||||
time::{Duration, Instant},
|
||||
@@ -358,7 +360,7 @@ fn tokio_uring_arbiter() {
|
||||
let f = tokio_uring::fs::File::create("test.txt").await.unwrap();
|
||||
let buf = b"Hello World!";
|
||||
|
||||
let (res, _) = f.write_at(&buf[..], 0).await;
|
||||
let (res, _) = f.write_all_at(&buf[..], 0).await;
|
||||
assert!(res.is_ok());
|
||||
|
||||
f.sync_all().await.unwrap();
|
||||
|
@@ -2,6 +2,23 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 2.6.0
|
||||
|
||||
- Add `ServerBuilder::shutdown_signal()` method.
|
||||
- Minimum supported Rust version (MSRV) is now 1.75.
|
||||
|
||||
## 2.5.1
|
||||
|
||||
- Fix panic in test server.
|
||||
- Minimum supported Rust version (MSRV) is now 1.71.
|
||||
|
||||
## 2.5.0
|
||||
|
||||
- Update `mio` dependency to `1`.
|
||||
|
||||
## 2.4.0
|
||||
|
||||
- Update `tokio-uring` dependency to `0.5`.
|
||||
- Minimum supported Rust version (MSRV) is now 1.70.
|
||||
|
||||
## 2.3.0
|
||||
|
33
actix-server/Cargo.toml
Executable file → Normal file
33
actix-server/Cargo.toml
Executable file → Normal file
@@ -1,10 +1,10 @@
|
||||
[package]
|
||||
name = "actix-server"
|
||||
version = "2.3.0"
|
||||
version = "2.6.0"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
"Ali MJ Al-Nasrawy <alimjalnasrawy@gmail.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", "tcp", "server", "framework", "async"]
|
||||
@@ -16,35 +16,38 @@ edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[package.metadata.cargo_check_external_types]
|
||||
allowed_external_types = [
|
||||
"tokio::*",
|
||||
]
|
||||
allowed_external_types = ["tokio::*"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
io-uring = ["tokio-uring", "actix-rt/io-uring"]
|
||||
|
||||
[dependencies]
|
||||
actix-rt = { version = "2.8", default-features = false }
|
||||
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 = "0.8", features = ["os-poll", "net"] }
|
||||
socket2 = "0.5"
|
||||
tokio = { version = "1.23.1", features = ["sync"] }
|
||||
mio = { version = "1", features = ["os-poll", "net"] }
|
||||
socket2 = "0.6"
|
||||
tokio = { version = "1.44.2", features = ["sync"] }
|
||||
tracing = { version = "0.1.30", default-features = false, features = ["log"] }
|
||||
|
||||
# runtime for `io-uring` feature
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
tokio-uring = { version = "0.4", optional = true }
|
||||
tokio-uring = { version = "0.5", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-codec = "0.5"
|
||||
actix-rt = "2.8"
|
||||
|
||||
bytes = "1"
|
||||
pretty_env_logger = "0.5"
|
||||
futures-util = { version = "0.3.17", default-features = false, features = ["sink", "async-await-macro"] }
|
||||
tokio = { version = "1.23.1", features = ["io-util", "rt-multi-thread", "macros", "fs"] }
|
||||
pretty_env_logger = "0.5"
|
||||
static_assertions = "1"
|
||||
tokio = { version = "1.44.2", features = ["io-util", "rt-multi-thread", "macros", "fs", "time"] }
|
||||
tokio-util = "0.7"
|
||||
tracing-subscriber = { version = "0.3", features = ["fmt", "env-filter"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
@@ -5,11 +5,11 @@
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
[](https://crates.io/crates/actix-server)
|
||||
[](https://docs.rs/actix-server/2.3.0)
|
||||
[](https://docs.rs/actix-server/2.6.0)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
||||

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

|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
|
@@ -8,6 +8,8 @@
|
||||
//!
|
||||
//! Follow the prompt and enter a file path, relative or absolute.
|
||||
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use std::io;
|
||||
|
||||
use actix_codec::{Framed, LinesCodec};
|
||||
|
51
actix-server/examples/shutdown-signal.rs
Normal file
51
actix-server/examples/shutdown-signal.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
//! Demonstrates use of the `ServerBuilder::shutdown_signal` method using `tokio-util`s
|
||||
//! `CancellationToken` helper using a nonsensical timer. In practice, this cancellation token would
|
||||
//! be wired throughout your application and typically triggered by OS signals elsewhere.
|
||||
|
||||
use std::{io, time::Duration};
|
||||
|
||||
use actix_rt::net::TcpStream;
|
||||
use actix_server::Server;
|
||||
use actix_service::fn_service;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::level_filters::LevelFilter;
|
||||
use tracing_subscriber::{prelude::*, EnvFilter};
|
||||
|
||||
async fn run(stop_signal: CancellationToken) -> io::Result<()> {
|
||||
tracing_subscriber::registry()
|
||||
.with(tracing_subscriber::fmt::layer())
|
||||
.with(
|
||||
EnvFilter::builder()
|
||||
.with_default_directive(LevelFilter::INFO.into())
|
||||
.from_env_lossy(),
|
||||
)
|
||||
.init();
|
||||
|
||||
let addr = ("127.0.0.1", 8080);
|
||||
tracing::info!("starting server on port: {}", &addr.0);
|
||||
|
||||
Server::build()
|
||||
.bind("shutdown-signal", addr, || {
|
||||
fn_service(|_stream: TcpStream| async { Ok::<_, io::Error>(()) })
|
||||
})?
|
||||
.shutdown_signal(stop_signal.cancelled_owned())
|
||||
.workers(2)
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> io::Result<()> {
|
||||
let stop_signal = CancellationToken::new();
|
||||
|
||||
tokio::spawn({
|
||||
let stop_signal = stop_signal.clone();
|
||||
async move {
|
||||
tokio::time::sleep(Duration::from_secs(10)).await;
|
||||
stop_signal.cancel();
|
||||
}
|
||||
});
|
||||
|
||||
run(stop_signal).await?;
|
||||
Ok(())
|
||||
}
|
@@ -76,7 +76,7 @@ impl Accept {
|
||||
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))?;
|
||||
.map_err(io::Error::other)?;
|
||||
|
||||
Ok((waker_queue, handles_server, accept_handle))
|
||||
}
|
||||
@@ -130,7 +130,7 @@ impl Accept {
|
||||
if let Err(err) = self.poll.poll(&mut events, self.timeout) {
|
||||
match err.kind() {
|
||||
io::ErrorKind::Interrupted => {}
|
||||
_ => panic!("Poll error: {}", err),
|
||||
_ => panic!("Poll error: {err}"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,7 +165,6 @@ impl Accept {
|
||||
// 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)) => {
|
||||
@@ -455,8 +454,8 @@ impl Accept {
|
||||
/// 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
|
||||
fn connection_error(err: &io::Error) -> bool {
|
||||
err.kind() == io::ErrorKind::ConnectionRefused
|
||||
|| err.kind() == io::ErrorKind::ConnectionAborted
|
||||
|| err.kind() == io::ErrorKind::ConnectionReset
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
use std::{io, num::NonZeroUsize, time::Duration};
|
||||
use std::{future::Future, io, num::NonZeroUsize, time::Duration};
|
||||
|
||||
use actix_rt::net::TcpStream;
|
||||
use futures_core::future::BoxFuture;
|
||||
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
|
||||
|
||||
use crate::{
|
||||
@@ -39,6 +40,7 @@ pub struct ServerBuilder {
|
||||
pub(crate) mptcp: MpTcp,
|
||||
pub(crate) exit: bool,
|
||||
pub(crate) listen_os_signals: bool,
|
||||
pub(crate) shutdown_signal: Option<BoxFuture<'static, ()>>,
|
||||
pub(crate) cmd_tx: UnboundedSender<ServerCommand>,
|
||||
pub(crate) cmd_rx: UnboundedReceiver<ServerCommand>,
|
||||
pub(crate) worker_config: ServerWorkerConfig,
|
||||
@@ -64,6 +66,7 @@ impl ServerBuilder {
|
||||
mptcp: MpTcp::Disabled,
|
||||
exit: false,
|
||||
listen_os_signals: true,
|
||||
shutdown_signal: None,
|
||||
cmd_tx,
|
||||
cmd_rx,
|
||||
worker_config: ServerWorkerConfig::default(),
|
||||
@@ -170,6 +173,41 @@ impl ServerBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Specify shutdown signal from a future.
|
||||
///
|
||||
/// Using this method will prevent OS signal handlers being set up.
|
||||
///
|
||||
/// Typically, a `CancellationToken` will be used, but any future _can_ be.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use std::io;
|
||||
/// # use tokio::net::TcpStream;
|
||||
/// # use actix_server::Server;
|
||||
/// # async fn run() -> io::Result<()> {
|
||||
/// use actix_service::fn_service;
|
||||
/// use tokio_util::sync::CancellationToken;
|
||||
///
|
||||
/// let stop_signal = CancellationToken::new();
|
||||
///
|
||||
/// Server::build()
|
||||
/// .bind("shutdown-signal", "127.0.0.1:12345", || {
|
||||
/// fn_service(|_stream: TcpStream| async { Ok::<_, io::Error>(()) })
|
||||
/// })?
|
||||
/// .shutdown_signal(stop_signal.cancelled_owned())
|
||||
/// .run()
|
||||
/// .await
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn shutdown_signal<Fut>(mut self, shutdown_signal: Fut) -> Self
|
||||
where
|
||||
Fut: Future<Output = ()> + Send + 'static,
|
||||
{
|
||||
self.shutdown_signal = Some(Box::pin(shutdown_signal));
|
||||
self
|
||||
}
|
||||
|
||||
/// Timeout for graceful workers shutdown in seconds.
|
||||
///
|
||||
/// After receiving a stop signal, workers have this much time to finish serving requests.
|
||||
@@ -370,9 +408,6 @@ pub(super) fn bind_addr<S: ToSocketAddrs>(
|
||||
} else if let Some(err) = opt_err.take() {
|
||||
Err(err)
|
||||
} else {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Can not bind to address.",
|
||||
))
|
||||
Err(io::Error::other("Can not bind to address."))
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,5 @@
|
||||
//! General purpose TCP server.
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
|
||||
|
@@ -18,7 +18,7 @@ use crate::{
|
||||
builder::ServerBuilder,
|
||||
join_all::join_all,
|
||||
service::InternalServiceFactory,
|
||||
signals::{SignalKind, Signals},
|
||||
signals::{OsSignals, SignalKind, StopSignal},
|
||||
waker_queue::{WakerInterest, WakerQueue},
|
||||
worker::{ServerWorker, ServerWorkerConfig, WorkerHandleServer},
|
||||
ServerHandle,
|
||||
@@ -183,11 +183,6 @@ impl ServerInner {
|
||||
}
|
||||
|
||||
fn run_sync(mut builder: ServerBuilder) -> io::Result<(Self, ServerEventMultiplexer)> {
|
||||
let sockets = mem::take(&mut builder.sockets)
|
||||
.into_iter()
|
||||
.map(|t| (t.0, t.2))
|
||||
.collect();
|
||||
|
||||
// Give log information on what runtime will be used.
|
||||
let is_actix = actix_rt::System::try_current().is_some();
|
||||
let is_tokio = tokio::runtime::Handle::try_current().is_ok();
|
||||
@@ -207,10 +202,20 @@ impl ServerInner {
|
||||
);
|
||||
}
|
||||
|
||||
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),
|
||||
signal_fut: builder.shutdown_signal.map(StopSignal::Cancel).or_else(|| {
|
||||
builder
|
||||
.listen_os_signals
|
||||
.then(OsSignals::new)
|
||||
.map(StopSignal::Os)
|
||||
}),
|
||||
cmd_rx: builder.cmd_rx,
|
||||
};
|
||||
|
||||
@@ -315,7 +320,16 @@ impl ServerInner {
|
||||
|
||||
fn map_signal(signal: SignalKind) -> ServerCommand {
|
||||
match signal {
|
||||
SignalKind::Int => {
|
||||
SignalKind::Cancel => {
|
||||
info!("Cancellation token/channel received; starting graceful shutdown");
|
||||
ServerCommand::Stop {
|
||||
graceful: true,
|
||||
completion: None,
|
||||
force_system_stop: true,
|
||||
}
|
||||
}
|
||||
|
||||
SignalKind::OsInt => {
|
||||
info!("SIGINT received; starting forced shutdown");
|
||||
ServerCommand::Stop {
|
||||
graceful: false,
|
||||
@@ -324,7 +338,7 @@ impl ServerInner {
|
||||
}
|
||||
}
|
||||
|
||||
SignalKind::Term => {
|
||||
SignalKind::OsTerm => {
|
||||
info!("SIGTERM received; starting graceful shutdown");
|
||||
ServerCommand::Stop {
|
||||
graceful: true,
|
||||
@@ -333,7 +347,7 @@ impl ServerInner {
|
||||
}
|
||||
}
|
||||
|
||||
SignalKind::Quit => {
|
||||
SignalKind::OsQuit => {
|
||||
info!("SIGQUIT received; starting forced shutdown");
|
||||
ServerCommand::Stop {
|
||||
graceful: false,
|
||||
@@ -347,7 +361,7 @@ impl ServerInner {
|
||||
|
||||
struct ServerEventMultiplexer {
|
||||
cmd_rx: UnboundedReceiver<ServerCommand>,
|
||||
signal_fut: Option<Signals>,
|
||||
signal_fut: Option<StopSignal>,
|
||||
}
|
||||
|
||||
impl Stream for ServerEventMultiplexer {
|
||||
|
@@ -1,10 +1,11 @@
|
||||
use std::{
|
||||
fmt,
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
pin::{pin, Pin},
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use futures_core::future::BoxFuture;
|
||||
use tracing::trace;
|
||||
|
||||
/// Types of process signals.
|
||||
@@ -12,28 +13,51 @@ use tracing::trace;
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[allow(dead_code)] // variants are never constructed on non-unix
|
||||
pub(crate) enum SignalKind {
|
||||
/// `SIGINT`
|
||||
Int,
|
||||
/// Cancellation token or channel.
|
||||
Cancel,
|
||||
|
||||
/// `SIGTERM`
|
||||
Term,
|
||||
/// OS `SIGINT`.
|
||||
OsInt,
|
||||
|
||||
/// `SIGQUIT`
|
||||
Quit,
|
||||
/// OS `SIGTERM`.
|
||||
OsTerm,
|
||||
|
||||
/// OS `SIGQUIT`.
|
||||
OsQuit,
|
||||
}
|
||||
|
||||
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",
|
||||
SignalKind::Cancel => "Cancellation token or channel",
|
||||
SignalKind::OsInt => "SIGINT",
|
||||
SignalKind::OsTerm => "SIGTERM",
|
||||
SignalKind::OsQuit => "SIGQUIT",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum StopSignal {
|
||||
/// OS signal handling is configured.
|
||||
Os(OsSignals),
|
||||
|
||||
/// Cancellation token or channel.
|
||||
Cancel(BoxFuture<'static, ()>),
|
||||
}
|
||||
|
||||
impl Future for StopSignal {
|
||||
type Output = SignalKind;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
match self.get_mut() {
|
||||
StopSignal::Os(os_signals) => pin!(os_signals).poll(cx),
|
||||
StopSignal::Cancel(cancel) => pin!(cancel).poll(cx).map(|()| SignalKind::Cancel),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Process signal listener.
|
||||
pub(crate) struct Signals {
|
||||
pub(crate) struct OsSignals {
|
||||
#[cfg(not(unix))]
|
||||
signals: futures_core::future::BoxFuture<'static, std::io::Result<()>>,
|
||||
|
||||
@@ -41,14 +65,14 @@ pub(crate) struct Signals {
|
||||
signals: Vec<(SignalKind, actix_rt::signal::unix::Signal)>,
|
||||
}
|
||||
|
||||
impl Signals {
|
||||
impl OsSignals {
|
||||
/// Constructs an OS signal listening future.
|
||||
pub(crate) fn new() -> Self {
|
||||
trace!("setting up OS signal listener");
|
||||
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
Signals {
|
||||
OsSignals {
|
||||
signals: Box::pin(actix_rt::signal::ctrl_c()),
|
||||
}
|
||||
}
|
||||
@@ -58,9 +82,9 @@ impl Signals {
|
||||
use actix_rt::signal::unix;
|
||||
|
||||
let sig_map = [
|
||||
(unix::SignalKind::interrupt(), SignalKind::Int),
|
||||
(unix::SignalKind::terminate(), SignalKind::Term),
|
||||
(unix::SignalKind::quit(), SignalKind::Quit),
|
||||
(unix::SignalKind::interrupt(), SignalKind::OsInt),
|
||||
(unix::SignalKind::terminate(), SignalKind::OsTerm),
|
||||
(unix::SignalKind::quit(), SignalKind::OsQuit),
|
||||
];
|
||||
|
||||
let signals = sig_map
|
||||
@@ -68,29 +92,27 @@ impl Signals {
|
||||
.filter_map(|(kind, sig)| {
|
||||
unix::signal(*kind)
|
||||
.map(|tokio_sig| (*sig, tokio_sig))
|
||||
.map_err(|e| {
|
||||
.map_err(|err| {
|
||||
tracing::error!(
|
||||
"can not initialize stream handler for {:?} err: {}",
|
||||
sig,
|
||||
e
|
||||
"can not initialize stream handler for {sig:?} err: {err}",
|
||||
)
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Signals { signals }
|
||||
OsSignals { signals }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for Signals {
|
||||
impl Future for OsSignals {
|
||||
type Output = SignalKind;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
self.signals.as_mut().poll(cx).map(|_| SignalKind::Int)
|
||||
self.signals.as_mut().poll(cx).map(|_| SignalKind::OsInt)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
@@ -106,3 +128,10 @@ impl Future for Signals {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
static_assertions::assert_impl_all!(StopSignal: Send, Unpin);
|
||||
}
|
||||
|
@@ -105,9 +105,9 @@ impl From<StdUnixListener> for MioListener {
|
||||
impl fmt::Debug for MioListener {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
MioListener::Tcp(ref lst) => write!(f, "{:?}", lst),
|
||||
MioListener::Tcp(ref lst) => write!(f, "{lst:?}"),
|
||||
#[cfg(unix)]
|
||||
MioListener::Uds(ref lst) => write!(f, "{:?}", lst),
|
||||
MioListener::Uds(ref lst) => write!(f, "{lst:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,9 +115,9 @@ 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),
|
||||
MioListener::Tcp(ref lst) => write!(f, "{lst:?}"),
|
||||
#[cfg(unix)]
|
||||
MioListener::Uds(ref lst) => write!(f, "{:?}", lst),
|
||||
MioListener::Uds(ref lst) => write!(f, "{lst:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,16 +126,16 @@ pub(crate) enum SocketAddr {
|
||||
Unknown,
|
||||
Tcp(StdSocketAddr),
|
||||
#[cfg(unix)]
|
||||
Uds(mio::net::SocketAddr),
|
||||
Uds(std::os::unix::net::SocketAddr),
|
||||
}
|
||||
|
||||
impl fmt::Display for SocketAddr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
Self::Unknown => write!(f, "Unknown SocketAddr"),
|
||||
Self::Tcp(ref addr) => write!(f, "{}", addr),
|
||||
Self::Tcp(ref addr) => write!(f, "{addr}"),
|
||||
#[cfg(unix)]
|
||||
Self::Uds(ref addr) => write!(f, "{:?}", addr),
|
||||
Self::Uds(ref addr) => write!(f, "{addr:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,9 +144,9 @@ impl fmt::Debug for SocketAddr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
Self::Unknown => write!(f, "Unknown SocketAddr"),
|
||||
Self::Tcp(ref addr) => write!(f, "{:?}", addr),
|
||||
Self::Tcp(ref addr) => write!(f, "{addr:?}"),
|
||||
#[cfg(unix)]
|
||||
Self::Uds(ref addr) => write!(f, "{:?}", addr),
|
||||
Self::Uds(ref addr) => write!(f, "{addr:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,6 +160,7 @@ pub enum MioStream {
|
||||
|
||||
/// 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>;
|
||||
}
|
||||
|
||||
@@ -265,14 +266,14 @@ mod tests {
|
||||
#[test]
|
||||
fn socket_addr() {
|
||||
let addr = SocketAddr::Tcp("127.0.0.1:8080".parse().unwrap());
|
||||
assert!(format!("{:?}", addr).contains("127.0.0.1:8080"));
|
||||
assert_eq!(format!("{}", addr), "127.0.0.1:8080");
|
||||
assert!(format!("{addr:?}").contains("127.0.0.1:8080"));
|
||||
assert_eq!(format!("{addr}"), "127.0.0.1:8080");
|
||||
|
||||
let addr: StdSocketAddr = "127.0.0.1:0".parse().unwrap();
|
||||
let lst = create_mio_tcp_listener(addr, 128, &MpTcp::Disabled).unwrap();
|
||||
let lst = MioListener::Tcp(lst);
|
||||
assert!(format!("{:?}", lst).contains("TcpListener"));
|
||||
assert!(format!("{}", lst).contains("127.0.0.1"));
|
||||
assert!(format!("{lst:?}").contains("TcpListener"));
|
||||
assert!(format!("{lst}").contains("127.0.0.1"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -282,12 +283,12 @@ mod tests {
|
||||
if let Ok(socket) = MioUnixListener::bind("/tmp/sock.xxxxx") {
|
||||
let addr = socket.local_addr().expect("Couldn't get local address");
|
||||
let a = SocketAddr::Uds(addr);
|
||||
assert!(format!("{:?}", a).contains("/tmp/sock.xxxxx"));
|
||||
assert!(format!("{}", a).contains("/tmp/sock.xxxxx"));
|
||||
assert!(format!("{a:?}").contains("/tmp/sock.xxxxx"));
|
||||
assert!(format!("{a}").contains("/tmp/sock.xxxxx"));
|
||||
|
||||
let lst = MioListener::Uds(socket);
|
||||
assert!(format!("{:?}", lst).contains("/tmp/sock.xxxxx"));
|
||||
assert!(format!("{}", lst).contains("/tmp/sock.xxxxx"));
|
||||
assert!(format!("{lst:?}").contains("/tmp/sock.xxxxx"));
|
||||
assert!(format!("{lst}").contains("/tmp/sock.xxxxx"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -123,7 +123,9 @@ impl TestServerHandle {
|
||||
|
||||
/// Connect to server, returning a Tokio `TcpStream`.
|
||||
pub fn connect(&self) -> io::Result<TcpStream> {
|
||||
TcpStream::from_std(net::TcpStream::connect(self.addr)?)
|
||||
let stream = net::TcpStream::connect(self.addr)?;
|
||||
stream.set_nonblocking(true)?;
|
||||
TcpStream::from_std(stream)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -52,7 +52,7 @@ impl WakerQueue {
|
||||
|
||||
waker
|
||||
.wake()
|
||||
.unwrap_or_else(|e| panic!("can not wake up Accept Poll: {}", e));
|
||||
.unwrap_or_else(|err| panic!("can not wake up Accept Poll: {err}"));
|
||||
}
|
||||
|
||||
/// Get a MutexGuard of the waker queue.
|
||||
@@ -62,7 +62,7 @@ impl WakerQueue {
|
||||
|
||||
/// 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);
|
||||
*queue = VecDeque::<WakerInterest>::with_capacity(16);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -325,7 +325,7 @@ impl ServerWorker {
|
||||
// no actix system
|
||||
(None, Some(rt_handle)) => {
|
||||
std::thread::Builder::new()
|
||||
.name(format!("actix-server worker {}", idx))
|
||||
.name(format!("actix-server worker {idx}"))
|
||||
.spawn(move || {
|
||||
let (worker_stopped_tx, worker_stopped_rx) = oneshot::channel();
|
||||
|
||||
@@ -341,11 +341,10 @@ impl ServerWorker {
|
||||
Ok((token, svc)) => services.push((idx, token, svc)),
|
||||
|
||||
Err(err) => {
|
||||
error!("can not start worker: {:?}", err);
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("can not start server service {}", idx),
|
||||
));
|
||||
error!("can not start worker: {err:?}");
|
||||
return Err(io::Error::other(format!(
|
||||
"can not start server service {idx}",
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -440,13 +439,12 @@ impl ServerWorker {
|
||||
Ok((token, svc)) => services.push((idx, token, svc)),
|
||||
|
||||
Err(err) => {
|
||||
error!("can not start worker: {:?}", err);
|
||||
error!("can not start worker: {err:?}");
|
||||
Arbiter::current().stop();
|
||||
factory_tx
|
||||
.send(Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("can not start server service {}", idx),
|
||||
)))
|
||||
.send(Err(io::Error::other(format!(
|
||||
"can not start server service {idx}",
|
||||
))))
|
||||
.unwrap();
|
||||
return;
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
#![allow(clippy::let_underscore_future)]
|
||||
#![allow(clippy::let_underscore_future, missing_docs)]
|
||||
|
||||
use std::{
|
||||
net,
|
||||
|
@@ -1,3 +1,5 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use std::net;
|
||||
|
||||
use actix_rt::net::TcpStream;
|
||||
@@ -69,5 +71,7 @@ async fn new_with_builder() {
|
||||
srv.connect().unwrap();
|
||||
|
||||
// connect to alt service defined in custom ServerBuilder
|
||||
TcpStream::from_std(net::TcpStream::connect(alt_addr).unwrap()).unwrap();
|
||||
let stream = net::TcpStream::connect(alt_addr).unwrap();
|
||||
stream.set_nonblocking(true).unwrap();
|
||||
TcpStream::from_std(stream).unwrap();
|
||||
}
|
||||
|
@@ -2,7 +2,11 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.70.
|
||||
- Minimum supported Rust version (MSRV) is now 1.75.
|
||||
|
||||
## 2.0.3
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.71.
|
||||
|
||||
## 2.0.2
|
||||
|
||||
|
@@ -1,10 +1,7 @@
|
||||
[package]
|
||||
name = "actix-service"
|
||||
version = "2.0.2"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
]
|
||||
version = "2.0.3"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>", "Rob Ede <robjtede@icloud.com>"]
|
||||
description = "Service trait and combinators for representing asynchronous request/response operations."
|
||||
keywords = ["network", "framework", "async", "futures", "service"]
|
||||
categories = ["network-programming", "asynchronous", "no-std"]
|
||||
@@ -15,10 +12,12 @@ rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
futures-core = { version = "0.3.17", default-features = false }
|
||||
paste = "1"
|
||||
pin-project-lite = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "2"
|
||||
actix-utils = "3"
|
||||
futures-util = { version = "0.3.17", default-features = false }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
@@ -3,10 +3,10 @@
|
||||
> Service trait and combinators for representing asynchronous request/response operations.
|
||||
|
||||
[](https://crates.io/crates/actix-service)
|
||||
[](https://docs.rs/actix-service/2.0.2)
|
||||
[](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.2)
|
||||
[](https://deps.rs/crate/actix-service/2.0.3)
|
||||

|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
|
@@ -1,3 +1,5 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use std::{future::Future, sync::mpsc, time::Duration};
|
||||
|
||||
async fn oracle<F, Fut>(f: F) -> (u32, u32)
|
||||
|
@@ -3,36 +3,38 @@
|
||||
use alloc::{boxed::Box, rc::Rc};
|
||||
use core::{future::Future, pin::Pin};
|
||||
|
||||
use paste::paste;
|
||||
|
||||
use crate::{Service, ServiceFactory};
|
||||
|
||||
/// A boxed future with no send bound or lifetime parameters.
|
||||
pub type BoxFuture<T> = Pin<Box<dyn Future<Output = T>>>;
|
||||
|
||||
macro_rules! service_object {
|
||||
($name: ident, $type: tt, $fn_name: ident) => {
|
||||
paste! {
|
||||
#[doc = "Type alias for service trait object using `" $type "`."]
|
||||
pub type $name<Req, Res, Err> = $type<
|
||||
dyn Service<Req, Response = Res, Error = Err, Future = BoxFuture<Result<Res, Err>>>,
|
||||
>;
|
||||
/// Type alias for service trait object using [`Box`].
|
||||
pub type BoxService<Req, Res, Err> =
|
||||
Box<dyn Service<Req, Response = Res, Error = Err, Future = BoxFuture<Result<Res, Err>>>>;
|
||||
|
||||
#[doc = "Wraps service as a trait object using [`" $name "`]."]
|
||||
pub fn $fn_name<S, Req>(service: S) -> $name<Req, S::Response, S::Error>
|
||||
where
|
||||
S: Service<Req> + 'static,
|
||||
Req: 'static,
|
||||
S::Future: 'static,
|
||||
{
|
||||
$type::new(ServiceWrapper::new(service))
|
||||
}
|
||||
}
|
||||
};
|
||||
/// Wraps service as a trait object using [`BoxService`].
|
||||
pub fn service<S, Req>(service: S) -> BoxService<Req, S::Response, S::Error>
|
||||
where
|
||||
S: Service<Req> + 'static,
|
||||
Req: 'static,
|
||||
S::Future: 'static,
|
||||
{
|
||||
Box::new(ServiceWrapper::new(service))
|
||||
}
|
||||
|
||||
service_object!(BoxService, Box, service);
|
||||
service_object!(RcService, Rc, rc_service);
|
||||
/// Type alias for service trait object using [`Rc`].
|
||||
pub type RcService<Req, Res, Err> =
|
||||
Rc<dyn Service<Req, Response = Res, Error = Err, Future = BoxFuture<Result<Res, Err>>>>;
|
||||
|
||||
/// Wraps service as a trait object using [`RcService`].
|
||||
pub fn rc_service<S, Req>(service: S) -> RcService<Req, S::Response, S::Error>
|
||||
where
|
||||
S: Service<Req> + 'static,
|
||||
Req: 'static,
|
||||
S::Future: 'static,
|
||||
{
|
||||
Rc::new(ServiceWrapper::new(service))
|
||||
}
|
||||
|
||||
struct ServiceWrapper<S> {
|
||||
inner: S,
|
||||
|
@@ -44,7 +44,7 @@ pub trait ServiceExt<Req>: Service<Req> {
|
||||
/// Call another service after call to this one has resolved successfully.
|
||||
///
|
||||
/// This function can be used to chain two services together and ensure that the second service
|
||||
/// isn't called until call to the fist service have finished. Result of the call to the first
|
||||
/// isn't called until call to the first service have finished. Result of the call to the first
|
||||
/// service is used as an input parameter for the second service's call.
|
||||
///
|
||||
/// Note that this function consumes the receiving service and returns a wrapped version of it.
|
||||
|
@@ -1,8 +1,6 @@
|
||||
//! See [`Service`] docs for information on this crate's foundational trait.
|
||||
|
||||
#![no_std]
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible, missing_docs)]
|
||||
#![allow(clippy::type_complexity)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
|
@@ -52,7 +52,7 @@ where
|
||||
/// Call another service after call to this one has resolved successfully.
|
||||
///
|
||||
/// This function can be used to chain two services together and ensure that
|
||||
/// the second service isn't called until call to the fist service have
|
||||
/// the second service isn't called until call to the first service have
|
||||
/// finished. Result of the call to the first service is used as an
|
||||
/// input parameter for the second service's call.
|
||||
///
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//! When MSRV is 1.48, replace with `core::future::Ready` and `core::future::ready()`.
|
||||
//! When MSRV is 1.82, replace with `core::future::Ready` and `core::future::ready()`.
|
||||
|
||||
use core::{
|
||||
future::Future,
|
||||
|
@@ -228,6 +228,7 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
// pseudo-doctest for Transform trait
|
||||
#[allow(unused)]
|
||||
pub struct TimeoutTransform {
|
||||
timeout: Duration,
|
||||
}
|
||||
@@ -249,6 +250,7 @@ mod tests {
|
||||
}
|
||||
|
||||
// pseudo-doctest for Transform trait
|
||||
#[allow(unused)]
|
||||
pub struct Timeout<S> {
|
||||
service: S,
|
||||
_timeout: Duration,
|
||||
|
@@ -2,6 +2,8 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.75.
|
||||
|
||||
## 3.4.0
|
||||
|
||||
- Add `rustls-0_23`, `rustls-0_23-webpki-roots`, and `rustls-0_23-native-roots` crate features.
|
||||
|
@@ -1,10 +1,7 @@
|
||||
[package]
|
||||
name = "actix-tls"
|
||||
version = "3.4.0"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
]
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>", "Rob Ede <robjtede@icloud.com>"]
|
||||
description = "TLS acceptor and connector services for Actix ecosystem"
|
||||
keywords = ["network", "tls", "ssl", "async", "transport"]
|
||||
repository = "https://github.com/actix/actix-net.git"
|
||||
@@ -18,17 +15,12 @@ all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[package.metadata.cargo_check_external_types]
|
||||
allowed_external_types = [
|
||||
"actix_service::*",
|
||||
"actix_utils::*",
|
||||
"futures_core::*",
|
||||
"tokio::*",
|
||||
]
|
||||
allowed_external_types = ["actix_service::*", "actix_utils::*", "futures_core::*", "tokio::*"]
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = [
|
||||
"rustls_021", # specified to force version with add_trust_anchors method
|
||||
"rustls_webpki_0101", # specified to force secure version
|
||||
"rustls_021", # specified to force version with add_trust_anchors method
|
||||
"rustls_webpki_0101", # specified to force secure version
|
||||
]
|
||||
|
||||
[features]
|
||||
@@ -76,11 +68,10 @@ uri = ["dep:http-0_2", "dep:http-1"]
|
||||
actix-rt = { version = "2.2", default-features = false }
|
||||
actix-service = "2"
|
||||
actix-utils = "3"
|
||||
|
||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||
impl-more = "0.1"
|
||||
pin-project-lite = "0.2.7"
|
||||
tokio = "1.23.1"
|
||||
tokio = "1.44.2"
|
||||
tokio-util = "0.7"
|
||||
tracing = { version = "0.1.30", default-features = false, features = ["log"] }
|
||||
|
||||
@@ -120,7 +111,7 @@ rustls-native-certs-07 = { package = "rustls-native-certs", version = "0.7", opt
|
||||
tokio-native-tls = { version = "0.3", optional = true }
|
||||
|
||||
[target.'cfg(any())'.dependencies]
|
||||
rustls-021 = { package = "rustls", version = "0.21.6", optional = true } # force version with add_trust_anchors method
|
||||
rustls-021 = { package = "rustls", version = "0.21.6", optional = true } # force version with add_trust_anchors method
|
||||
rustls-webpki-0101 = { package = "rustls-webpki", version = "0.101.4", optional = true } # force secure version
|
||||
|
||||
[dev-dependencies]
|
||||
@@ -128,10 +119,10 @@ actix-codec = "0.5"
|
||||
actix-rt = "2.2"
|
||||
actix-server = "2"
|
||||
bytes = "1"
|
||||
pretty_env_logger = "0.5"
|
||||
futures-util = { version = "0.3.17", default-features = false, features = ["sink"] }
|
||||
itertools = "0.12"
|
||||
rcgen = "0.12"
|
||||
itertools = "0.14"
|
||||
pretty_env_logger = "0.5"
|
||||
rcgen = "0.13"
|
||||
rustls-pemfile = "2"
|
||||
tokio-rustls-026 = { package = "tokio-rustls", version = "0.26" }
|
||||
trust-dns-resolver = "0.23"
|
||||
@@ -139,3 +130,6 @@ trust-dns-resolver = "0.23"
|
||||
[[example]]
|
||||
name = "accept-rustls"
|
||||
required-features = ["accept", "rustls-0_23"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
@@ -80,7 +80,7 @@ async fn main() -> io::Result<()> {
|
||||
// Set up TLS service factory
|
||||
tls_acceptor
|
||||
.clone()
|
||||
.map_err(|err| println!("Rustls error: {:?}", err))
|
||||
.map_err(|err| println!("Rustls error: {err:?}"))
|
||||
.and_then(move |stream: TlsStream<TcpStream>| {
|
||||
let num = count.fetch_add(1, Ordering::Relaxed);
|
||||
info!("[{}] Got TLS connection: {:?}", num, &*stream);
|
||||
|
@@ -81,9 +81,9 @@ where
|
||||
trace!("TLS handshake success: {:?}", stream.hostname());
|
||||
stream.replace_io(res).1
|
||||
})
|
||||
.map_err(|e| {
|
||||
trace!("TLS handshake error: {:?}", e);
|
||||
io::Error::new(io::ErrorKind::Other, format!("{}", e))
|
||||
.map_err(|err| {
|
||||
trace!("TLS handshake error: {err:?}");
|
||||
io::Error::other(format!("{err}"))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@@ -141,10 +141,7 @@ where
|
||||
}
|
||||
Err(err) => {
|
||||
trace!("TLS handshake error: {:?}", err);
|
||||
Poll::Ready(Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("{}", err),
|
||||
)))
|
||||
Poll::Ready(Err(io::Error::other(format!("{err}"))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -159,8 +159,7 @@ where
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
match self.get_mut() {
|
||||
Self::InvalidDns => Poll::Ready(Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
Self::InvalidDns => Poll::Ready(Err(io::Error::other(
|
||||
"Rustls v0.20 can only handle hostname-based connections. Enable the `rustls-0_21` \
|
||||
feature and use the Rustls v0.21 utilities to gain this feature.",
|
||||
))),
|
||||
|
@@ -1,7 +1,5 @@
|
||||
//! TLS acceptor and connector services for the Actix ecosystem.
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible, missing_docs)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
|
@@ -21,12 +21,12 @@ use rustls_pki_types_1::ServerName;
|
||||
use tokio_rustls_026::rustls::RootCertStore;
|
||||
|
||||
fn new_cert_and_key() -> (String, String) {
|
||||
let cert =
|
||||
let rcgen::CertifiedKey { cert, key_pair } =
|
||||
rcgen::generate_simple_self_signed(vec!["127.0.0.1".to_owned(), "localhost".to_owned()])
|
||||
.unwrap();
|
||||
|
||||
let key = cert.serialize_private_key_pem();
|
||||
let cert = cert.serialize_pem().unwrap();
|
||||
let key = key_pair.serialize_pem();
|
||||
let cert = cert.pem();
|
||||
|
||||
(cert, key)
|
||||
}
|
||||
@@ -126,7 +126,7 @@ async fn accepts_connections() {
|
||||
let tls_acceptor = Acceptor::new(openssl_acceptor);
|
||||
|
||||
tls_acceptor
|
||||
.map_err(|err| println!("OpenSSL error: {:?}", err))
|
||||
.map_err(|err| println!("OpenSSL error: {err:?}"))
|
||||
.and_then(move |_stream: TlsStream<TcpStream>| ok(()))
|
||||
}
|
||||
});
|
||||
|
@@ -24,12 +24,12 @@ use rustls_pki_types_1::PrivateKeyDer;
|
||||
use tls_openssl::ssl::SslVerifyMode;
|
||||
|
||||
fn new_cert_and_key() -> (String, String) {
|
||||
let cert =
|
||||
let rcgen::CertifiedKey { cert, key_pair } =
|
||||
rcgen::generate_simple_self_signed(vec!["127.0.0.1".to_owned(), "localhost".to_owned()])
|
||||
.unwrap();
|
||||
|
||||
let key = cert.serialize_private_key_pem();
|
||||
let cert = cert.serialize_pem().unwrap();
|
||||
let key = key_pair.serialize_pem();
|
||||
let cert = cert.pem();
|
||||
|
||||
(cert, key)
|
||||
}
|
||||
@@ -87,7 +87,7 @@ async fn accepts_connections() {
|
||||
let tls_acceptor = Acceptor::new(rustls_server_config(cert.clone(), key.clone()));
|
||||
|
||||
tls_acceptor
|
||||
.map_err(|err| println!("Rustls error: {:?}", err))
|
||||
.map_err(|err| println!("Rustls error: {err:?}"))
|
||||
.and_then(move |_stream: TlsStream<TcpStream>| ok(()))
|
||||
}
|
||||
});
|
||||
|
@@ -1,3 +1,4 @@
|
||||
#![allow(missing_docs)]
|
||||
#![cfg(feature = "connect")]
|
||||
|
||||
use std::{
|
||||
|
@@ -1,3 +1,4 @@
|
||||
#![allow(missing_docs)]
|
||||
#![cfg(feature = "connect")]
|
||||
|
||||
use std::{
|
||||
@@ -25,7 +26,7 @@ async fn custom_resolver() {
|
||||
port: u16,
|
||||
) -> LocalBoxFuture<'a, Result<Vec<SocketAddr>, Box<dyn std::error::Error>>> {
|
||||
Box::pin(async move {
|
||||
let local = format!("127.0.0.1:{}", port).parse().unwrap();
|
||||
let local = format!("127.0.0.1:{port}").parse().unwrap();
|
||||
Ok(vec![local])
|
||||
})
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.70.
|
||||
- Minimum supported Rust version (MSRV) is now 1.75.
|
||||
|
||||
## 0.1.0
|
||||
|
||||
|
@@ -13,20 +13,17 @@ edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[package.metadata.cargo_check_external_types]
|
||||
allowed_external_types = [
|
||||
"actix_service::*",
|
||||
"actix_utils::*",
|
||||
"tracing::*",
|
||||
"tracing_futures::*",
|
||||
]
|
||||
allowed_external_types = ["actix_service::*", "actix_utils::*", "tracing::*", "tracing_futures::*"]
|
||||
|
||||
[dependencies]
|
||||
actix-service = "2"
|
||||
actix-utils = "3"
|
||||
|
||||
tracing = "0.1.35"
|
||||
tracing-futures = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "2"
|
||||
slab = "0.4"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
@@ -1,7 +1,5 @@
|
||||
//! Actix tracing - support for tokio tracing with Actix services.
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
|
||||
@@ -22,6 +20,7 @@ pub struct TracingService<S, F> {
|
||||
}
|
||||
|
||||
impl<S, F> TracingService<S, F> {
|
||||
/// Constructs new tracing middleware.
|
||||
pub fn new(inner: S, make_span: F) -> Self {
|
||||
TracingService { inner, make_span }
|
||||
}
|
||||
@@ -63,6 +62,7 @@ pub struct TracingTransform<S, U, F> {
|
||||
}
|
||||
|
||||
impl<S, U, F> TracingTransform<S, U, F> {
|
||||
/// Constructs new tracing middleware.
|
||||
pub fn new(make_span: F) -> Self {
|
||||
TracingTransform {
|
||||
make_span,
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.70.
|
||||
- Minimum supported Rust version (MSRV) is now 1.75.
|
||||
|
||||
## 3.0.1
|
||||
|
||||
|
@@ -1,10 +1,7 @@
|
||||
[package]
|
||||
name = "actix-utils"
|
||||
version = "3.0.1"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
]
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>", "Rob Ede <robjtede@icloud.com>"]
|
||||
description = "Various utilities used in the Actix ecosystem"
|
||||
keywords = ["network", "framework", "async", "futures"]
|
||||
categories = ["network-programming", "asynchronous"]
|
||||
@@ -14,10 +11,13 @@ edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
pin-project-lite = "0.2"
|
||||
local-waker = "0.1"
|
||||
pin-project-lite = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "2"
|
||||
futures-util = { version = "0.3.17", default-features = false }
|
||||
static_assertions = "1.1"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
@@ -1,7 +1,5 @@
|
||||
//! Various utilities used in the Actix ecosystem.
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible, missing_docs)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
|
||||
|
@@ -2,7 +2,12 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.70.
|
||||
- Minimum supported Rust version (MSRV) is now 1.75.
|
||||
|
||||
## 1.4.0
|
||||
|
||||
- Add `ByteString::split_at()` method.
|
||||
- Minimum supported Rust version (MSRV) is now 1.71.
|
||||
|
||||
## 1.3.1
|
||||
|
||||
|
@@ -1,12 +1,9 @@
|
||||
[package]
|
||||
name = "bytestring"
|
||||
version = "1.3.1"
|
||||
version = "1.4.0"
|
||||
description = "A UTF-8 encoded read-only string using `Bytes` as storage"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
]
|
||||
keywords = ["string", "bytes", "utf8", "web", "actix"]
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>", "Rob Ede <robjtede@icloud.com>"]
|
||||
keywords = ["string", "bytes", "utf8", "web", "bytestring"]
|
||||
categories = ["no-std", "web-programming"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-net"
|
||||
@@ -15,16 +12,16 @@ edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[package.metadata.cargo_check_external_types]
|
||||
allowed_external_types = [
|
||||
"bytes::*",
|
||||
"serde::*",
|
||||
]
|
||||
allowed_external_types = ["bytes::*", "serde::*"]
|
||||
|
||||
[dependencies]
|
||||
bytes = { version = "1.2", default-features = false }
|
||||
serde = { version = "1.0", optional = true }
|
||||
serde = { version = "1", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
ahash = { version = "0.8", default-features = false }
|
||||
serde_json = "1.0"
|
||||
serde_json = "1"
|
||||
static_assertions = "1.1"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
@@ -5,11 +5,11 @@
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
[](https://crates.io/crates/bytestring)
|
||||
[](https://docs.rs/bytestring/1.3.1)
|
||||
[](https://docs.rs/bytestring/1.4.0)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/bytestring/1.3.1)
|
||||
[](https://deps.rs/crate/bytestring/1.4.0)
|
||||

|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
|
@@ -3,8 +3,6 @@
|
||||
//! See docs for [`ByteString`].
|
||||
|
||||
#![no_std]
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible, missing_docs)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
@@ -17,7 +15,7 @@ use core::{borrow::Borrow, fmt, hash, ops, str};
|
||||
|
||||
use bytes::Bytes;
|
||||
|
||||
/// An immutable UTF-8 encoded string with [`Bytes`] as a storage.
|
||||
/// An immutable UTF-8 encoded string using [`Bytes`] as the storage.
|
||||
#[derive(Clone, Default, Eq, PartialOrd, Ord)]
|
||||
pub struct ByteString(Bytes);
|
||||
|
||||
@@ -53,7 +51,29 @@ impl ByteString {
|
||||
Self(src)
|
||||
}
|
||||
|
||||
/// Returns a new byte string that is equivalent to the given `subset`.
|
||||
/// Divides one bytestring into two at an index, returning both parts.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `mid` is not on a UTF-8 code point boundary, or if it is past the end of the last
|
||||
/// code point of the bytestring.
|
||||
pub fn split_at(&self, mid: usize) -> (ByteString, ByteString) {
|
||||
let this: &str = self.as_ref();
|
||||
let _valid_midpoint_check = this.split_at(mid);
|
||||
|
||||
let mut bytes = self.0.clone();
|
||||
let first = bytes.split_to(mid);
|
||||
let last = bytes;
|
||||
|
||||
unsafe {
|
||||
(
|
||||
ByteString::from_bytes_unchecked(first),
|
||||
ByteString::from_bytes_unchecked(last),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new `ByteString` that is equivalent to the given `subset`.
|
||||
///
|
||||
/// When processing a `ByteString` buffer with other tools, one often gets a `&str` which is in
|
||||
/// fact a slice of the original `ByteString`; i.e., a subset of it. This function turns that
|
||||
@@ -465,4 +485,33 @@ mod test {
|
||||
// being a logical subset of the string
|
||||
ByteString::from_static("foo bar").slice_ref("foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn split_at() {
|
||||
let buf = ByteString::from_static("foo bar");
|
||||
|
||||
let (first, last) = buf.split_at(0);
|
||||
assert_eq!(ByteString::from_static(""), first);
|
||||
assert_eq!(ByteString::from_static("foo bar"), last);
|
||||
|
||||
let (first, last) = buf.split_at(4);
|
||||
assert_eq!(ByteString::from_static("foo "), first);
|
||||
assert_eq!(ByteString::from_static("bar"), last);
|
||||
|
||||
let (first, last) = buf.split_at(7);
|
||||
assert_eq!(ByteString::from_static("foo bar"), first);
|
||||
assert_eq!(ByteString::from_static(""), last);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "byte index 1 is not a char boundary;"]
|
||||
fn split_at_invalid_code_point() {
|
||||
ByteString::from_static("µ").split_at(1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "byte index 9 is out of bounds"]
|
||||
fn split_at_outside_string() {
|
||||
ByteString::from_static("foo").split_at(9);
|
||||
}
|
||||
}
|
||||
|
@@ -9,7 +9,7 @@ coverage:
|
||||
default:
|
||||
threshold: 10% # make CI green
|
||||
|
||||
ignore: # ignore codecoverage on following paths
|
||||
ignore: # ignore code coverage on following paths
|
||||
- "examples"
|
||||
- ".github"
|
||||
- "**/*.md"
|
||||
|
58
flake.lock
generated
Normal file
58
flake.lock
generated
Normal file
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-parts": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1715865404,
|
||||
"narHash": "sha256-/GJvTdTpuDjNn84j82cU6bXztE0MSkdnTWClUCRub78=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "8dc45382d5206bd292f9c2768b8058a8fd8311d9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1722651103,
|
||||
"narHash": "sha256-IRiJA0NVAoyaZeKZluwfb2DoTpBAj+FLI0KfybBeDU0=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "a633d89c6dc9a2a8aae11813a62d7c58b2c0cc51",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-24.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"lastModified": 1714640452,
|
||||
"narHash": "sha256-QBx10+k6JWz6u7VsohfSw8g8hjdBZEf8CFzXH1/1Z94=",
|
||||
"type": "tarball",
|
||||
"url": "https://github.com/NixOS/nixpkgs/archive/50eb7ecf4cd0a5756d7275c8ba36790e5bd53e33.tar.gz"
|
||||
},
|
||||
"original": {
|
||||
"type": "tarball",
|
||||
"url": "https://github.com/NixOS/nixpkgs/archive/50eb7ecf4cd0a5756d7275c8ba36790e5bd53e33.tar.gz"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-parts": "flake-parts",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
30
flake.nix
Normal file
30
flake.nix
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
|
||||
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||
};
|
||||
|
||||
outputs = inputs@{ flake-parts, ... }:
|
||||
flake-parts.lib.mkFlake { inherit inputs; } {
|
||||
systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
|
||||
perSystem = { pkgs, config, inputs', system, lib, ... }: {
|
||||
formatter = pkgs.nixpkgs-fmt;
|
||||
|
||||
devShells.default = pkgs.mkShell {
|
||||
packages = [
|
||||
config.formatter
|
||||
pkgs.fd
|
||||
pkgs.just
|
||||
pkgs.nodePackages.prettier
|
||||
pkgs.taplo
|
||||
pkgs.watchexec
|
||||
] ++ lib.optional pkgs.stdenv.isDarwin [
|
||||
pkgs.pkgsBuildHost.darwin.apple_sdk.frameworks.Security
|
||||
pkgs.pkgsBuildHost.darwin.apple_sdk.frameworks.CoreFoundation
|
||||
pkgs.pkgsBuildHost.darwin.apple_sdk.frameworks.SystemConfiguration
|
||||
pkgs.pkgsBuildHost.libiconv
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
87
justfile
87
justfile
@@ -1,10 +1,35 @@
|
||||
_list:
|
||||
@just --list
|
||||
|
||||
# Downgrade dev-dependencies necessary to run MSRV checks/tests.
|
||||
toolchain := ""
|
||||
|
||||
# Check project.
|
||||
check: && clippy
|
||||
just --unstable --fmt --check
|
||||
# nixpkgs-fmt --check .
|
||||
fd --hidden --type=file -e=md -e=yml --exec-batch prettier --check
|
||||
fd --hidden -e=toml --exec-batch taplo format --check
|
||||
fd --hidden -e=toml --exec-batch taplo lint
|
||||
cargo +nightly fmt -- --check
|
||||
|
||||
# Format project.
|
||||
fmt:
|
||||
just --unstable --fmt
|
||||
# nixpkgs-fmt .
|
||||
fd --hidden --type=file -e=md -e=yml --exec-batch prettier --write
|
||||
fd --type=file --hidden -e=toml --exec-batch taplo format
|
||||
cargo +nightly fmt
|
||||
|
||||
# Downgrade dependencies necessary to run MSRV checks/tests.
|
||||
[private]
|
||||
downgrade-for-msrv:
|
||||
cargo update -p=clap --precise=4.4.18
|
||||
cargo {{ toolchain }} update -p=rayon --precise=1.10.0 # next ver: 1.80.0
|
||||
cargo {{ toolchain }} update -p=rayon-core --precise=1.12.1 # next ver: 1.80.0
|
||||
cargo {{ toolchain }} update -p=native-tls --precise=0.2.13 # next ver: 1.80.0
|
||||
cargo {{ toolchain }} update -p=idna_adapter --precise=1.2.0 # next ver: 1.82.0
|
||||
cargo {{ toolchain }} update -p=litemap --precise=0.7.4 # next ver: 1.81.0
|
||||
cargo {{ toolchain }} update -p=zerofrom --precise=0.1.5 # next ver: 1.81.0
|
||||
cargo {{ toolchain }} update -p=half --precise=2.4.1 # next ver: 1.81.0
|
||||
|
||||
msrv := ```
|
||||
cargo metadata --format-version=1 \
|
||||
@@ -12,7 +37,6 @@ msrv := ```
|
||||
| sed -E 's/^1\.([0-9]{2})$/1\.\1\.0/'
|
||||
```
|
||||
msrv_rustup := "+" + msrv
|
||||
|
||||
non_linux_all_features_list := ```
|
||||
cargo metadata --format-version=1 \
|
||||
| jq '.packages[] | select(.source == null) | .features | keys' \
|
||||
@@ -20,45 +44,70 @@ non_linux_all_features_list := ```
|
||||
--arg exclusions "tokio-uring,io-uring" \
|
||||
'add | unique | . - ($exclusions | split(",")) | join(",")'
|
||||
```
|
||||
all_crate_features := if os() == "linux" { "--all-features" } else { "--features='" + non_linux_all_features_list + "'" }
|
||||
|
||||
all_crate_features := if os() == "linux" {
|
||||
"--all-features"
|
||||
} else {
|
||||
"--features='" + non_linux_all_features_list + "'"
|
||||
}
|
||||
# Run Clippy over workspace.
|
||||
clippy:
|
||||
cargo {{ toolchain }} clippy --workspace --all-targets {{ all_crate_features }}
|
||||
|
||||
# Run Clippy using MSRV.
|
||||
clippy-msrv: downgrade-for-msrv
|
||||
@just toolchain={{ msrv_rustup }} clippy
|
||||
|
||||
# Test workspace code.
|
||||
test toolchain="":
|
||||
[macos]
|
||||
[windows]
|
||||
test:
|
||||
cargo {{ toolchain }} test --lib --tests --package=actix-macros
|
||||
cargo {{ toolchain }} nextest run --workspace --exclude=actix-macros --no-default-features
|
||||
cargo {{ toolchain }} nextest run --workspace --exclude=actix-macros {{ all_crate_features }}
|
||||
cargo {{ toolchain }} nextest run --no-tests=warn --workspace --exclude=actix-macros --no-default-features
|
||||
cargo {{ toolchain }} nextest run --no-tests=warn --workspace --exclude=actix-macros {{ all_crate_features }}
|
||||
|
||||
# Test workspace code.
|
||||
[linux]
|
||||
test:
|
||||
cargo {{ toolchain }} test --lib --tests --package=actix-macros
|
||||
cargo {{ toolchain }} nextest run --no-tests=warn --workspace --exclude=actix-macros --no-default-features
|
||||
cargo {{ toolchain }} nextest run --no-tests=warn --workspace --exclude=actix-macros {{ non_linux_all_features_list }}
|
||||
cargo {{ toolchain }} nextest run --no-tests=warn --workspace --exclude=actix-macros {{ all_crate_features }}
|
||||
|
||||
# Test workspace using MSRV.
|
||||
test-msrv: downgrade-for-msrv (test msrv_rustup)
|
||||
test-msrv: downgrade-for-msrv
|
||||
@just toolchain={{ msrv_rustup }} test
|
||||
|
||||
# Test workspace docs.
|
||||
test-docs toolchain="": && doc
|
||||
test-docs: && doc
|
||||
cargo {{ toolchain }} test --doc --workspace {{ all_crate_features }} --no-fail-fast -- --nocapture
|
||||
|
||||
# Test workspace.
|
||||
test-all toolchain="": (test toolchain) (test-docs toolchain)
|
||||
test-all: test test-docs
|
||||
|
||||
# Document crates in workspace.
|
||||
doc *args:
|
||||
doc *args: && doc-set-workspace-crates
|
||||
rm -f "$(cargo metadata --format-version=1 | jq -r '.target_directory')/doc/crates.js"
|
||||
RUSTDOCFLAGS="--cfg=docsrs -Dwarnings" cargo +nightly doc --no-deps --workspace {{ all_crate_features }} {{ args }}
|
||||
|
||||
[private]
|
||||
doc-set-workspace-crates:
|
||||
#!/usr/bin/env bash
|
||||
(
|
||||
echo "window.ALL_CRATES ="
|
||||
cargo metadata --format-version=1 \
|
||||
| jq '[.packages[] | select(.source == null) | .targets | map(select(.doc) | .name)] | flatten'
|
||||
echo ";"
|
||||
) > "$(cargo metadata --format-version=1 | jq -r '.target_directory')/doc/crates.js"
|
||||
|
||||
# Document crates in workspace and watch for changes.
|
||||
doc-watch:
|
||||
@just doc --open
|
||||
cargo watch -- just doc
|
||||
|
||||
# Check for unintentional external type exposure on all crates in workspace.
|
||||
check-external-types-all toolchain="+nightly":
|
||||
check-external-types-all:
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
exit=0
|
||||
for f in $(find . -mindepth 2 -maxdepth 2 -name Cargo.toml | grep -vE "\-codegen/|\-derive/|\-macros/"); do
|
||||
if ! just check-external-types-manifest "$f" {{ toolchain }}; then exit=1; fi
|
||||
if ! just toolchain="+nightly" check-external-types-manifest "$f"; then exit=1; fi
|
||||
echo
|
||||
echo
|
||||
done
|
||||
@@ -71,9 +120,9 @@ check-external-types-all-table toolchain="+nightly":
|
||||
for f in $(find . -mindepth 2 -maxdepth 2 -name Cargo.toml | grep -vE "\-codegen/|\-derive/|\-macros/"); do
|
||||
echo
|
||||
echo "Checking for $f"
|
||||
just check-external-types-manifest "$f" {{ toolchain }} --output-format=markdown-table
|
||||
just toolchain="+nightly" check-external-types-manifest "$f" --output-format=markdown-table
|
||||
done
|
||||
|
||||
# Check for unintentional external type exposure on a crate.
|
||||
check-external-types-manifest manifest_path toolchain="+nightly" *extra_args="":
|
||||
check-external-types-manifest manifest_path *extra_args="":
|
||||
cargo {{ toolchain }} check-external-types --manifest-path "{{ manifest_path }}" {{ extra_args }}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.70.
|
||||
- Minimum supported Rust version (MSRV) is now 1.75.
|
||||
|
||||
## 0.1.5
|
||||
|
||||
|
@@ -2,10 +2,7 @@
|
||||
name = "local-channel"
|
||||
version = "0.1.5"
|
||||
description = "A non-threadsafe multi-producer, single-consumer, futures-aware, FIFO queue"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
]
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>", "Rob Ede <robjtede@icloud.com>"]
|
||||
repository = "https://github.com/actix/actix-net"
|
||||
keywords = ["channel", "local", "futures"]
|
||||
license.workspace = true
|
||||
@@ -13,10 +10,7 @@ edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[package.metadata.cargo_check_external_types]
|
||||
allowed_external_types = [
|
||||
"futures_core::*",
|
||||
"futures_sink::*",
|
||||
]
|
||||
allowed_external_types = ["futures_core::*", "futures_sink::*"]
|
||||
|
||||
[dependencies]
|
||||
futures-core = "0.3.17"
|
||||
@@ -25,4 +19,4 @@ local-waker = "0.1"
|
||||
|
||||
[dev-dependencies]
|
||||
futures-util = { version = "0.3.17", default-features = false }
|
||||
tokio = { version = "1.23.1", features = ["rt", "macros"] }
|
||||
tokio = { version = "1.44.2", features = ["rt", "macros"] }
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Minimum supported Rust version (MSRV) is now 1.70.
|
||||
- Minimum supported Rust version (MSRV) is now 1.75.
|
||||
|
||||
## 0.1.4
|
||||
|
||||
|
@@ -2,10 +2,7 @@
|
||||
name = "local-waker"
|
||||
version = "0.1.4"
|
||||
description = "A synchronization primitive for thread-local task wakeup"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
]
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>", "Rob Ede <robjtede@icloud.com>"]
|
||||
repository = "https://github.com/actix/actix-net"
|
||||
keywords = ["waker", "local", "futures", "no-std"]
|
||||
categories = ["asynchronous", "no-std"]
|
||||
|
Reference in New Issue
Block a user