mirror of
https://github.com/fafhrd91/actix-net
synced 2025-08-15 06:45:56 +02:00
Compare commits
37 Commits
router-v0.
...
macros-v0.
Author | SHA1 | Date | |
---|---|---|---|
|
47f278b17a | ||
|
ca77d8d835 | ||
|
00775884f8 | ||
|
4ff8a2cf68 | ||
|
5c555a9408 | ||
|
ca435b2575 | ||
|
9fa8d7fc5a | ||
|
b03fe7c5b6 | ||
|
6fed1c3e7d | ||
|
c3d697df97 | ||
|
80a362712f | ||
|
2b1edb95ea | ||
|
4644fa41cf | ||
|
98c37fe47d | ||
|
b9455d2ca9 | ||
|
0183b0f8cc | ||
|
b122a1ae1a | ||
|
4303058243 | ||
|
48b2e11509 | ||
|
5379a46a99 | ||
|
f8f1ac94bc | ||
|
82cd5b8290 | ||
|
c65e8524b2 | ||
|
a83dfaa162 | ||
|
e4ec956001 | ||
|
95cba659ff | ||
|
5687e81d9f | ||
|
a0fe2a9b2e | ||
|
ad22a93466 | ||
|
c2d5b2398a | ||
|
5b1ff30dd9 | ||
|
e1317bb3a0 | ||
|
dcea009158 | ||
|
13c18b8a51 | ||
|
06b17d6a43 | ||
|
605ec25143 | ||
|
3824493fd3 |
@@ -1,3 +1,25 @@
|
|||||||
[alias]
|
[alias]
|
||||||
chk = "hack check --workspace --all-features --tests --examples"
|
lint = "clippy --workspace --all-features --tests --examples --bins -- -Dclippy::todo"
|
||||||
lint = "hack --clean-per-run clippy --workspace --tests --examples"
|
|
||||||
|
ci-doctest = "test --workspace --all-features --doc --no-fail-fast -- --nocapture"
|
||||||
|
|
||||||
|
# just check the library (without dev deps)
|
||||||
|
ci-check-min = "hack --workspace check --no-default-features"
|
||||||
|
ci-check-lib = "hack --workspace --feature-powerset --exclude-features=io-uring check"
|
||||||
|
ci-check-lib-linux = "hack --workspace --feature-powerset check"
|
||||||
|
|
||||||
|
# check everything
|
||||||
|
ci-check = "hack --workspace --feature-powerset --exclude-features=io-uring check --tests --examples"
|
||||||
|
ci-check-linux = "hack --workspace --feature-powerset check --tests --examples"
|
||||||
|
|
||||||
|
# tests avoiding io-uring feature
|
||||||
|
ci-test = "hack test --workspace --exclude=actix-rt --exclude=actix-server --all-features --lib --tests --no-fail-fast -- --nocapture"
|
||||||
|
ci-test-rt = " hack --feature-powerset --exclude-features=io-uring test --package=actix-rt --lib --tests --no-fail-fast -- --nocapture"
|
||||||
|
ci-test-server = "hack --feature-powerset --exclude-features=io-uring test --package=actix-server --lib --tests --no-fail-fast -- --nocapture"
|
||||||
|
|
||||||
|
# test with io-uring feature
|
||||||
|
ci-test-rt-linux = " hack --feature-powerset test --package=actix-rt --lib --tests --no-fail-fast -- --nocapture"
|
||||||
|
ci-test-server-linux = "hack --feature-powerset test --package=actix-server --lib --tests --no-fail-fast -- --nocapture"
|
||||||
|
|
||||||
|
# test lower msrv
|
||||||
|
ci-test-lower-msrv = "hack --workspace --exclude=actix-server --exclude=actix-tls --feature-powerset test --lib --tests --no-fail-fast -- --nocapture"
|
||||||
|
156
.github/workflows/ci.yml
vendored
156
.github/workflows/ci.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
- { name: Windows (MinGW), os: windows-latest, triple: x86_64-pc-windows-gnu }
|
- { name: Windows (MinGW), os: windows-latest, triple: x86_64-pc-windows-gnu }
|
||||||
- { name: Windows (32-bit), os: windows-latest, triple: i686-pc-windows-msvc }
|
- { name: Windows (32-bit), os: windows-latest, triple: i686-pc-windows-msvc }
|
||||||
version:
|
version:
|
||||||
- 1.46.0 # MSRV
|
- 1.52.0 # MSRV for -server and -tls
|
||||||
- stable
|
- stable
|
||||||
- nightly
|
- nightly
|
||||||
|
|
||||||
@@ -64,8 +64,7 @@ jobs:
|
|||||||
|
|
||||||
# - name: Generate Cargo.lock
|
# - name: Generate Cargo.lock
|
||||||
# uses: actions-rs/cargo@v1
|
# uses: actions-rs/cargo@v1
|
||||||
# with:
|
# with: { command: generate-lockfile }
|
||||||
# command: generate-lockfile
|
|
||||||
# - name: Cache Dependencies
|
# - name: Cache Dependencies
|
||||||
# uses: Swatinem/rust-cache@v1.2.0
|
# uses: Swatinem/rust-cache@v1.2.0
|
||||||
|
|
||||||
@@ -75,57 +74,136 @@ jobs:
|
|||||||
command: install
|
command: install
|
||||||
args: cargo-hack
|
args: cargo-hack
|
||||||
|
|
||||||
- name: check minimal
|
- name: check lib
|
||||||
|
if: >
|
||||||
|
matrix.target.os != 'ubuntu-latest'
|
||||||
|
&& matrix.target.triple != 'x86_64-pc-windows-gnu'
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with: { command: ci-check-lib }
|
||||||
command: hack
|
- name: check lib
|
||||||
args: check --workspace --no-default-features
|
if: matrix.target.os == 'ubuntu-latest'
|
||||||
|
|
||||||
- name: check minimal + tests
|
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with: { command: ci-check-lib-linux }
|
||||||
command: hack
|
- name: check lib
|
||||||
args: check --workspace --no-default-features --tests --examples
|
if: matrix.target.triple == 'x86_64-pc-windows-gnu'
|
||||||
|
|
||||||
- name: check default
|
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with: { command: ci-check-min }
|
||||||
command: check
|
|
||||||
args: --workspace --tests --examples
|
|
||||||
|
|
||||||
- name: check full
|
- name: check full
|
||||||
# TODO: compile OpenSSL and run tests on MinGW
|
# TODO: compile OpenSSL and run tests on MinGW
|
||||||
if: matrix.target.triple != 'x86_64-pc-windows-gnu'
|
if: >
|
||||||
|
matrix.target.os != 'ubuntu-latest'
|
||||||
|
&& matrix.target.triple != 'x86_64-pc-windows-gnu'
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with: { command: ci-check }
|
||||||
command: check
|
- name: check all
|
||||||
args: --workspace --all-features --tests --examples
|
if: matrix.target.os == 'ubuntu-latest'
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with: { command: ci-check-linux }
|
||||||
|
|
||||||
- name: tests
|
- name: tests
|
||||||
if: matrix.target.triple != 'x86_64-pc-windows-gnu'
|
if: >
|
||||||
|
matrix.target.os != 'ubuntu-latest'
|
||||||
|
&& matrix.target.triple != 'x86_64-pc-windows-gnu'
|
||||||
|
run: |
|
||||||
|
cargo ci-test
|
||||||
|
cargo ci-test-rt
|
||||||
|
cargo ci-test-server
|
||||||
|
- name: tests
|
||||||
|
if: matrix.target.os == 'ubuntu-latest'
|
||||||
|
run: |
|
||||||
|
cargo ci-test
|
||||||
|
cargo ci-test-rt-linux
|
||||||
|
cargo ci-test-server-linux
|
||||||
|
|
||||||
|
- name: Clear the cargo caches
|
||||||
|
run: |
|
||||||
|
cargo install cargo-cache --version 0.6.2 --no-default-features --features ci-autoclean
|
||||||
|
cargo-cache
|
||||||
|
|
||||||
|
build_and_test_lower_msrv:
|
||||||
|
name: Linux / 1.46 (lower MSRV)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install 1.46.0 # MSRV for all but -server and -tls
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: 1.46.0-x86_64-unknown-linux-gnu
|
||||||
|
profile: minimal
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- name: Install cargo-hack
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: test
|
command: install
|
||||||
args: --workspace --all-features --no-fail-fast -- --nocapture
|
args: cargo-hack
|
||||||
|
|
||||||
|
- name: tests
|
||||||
|
run: cargo ci-test-lower-msrv
|
||||||
|
|
||||||
|
- name: Clear the cargo caches
|
||||||
|
run: |
|
||||||
|
cargo install cargo-cache --version 0.6.2 --no-default-features --features ci-autoclean
|
||||||
|
cargo-cache
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
name: coverage
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install Rust (nightly)
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: stable-x86_64-unknown-linux-gnu
|
||||||
|
profile: minimal
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- name: Generate Cargo.lock
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with: { command: generate-lockfile }
|
||||||
|
- name: Cache Dependencies
|
||||||
|
uses: Swatinem/rust-cache@v1.3.0
|
||||||
|
|
||||||
- name: Generate coverage file
|
- name: Generate coverage file
|
||||||
if: >
|
if: github.ref == 'refs/heads/master'
|
||||||
matrix.target.os == 'ubuntu-latest'
|
|
||||||
&& matrix.version == 'stable'
|
|
||||||
&& github.ref == 'refs/heads/master'
|
|
||||||
run: |
|
run: |
|
||||||
cargo install cargo-tarpaulin
|
cargo install cargo-tarpaulin
|
||||||
cargo tarpaulin --out Xml --verbose
|
cargo tarpaulin --out Xml --verbose
|
||||||
- name: Upload to Codecov
|
- name: Upload to Codecov
|
||||||
if: >
|
if: github.ref == 'refs/heads/master'
|
||||||
matrix.target.os == 'ubuntu-latest'
|
|
||||||
&& matrix.version == 'stable'
|
|
||||||
&& github.ref == 'refs/heads/master'
|
|
||||||
uses: codecov/codecov-action@v1
|
uses: codecov/codecov-action@v1
|
||||||
with:
|
with: { file: cobertura.xml }
|
||||||
file: cobertura.xml
|
|
||||||
|
|
||||||
- name: Clear the cargo caches
|
rustdoc:
|
||||||
run: |
|
name: rustdoc
|
||||||
cargo install cargo-cache --no-default-features --features ci-autoclean
|
runs-on: ubuntu-latest
|
||||||
cargo-cache
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install Rust (nightly)
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: nightly-x86_64-unknown-linux-gnu
|
||||||
|
profile: minimal
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- name: Generate Cargo.lock
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with: { command: generate-lockfile }
|
||||||
|
- name: Cache Dependencies
|
||||||
|
uses: Swatinem/rust-cache@v1.3.0
|
||||||
|
|
||||||
|
- name: Install cargo-hack
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: install
|
||||||
|
args: cargo-hack
|
||||||
|
|
||||||
|
- name: doc tests
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
timeout-minutes: 40
|
||||||
|
with: { command: ci-doctest }
|
||||||
|
2
.github/workflows/clippy-fmt.yml
vendored
2
.github/workflows/clippy-fmt.yml
vendored
@@ -39,4 +39,4 @@ jobs:
|
|||||||
uses: actions-rs/clippy-check@v1
|
uses: actions-rs/clippy-check@v1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
args: --workspace --tests --all-features
|
args: --workspace --all-features --tests --examples --bins -- -Dclippy::todo
|
||||||
|
@@ -2,7 +2,6 @@
|
|||||||
members = [
|
members = [
|
||||||
"actix-codec",
|
"actix-codec",
|
||||||
"actix-macros",
|
"actix-macros",
|
||||||
"actix-router",
|
|
||||||
"actix-rt",
|
"actix-rt",
|
||||||
"actix-server",
|
"actix-server",
|
||||||
"actix-service",
|
"actix-service",
|
||||||
@@ -17,7 +16,6 @@ members = [
|
|||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
actix-codec = { path = "actix-codec" }
|
actix-codec = { path = "actix-codec" }
|
||||||
actix-macros = { path = "actix-macros" }
|
actix-macros = { path = "actix-macros" }
|
||||||
actix-router = { path = "actix-router" }
|
|
||||||
actix-rt = { path = "actix-rt" }
|
actix-rt = { path = "actix-rt" }
|
||||||
actix-server = { path = "actix-server" }
|
actix-server = { path = "actix-server" }
|
||||||
actix-service = { path = "actix-service" }
|
actix-service = { path = "actix-service" }
|
||||||
@@ -27,3 +25,8 @@ actix-utils = { path = "actix-utils" }
|
|||||||
bytestring = { path = "bytestring" }
|
bytestring = { path = "bytestring" }
|
||||||
local-channel = { path = "local-channel" }
|
local-channel = { path = "local-channel" }
|
||||||
local-waker = { path = "local-waker" }
|
local-waker = { path = "local-waker" }
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = true
|
||||||
|
opt-level = 3
|
||||||
|
codegen-units = 1
|
||||||
|
@@ -20,5 +20,5 @@ futures-core = { version = "0.3.7", default-features = false }
|
|||||||
futures-sink = { version = "0.3.7", default-features = false }
|
futures-sink = { version = "0.3.7", default-features = false }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
pin-project-lite = "0.2"
|
pin-project-lite = "0.2"
|
||||||
tokio = "1"
|
tokio = "1.5.1"
|
||||||
tokio-util = { version = "0.6", features = ["codec", "io"] }
|
tokio-util = { version = "0.6", features = ["codec", "io"] }
|
||||||
|
@@ -21,14 +21,13 @@ bitflags::bitflags! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pin_project_lite::pin_project! {
|
pin_project_lite::pin_project! {
|
||||||
/// A unified `Stream` and `Sink` interface to an underlying I/O object, using
|
/// A unified `Stream` and `Sink` interface to an underlying I/O object, using the `Encoder` and
|
||||||
/// the `Encoder` and `Decoder` traits to encode and decode frames.
|
/// `Decoder` traits to encode and decode frames.
|
||||||
///
|
///
|
||||||
/// Raw I/O objects work with byte sequences, but higher-level code usually
|
/// Raw I/O objects work with byte sequences, but higher-level code usually wants to batch these
|
||||||
/// wants to batch these into meaningful chunks, called "frames". This
|
/// into meaningful chunks, called "frames". This method layers framing on top of an I/O object,
|
||||||
/// method layers framing on top of an I/O object, by using the `Encoder`/`Decoder`
|
/// by using the `Encoder`/`Decoder` traits to handle encoding and decoding of message frames.
|
||||||
/// traits to handle encoding and decoding of message frames. Note that
|
/// Note that the incoming and outgoing frame types may be distinct.
|
||||||
/// the incoming and outgoing frame types may be distinct.
|
|
||||||
pub struct Framed<T, U> {
|
pub struct Framed<T, U> {
|
||||||
#[pin]
|
#[pin]
|
||||||
io: T,
|
io: T,
|
||||||
@@ -44,10 +43,9 @@ where
|
|||||||
T: AsyncRead + AsyncWrite,
|
T: AsyncRead + AsyncWrite,
|
||||||
U: Decoder,
|
U: Decoder,
|
||||||
{
|
{
|
||||||
/// This function returns a *single* object that is both `Stream` and
|
/// This function returns a *single* object that is both `Stream` and `Sink`; grouping this into
|
||||||
/// `Sink`; grouping this into a single object is often useful for layering
|
/// a single object is often useful for layering things like gzip or TLS, which require both
|
||||||
/// things like gzip or TLS, which require both read and write access to the
|
/// read and write access to the underlying object.
|
||||||
/// underlying object.
|
|
||||||
pub fn new(io: T, codec: U) -> Framed<T, U> {
|
pub fn new(io: T, codec: U) -> Framed<T, U> {
|
||||||
Framed {
|
Framed {
|
||||||
io,
|
io,
|
||||||
@@ -70,21 +68,18 @@ impl<T, U> Framed<T, U> {
|
|||||||
&mut self.codec
|
&mut self.codec
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the underlying I/O stream wrapped by
|
/// Returns a reference to the underlying I/O stream wrapped by `Frame`.
|
||||||
/// `Frame`.
|
|
||||||
///
|
///
|
||||||
/// Note that care should be taken to not tamper with the underlying stream
|
/// Note that care should be taken to not tamper with the underlying stream of data coming in as
|
||||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
/// it may corrupt the stream of frames otherwise being worked with.
|
||||||
/// being worked with.
|
|
||||||
pub fn io_ref(&self) -> &T {
|
pub fn io_ref(&self) -> &T {
|
||||||
&self.io
|
&self.io
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a mutable reference to the underlying I/O stream.
|
/// Returns a mutable reference to the underlying I/O stream.
|
||||||
///
|
///
|
||||||
/// Note that care should be taken to not tamper with the underlying stream
|
/// Note that care should be taken to not tamper with the underlying stream of data coming in as
|
||||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
/// it may corrupt the stream of frames otherwise being worked with.
|
||||||
/// being worked with.
|
|
||||||
pub fn io_mut(&mut self) -> &mut T {
|
pub fn io_mut(&mut self) -> &mut T {
|
||||||
&mut self.io
|
&mut self.io
|
||||||
}
|
}
|
||||||
@@ -183,16 +178,15 @@ impl<T, U> Framed<T, U> {
|
|||||||
U: Decoder,
|
U: Decoder,
|
||||||
{
|
{
|
||||||
loop {
|
loop {
|
||||||
let mut this = self.as_mut().project();
|
let this = self.as_mut().project();
|
||||||
// Repeatedly call `decode` or `decode_eof` as long as it is
|
// Repeatedly call `decode` or `decode_eof` as long as it is "readable". Readable is
|
||||||
// "readable". Readable is defined as not having returned `None`. If
|
// defined as not having returned `None`. If the upstream has returned EOF, and the
|
||||||
// the upstream has returned EOF, and the decoder is no longer
|
// decoder is no longer readable, it can be assumed that the decoder will never become
|
||||||
// readable, it can be assumed that the decoder will never become
|
|
||||||
// readable again, at which point the stream is terminated.
|
// readable again, at which point the stream is terminated.
|
||||||
|
|
||||||
if this.flags.contains(Flags::READABLE) {
|
if this.flags.contains(Flags::READABLE) {
|
||||||
if this.flags.contains(Flags::EOF) {
|
if this.flags.contains(Flags::EOF) {
|
||||||
match this.codec.decode_eof(&mut this.read_buf) {
|
match this.codec.decode_eof(this.read_buf) {
|
||||||
Ok(Some(frame)) => return Poll::Ready(Some(Ok(frame))),
|
Ok(Some(frame)) => return Poll::Ready(Some(Ok(frame))),
|
||||||
Ok(None) => return Poll::Ready(None),
|
Ok(None) => return Poll::Ready(None),
|
||||||
Err(e) => return Poll::Ready(Some(Err(e))),
|
Err(e) => return Poll::Ready(Some(Err(e))),
|
||||||
@@ -201,7 +195,7 @@ impl<T, U> Framed<T, U> {
|
|||||||
|
|
||||||
log::trace!("attempting to decode a frame");
|
log::trace!("attempting to decode a frame");
|
||||||
|
|
||||||
match this.codec.decode(&mut this.read_buf) {
|
match this.codec.decode(this.read_buf) {
|
||||||
Ok(Some(frame)) => {
|
Ok(Some(frame)) => {
|
||||||
log::trace!("frame decoded from buffer");
|
log::trace!("frame decoded from buffer");
|
||||||
return Poll::Ready(Some(Ok(frame)));
|
return Poll::Ready(Some(Ok(frame)));
|
||||||
@@ -215,7 +209,7 @@ impl<T, U> Framed<T, U> {
|
|||||||
|
|
||||||
debug_assert!(!this.flags.contains(Flags::EOF));
|
debug_assert!(!this.flags.contains(Flags::EOF));
|
||||||
|
|
||||||
// Otherwise, try to read more data and try again. Make sure we've got room
|
// Otherwise, try to read more data and try again. Make sure we've got room.
|
||||||
let remaining = this.read_buf.capacity() - this.read_buf.len();
|
let remaining = this.read_buf.capacity() - this.read_buf.len();
|
||||||
if remaining < LW {
|
if remaining < LW {
|
||||||
this.read_buf.reserve(HW - remaining)
|
this.read_buf.reserve(HW - remaining)
|
||||||
@@ -341,13 +335,12 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T, U> Framed<T, U> {
|
impl<T, U> Framed<T, U> {
|
||||||
/// This function returns a *single* object that is both `Stream` and
|
/// This function returns a *single* object that is both `Stream` and `Sink`; grouping this into
|
||||||
/// `Sink`; grouping this into a single object is often useful for layering
|
/// a single object is often useful for layering things like gzip or TLS, which require both
|
||||||
/// things like gzip or TLS, which require both read and write access to the
|
/// read and write access to the underlying object.
|
||||||
/// underlying object.
|
|
||||||
///
|
///
|
||||||
/// These objects take a stream, a read buffer and a write buffer. These
|
/// These objects take a stream, a read buffer and a write buffer. These fields can be obtained
|
||||||
/// fields can be obtained from an existing `Framed` with the `into_parts` method.
|
/// from an existing `Framed` with the `into_parts` method.
|
||||||
pub fn from_parts(parts: FramedParts<T, U>) -> Framed<T, U> {
|
pub fn from_parts(parts: FramedParts<T, U>) -> Framed<T, U> {
|
||||||
Framed {
|
Framed {
|
||||||
io: parts.io,
|
io: parts.io,
|
||||||
@@ -358,12 +351,11 @@ impl<T, U> Framed<T, U> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Consumes the `Frame`, returning its underlying I/O stream, the buffer
|
/// Consumes the `Frame`, returning its underlying I/O stream, the buffer with unprocessed data,
|
||||||
/// with unprocessed data, and the codec.
|
/// and the codec.
|
||||||
///
|
///
|
||||||
/// Note that care should be taken to not tamper with the underlying stream
|
/// Note that care should be taken to not tamper with the underlying stream of data coming in as
|
||||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
/// it may corrupt the stream of frames otherwise being worked with.
|
||||||
/// being worked with.
|
|
||||||
pub fn into_parts(self) -> FramedParts<T, U> {
|
pub fn into_parts(self) -> FramedParts<T, U> {
|
||||||
FramedParts {
|
FramedParts {
|
||||||
io: self.io,
|
io: self.io,
|
||||||
@@ -376,14 +368,15 @@ impl<T, U> Framed<T, U> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// `FramedParts` contains an export of the data of a Framed transport.
|
/// `FramedParts` contains an export of the data of a Framed transport.
|
||||||
/// It can be used to construct a new `Framed` with a different codec.
|
///
|
||||||
/// It contains all current buffers and the inner transport.
|
/// It can be used to construct a new `Framed` with a different codec. It contains all current
|
||||||
|
/// buffers and the inner transport.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FramedParts<T, U> {
|
pub struct FramedParts<T, U> {
|
||||||
/// The inner transport used to read bytes to and write bytes to
|
/// The inner transport used to read bytes to and write bytes to.
|
||||||
pub io: T,
|
pub io: T,
|
||||||
|
|
||||||
/// The codec
|
/// The codec object.
|
||||||
pub codec: U,
|
pub codec: U,
|
||||||
|
|
||||||
/// The buffer with read but unprocessed data.
|
/// The buffer with read but unprocessed data.
|
||||||
@@ -396,7 +389,7 @@ pub struct FramedParts<T, U> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T, U> FramedParts<T, U> {
|
impl<T, U> FramedParts<T, U> {
|
||||||
/// Create a new, default, `FramedParts`
|
/// Creates a new default `FramedParts`.
|
||||||
pub fn new(io: T, codec: U) -> FramedParts<T, U> {
|
pub fn new(io: T, codec: U) -> FramedParts<T, U> {
|
||||||
FramedParts {
|
FramedParts {
|
||||||
io,
|
io,
|
||||||
@@ -407,7 +400,7 @@ impl<T, U> FramedParts<T, U> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new `FramedParts` with read buffer
|
/// Creates a new `FramedParts` with read buffer.
|
||||||
pub fn with_read_buf(io: T, codec: U, read_buf: BytesMut) -> FramedParts<T, U> {
|
pub fn with_read_buf(io: T, codec: U, read_buf: BytesMut) -> FramedParts<T, U> {
|
||||||
FramedParts {
|
FramedParts {
|
||||||
io,
|
io,
|
||||||
|
@@ -3,6 +3,25 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 0.2.3 - 2021-10-19
|
||||||
|
* Fix test macro in presence of other imports named "test". [#399]
|
||||||
|
|
||||||
|
[#399]: https://github.com/actix/actix-net/pull/399
|
||||||
|
|
||||||
|
|
||||||
|
## 0.2.2 - 2021-10-14
|
||||||
|
* Improve error recovery potential when macro input is invalid. [#391]
|
||||||
|
* Allow custom `System`s on test macro. [#391]
|
||||||
|
|
||||||
|
[#391]: https://github.com/actix/actix-net/pull/391
|
||||||
|
|
||||||
|
|
||||||
|
## 0.2.1 - 2021-02-02
|
||||||
|
* Add optional argument `system` to `main` macro which can be used to specify the path to `actix_rt::System` (useful for re-exports). [#363]
|
||||||
|
|
||||||
|
[#363]: https://github.com/actix/actix-net/pull/363
|
||||||
|
|
||||||
|
|
||||||
## 0.2.0 - 2021-02-02
|
## 0.2.0 - 2021-02-02
|
||||||
* Update to latest `actix_rt::System::new` signature. [#261]
|
* Update to latest `actix_rt::System::new` signature. [#261]
|
||||||
|
|
||||||
|
@@ -1,10 +1,13 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-macros"
|
name = "actix-macros"
|
||||||
version = "0.2.0"
|
version = "0.2.3"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = [
|
||||||
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
|
"Ibraheem Ahmed <ibrah1440@gmail.com>",
|
||||||
|
"Rob Ede <robjtede@icloud.com>",
|
||||||
|
]
|
||||||
description = "Macros for Actix system and runtime"
|
description = "Macros for Actix system and runtime"
|
||||||
repository = "https://github.com/actix/actix-net.git"
|
repository = "https://github.com/actix/actix-net.git"
|
||||||
documentation = "https://docs.rs/actix-macros"
|
|
||||||
categories = ["network-programming", "asynchronous"]
|
categories = ["network-programming", "asynchronous"]
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
@@ -20,4 +23,5 @@ syn = { version = "^1", features = ["full"] }
|
|||||||
actix-rt = "2.0.0"
|
actix-rt = "2.0.0"
|
||||||
|
|
||||||
futures-util = { version = "0.3.7", default-features = false }
|
futures-util = { version = "0.3.7", default-features = false }
|
||||||
|
rustversion = "1"
|
||||||
trybuild = "1"
|
trybuild = "1"
|
||||||
|
@@ -27,8 +27,15 @@ use quote::quote;
|
|||||||
#[allow(clippy::needless_doctest_main)]
|
#[allow(clippy::needless_doctest_main)]
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||||
pub fn main(_: TokenStream, item: TokenStream) -> TokenStream {
|
pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
let mut input = syn::parse_macro_input!(item as syn::ItemFn);
|
let mut input = match syn::parse::<syn::ItemFn>(item.clone()) {
|
||||||
|
Ok(input) => input,
|
||||||
|
// on parse err, make IDEs happy; see fn docs
|
||||||
|
Err(err) => return input_and_compile_error(item, err),
|
||||||
|
};
|
||||||
|
|
||||||
|
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
|
||||||
|
|
||||||
let attrs = &input.attrs;
|
let attrs = &input.attrs;
|
||||||
let vis = &input.vis;
|
let vis = &input.vis;
|
||||||
let sig = &mut input.sig;
|
let sig = &mut input.sig;
|
||||||
@@ -43,13 +50,47 @@ pub fn main(_: TokenStream, item: TokenStream) -> TokenStream {
|
|||||||
.into();
|
.into();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut system = syn::parse_str::<syn::Path>("::actix_rt::System").unwrap();
|
||||||
|
|
||||||
|
for arg in &args {
|
||||||
|
match arg {
|
||||||
|
syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue {
|
||||||
|
lit: syn::Lit::Str(lit),
|
||||||
|
path,
|
||||||
|
..
|
||||||
|
})) => match path
|
||||||
|
.get_ident()
|
||||||
|
.map(|i| i.to_string().to_lowercase())
|
||||||
|
.as_deref()
|
||||||
|
{
|
||||||
|
Some("system") => match lit.parse() {
|
||||||
|
Ok(path) => system = path,
|
||||||
|
Err(_) => {
|
||||||
|
return syn::Error::new_spanned(lit, "Expected path")
|
||||||
|
.to_compile_error()
|
||||||
|
.into();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return syn::Error::new_spanned(arg, "Unknown attribute specified")
|
||||||
|
.to_compile_error()
|
||||||
|
.into();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return syn::Error::new_spanned(arg, "Unknown attribute specified")
|
||||||
|
.to_compile_error()
|
||||||
|
.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sig.asyncness = None;
|
sig.asyncness = None;
|
||||||
|
|
||||||
(quote! {
|
(quote! {
|
||||||
#(#attrs)*
|
#(#attrs)*
|
||||||
#vis #sig {
|
#vis #sig {
|
||||||
actix_rt::System::new()
|
<#system>::new().block_on(async move { #body })
|
||||||
.block_on(async move { #body })
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
@@ -65,8 +106,15 @@ pub fn main(_: TokenStream, item: TokenStream) -> TokenStream {
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn test(_: TokenStream, item: TokenStream) -> TokenStream {
|
pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
let mut input = syn::parse_macro_input!(item as syn::ItemFn);
|
let mut input = match syn::parse::<syn::ItemFn>(item.clone()) {
|
||||||
|
Ok(input) => input,
|
||||||
|
// on parse err, make IDEs happy; see fn docs
|
||||||
|
Err(err) => return input_and_compile_error(item, err),
|
||||||
|
};
|
||||||
|
|
||||||
|
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
|
||||||
|
|
||||||
let attrs = &input.attrs;
|
let attrs = &input.attrs;
|
||||||
let vis = &input.vis;
|
let vis = &input.vis;
|
||||||
let sig = &mut input.sig;
|
let sig = &mut input.sig;
|
||||||
@@ -91,18 +139,64 @@ pub fn test(_: TokenStream, item: TokenStream) -> TokenStream {
|
|||||||
sig.asyncness = None;
|
sig.asyncness = None;
|
||||||
|
|
||||||
let missing_test_attr = if has_test_attr {
|
let missing_test_attr = if has_test_attr {
|
||||||
quote!()
|
quote! {}
|
||||||
} else {
|
} else {
|
||||||
quote!(#[test])
|
quote! { #[::core::prelude::v1::test] }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut system = syn::parse_str::<syn::Path>("::actix_rt::System").unwrap();
|
||||||
|
|
||||||
|
for arg in &args {
|
||||||
|
match arg {
|
||||||
|
syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue {
|
||||||
|
lit: syn::Lit::Str(lit),
|
||||||
|
path,
|
||||||
|
..
|
||||||
|
})) => match path
|
||||||
|
.get_ident()
|
||||||
|
.map(|i| i.to_string().to_lowercase())
|
||||||
|
.as_deref()
|
||||||
|
{
|
||||||
|
Some("system") => match lit.parse() {
|
||||||
|
Ok(path) => system = path,
|
||||||
|
Err(_) => {
|
||||||
|
return syn::Error::new_spanned(lit, "Expected path")
|
||||||
|
.to_compile_error()
|
||||||
|
.into();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return syn::Error::new_spanned(arg, "Unknown attribute specified")
|
||||||
|
.to_compile_error()
|
||||||
|
.into();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return syn::Error::new_spanned(arg, "Unknown attribute specified")
|
||||||
|
.to_compile_error()
|
||||||
|
.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
(quote! {
|
(quote! {
|
||||||
#missing_test_attr
|
#missing_test_attr
|
||||||
#(#attrs)*
|
#(#attrs)*
|
||||||
#vis #sig {
|
#vis #sig {
|
||||||
actix_rt::System::new()
|
<#system>::new().block_on(async { #body })
|
||||||
.block_on(async { #body })
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts the error to a token stream and appends it to the original input.
|
||||||
|
///
|
||||||
|
/// Returning the original input in addition to the error is good for IDEs which can gracefully
|
||||||
|
/// recover and show more precise errors within the macro body.
|
||||||
|
///
|
||||||
|
/// See <https://github.com/rust-analyzer/rust-analyzer/issues/10468> for more info.
|
||||||
|
fn input_and_compile_error(mut item: TokenStream, err: syn::Error) -> TokenStream {
|
||||||
|
let compile_err = TokenStream::from(err.to_compile_error());
|
||||||
|
item.extend(compile_err);
|
||||||
|
item
|
||||||
|
}
|
||||||
|
@@ -1,11 +1,18 @@
|
|||||||
|
#[rustversion::stable(1.46)] // MSRV
|
||||||
#[test]
|
#[test]
|
||||||
fn compile_macros() {
|
fn compile_macros() {
|
||||||
let t = trybuild::TestCases::new();
|
let t = trybuild::TestCases::new();
|
||||||
t.pass("tests/trybuild/main-01-basic.rs");
|
t.pass("tests/trybuild/main-01-basic.rs");
|
||||||
t.compile_fail("tests/trybuild/main-02-only-async.rs");
|
t.compile_fail("tests/trybuild/main-02-only-async.rs");
|
||||||
t.pass("tests/trybuild/main-03-fn-params.rs");
|
t.pass("tests/trybuild/main-03-fn-params.rs");
|
||||||
|
t.pass("tests/trybuild/main-04-system-path.rs");
|
||||||
|
t.compile_fail("tests/trybuild/main-05-system-expect-path.rs");
|
||||||
|
t.compile_fail("tests/trybuild/main-06-unknown-attr.rs");
|
||||||
|
|
||||||
t.pass("tests/trybuild/test-01-basic.rs");
|
t.pass("tests/trybuild/test-01-basic.rs");
|
||||||
t.pass("tests/trybuild/test-02-keep-attrs.rs");
|
t.pass("tests/trybuild/test-02-keep-attrs.rs");
|
||||||
t.compile_fail("tests/trybuild/test-03-only-async.rs");
|
t.compile_fail("tests/trybuild/test-03-only-async.rs");
|
||||||
|
t.pass("tests/trybuild/test-04-system-path.rs");
|
||||||
|
t.compile_fail("tests/trybuild/test-05-system-expect-path.rs");
|
||||||
|
t.compile_fail("tests/trybuild/test-06-unknown-attr.rs");
|
||||||
}
|
}
|
||||||
|
8
actix-macros/tests/trybuild/main-04-system-path.rs
Normal file
8
actix-macros/tests/trybuild/main-04-system-path.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
mod system {
|
||||||
|
pub use actix_rt::System as MySystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::main(system = "system::MySystem")]
|
||||||
|
async fn main() {
|
||||||
|
futures_util::future::ready(()).await
|
||||||
|
}
|
@@ -0,0 +1,4 @@
|
|||||||
|
#[actix_rt::main(system = "!@#*&")]
|
||||||
|
async fn main2() {}
|
||||||
|
|
||||||
|
fn main() {}
|
@@ -0,0 +1,5 @@
|
|||||||
|
error: Expected path
|
||||||
|
--> $DIR/main-05-system-expect-path.rs:1:27
|
||||||
|
|
|
||||||
|
1 | #[actix_rt::main(system = "!@#*&")]
|
||||||
|
| ^^^^^^^
|
7
actix-macros/tests/trybuild/main-06-unknown-attr.rs
Normal file
7
actix-macros/tests/trybuild/main-06-unknown-attr.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#[actix_rt::main(foo = "bar")]
|
||||||
|
async fn async_main() {}
|
||||||
|
|
||||||
|
#[actix_rt::main(bar::baz)]
|
||||||
|
async fn async_main2() {}
|
||||||
|
|
||||||
|
fn main() {}
|
11
actix-macros/tests/trybuild/main-06-unknown-attr.stderr
Normal file
11
actix-macros/tests/trybuild/main-06-unknown-attr.stderr
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
error: Unknown attribute specified
|
||||||
|
--> $DIR/main-06-unknown-attr.rs:1:18
|
||||||
|
|
|
||||||
|
1 | #[actix_rt::main(foo = "bar")]
|
||||||
|
| ^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: Unknown attribute specified
|
||||||
|
--> $DIR/main-06-unknown-attr.rs:4:18
|
||||||
|
|
|
||||||
|
4 | #[actix_rt::main(bar::baz)]
|
||||||
|
| ^^^^^^^^
|
10
actix-macros/tests/trybuild/test-04-system-path.rs
Normal file
10
actix-macros/tests/trybuild/test-04-system-path.rs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
mod system {
|
||||||
|
pub use actix_rt::System as MySystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test(system = "system::MySystem")]
|
||||||
|
async fn my_test() {
|
||||||
|
futures_util::future::ready(()).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
@@ -0,0 +1,4 @@
|
|||||||
|
#[actix_rt::test(system = "!@#*&")]
|
||||||
|
async fn my_test() {}
|
||||||
|
|
||||||
|
fn main() {}
|
@@ -0,0 +1,5 @@
|
|||||||
|
error: Expected path
|
||||||
|
--> $DIR/test-05-system-expect-path.rs:1:27
|
||||||
|
|
|
||||||
|
1 | #[actix_rt::test(system = "!@#*&")]
|
||||||
|
| ^^^^^^^
|
7
actix-macros/tests/trybuild/test-06-unknown-attr.rs
Normal file
7
actix-macros/tests/trybuild/test-06-unknown-attr.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#[actix_rt::test(foo = "bar")]
|
||||||
|
async fn my_test_1() {}
|
||||||
|
|
||||||
|
#[actix_rt::test(bar::baz)]
|
||||||
|
async fn my_test_2() {}
|
||||||
|
|
||||||
|
fn main() {}
|
11
actix-macros/tests/trybuild/test-06-unknown-attr.stderr
Normal file
11
actix-macros/tests/trybuild/test-06-unknown-attr.stderr
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
error: Unknown attribute specified
|
||||||
|
--> $DIR/test-06-unknown-attr.rs:1:18
|
||||||
|
|
|
||||||
|
1 | #[actix_rt::test(foo = "bar")]
|
||||||
|
| ^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: Unknown attribute specified
|
||||||
|
--> $DIR/test-06-unknown-attr.rs:4:18
|
||||||
|
|
|
||||||
|
4 | #[actix_rt::test(bar::baz)]
|
||||||
|
| ^^^^^^^^
|
@@ -1,82 +0,0 @@
|
|||||||
# Changes
|
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
|
||||||
|
|
||||||
|
|
||||||
## 0.4.0 - 2021-06-06
|
|
||||||
* When matching path parameters, `%25` is now kept in the percent-encoded form; no longer decoded to `%`. [#357]
|
|
||||||
* Path tail patterns now match new lines (`\n`) in request URL. [#360]
|
|
||||||
* Fixed a safety bug where `Path` could return a malformed string after percent decoding. [#359]
|
|
||||||
* Methods `Path::{add, add_static}` now take `impl Into<Cow<'static, str>>`. [#345]
|
|
||||||
|
|
||||||
[#345]: https://github.com/actix/actix-net/pull/345
|
|
||||||
[#357]: https://github.com/actix/actix-net/pull/357
|
|
||||||
[#359]: https://github.com/actix/actix-net/pull/359
|
|
||||||
[#360]: https://github.com/actix/actix-net/pull/360
|
|
||||||
|
|
||||||
## 0.3.0 - 2019-12-31
|
|
||||||
* Version was yanked previously. See https://crates.io/crates/actix-router/0.3.0
|
|
||||||
|
|
||||||
|
|
||||||
## 0.2.7 - 2021-02-06
|
|
||||||
* Add `Router::recognize_checked` [#247]
|
|
||||||
|
|
||||||
[#247]: https://github.com/actix/actix-net/pull/247
|
|
||||||
|
|
||||||
|
|
||||||
## 0.2.6 - 2021-01-09
|
|
||||||
* Use `bytestring` version range compatible with Bytes v1.0. [#246]
|
|
||||||
|
|
||||||
[#246]: https://github.com/actix/actix-net/pull/246
|
|
||||||
|
|
||||||
|
|
||||||
## 0.2.5 - 2020-09-20
|
|
||||||
* Fix `from_hex()` method
|
|
||||||
|
|
||||||
|
|
||||||
## 0.2.4 - 2019-12-31
|
|
||||||
* Add `ResourceDef::resource_path_named()` path generation method
|
|
||||||
|
|
||||||
|
|
||||||
## 0.2.3 - 2019-12-25
|
|
||||||
* Add impl `IntoPattern` for `&String`
|
|
||||||
|
|
||||||
|
|
||||||
## 0.2.2 - 2019-12-25
|
|
||||||
* Use `IntoPattern` for `RouterBuilder::path()`
|
|
||||||
|
|
||||||
|
|
||||||
## 0.2.1 - 2019-12-25
|
|
||||||
* Add `IntoPattern` trait
|
|
||||||
* Add multi-pattern resources
|
|
||||||
|
|
||||||
|
|
||||||
## 0.2.0 - 2019-12-07
|
|
||||||
* Update http to 0.2
|
|
||||||
* Update regex to 1.3
|
|
||||||
* Use bytestring instead of string
|
|
||||||
|
|
||||||
|
|
||||||
## 0.1.5 - 2019-05-15
|
|
||||||
* Remove debug prints
|
|
||||||
|
|
||||||
|
|
||||||
## 0.1.4 - 2019-05-15
|
|
||||||
* Fix checked resource match
|
|
||||||
|
|
||||||
|
|
||||||
## 0.1.3 - 2019-04-22
|
|
||||||
* Added support for `remainder match` (i.e "/path/{tail}*")
|
|
||||||
|
|
||||||
|
|
||||||
## 0.1.2 - 2019-04-07
|
|
||||||
* Export `Quoter` type
|
|
||||||
* Allow to reset `Path` instance
|
|
||||||
|
|
||||||
|
|
||||||
## 0.1.1 - 2019-04-03
|
|
||||||
* Get dynamic segment by name instead of iterator.
|
|
||||||
|
|
||||||
|
|
||||||
## 0.1.0 - 2019-03-09
|
|
||||||
* Initial release
|
|
@@ -1,27 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "actix-router"
|
|
||||||
version = "0.4.0"
|
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
|
||||||
description = "Resource path matching library"
|
|
||||||
keywords = ["actix", "router", "routing"]
|
|
||||||
repository = "https://github.com/actix/actix-net"
|
|
||||||
license = "MIT OR Apache-2.0"
|
|
||||||
edition = "2018"
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
name = "actix_router"
|
|
||||||
path = "src/lib.rs"
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["http"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
regex = "1.3.1"
|
|
||||||
serde = "1.0.104"
|
|
||||||
bytestring = ">=0.1.5, <2"
|
|
||||||
log = "0.4.8"
|
|
||||||
http = { version = "0.2.2", optional = true }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
http = "0.2.2"
|
|
||||||
serde_derive = "1.0"
|
|
@@ -1 +0,0 @@
|
|||||||
../LICENSE-APACHE
|
|
@@ -1 +0,0 @@
|
|||||||
../LICENSE-MIT
|
|
@@ -1,717 +0,0 @@
|
|||||||
use serde::de::{self, Deserializer, Error as DeError, Visitor};
|
|
||||||
use serde::forward_to_deserialize_any;
|
|
||||||
|
|
||||||
use crate::path::{Path, PathIter};
|
|
||||||
use crate::ResourcePath;
|
|
||||||
|
|
||||||
macro_rules! unsupported_type {
|
|
||||||
($trait_fn:ident, $name:expr) => {
|
|
||||||
fn $trait_fn<V>(self, _: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
Err(de::value::Error::custom(concat!(
|
|
||||||
"unsupported type: ",
|
|
||||||
$name
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! parse_single_value {
|
|
||||||
($trait_fn:ident, $visit_fn:ident, $tp:tt) => {
|
|
||||||
fn $trait_fn<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
if self.path.len() != 1 {
|
|
||||||
Err(de::value::Error::custom(
|
|
||||||
format!("wrong number of parameters: {} expected 1", self.path.len())
|
|
||||||
.as_str(),
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
let v = self.path[0].parse().map_err(|_| {
|
|
||||||
de::value::Error::custom(format!(
|
|
||||||
"can not parse {:?} to a {}",
|
|
||||||
&self.path[0], $tp
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
visitor.$visit_fn(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PathDeserializer<'de, T: ResourcePath> {
|
|
||||||
path: &'de Path<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de, T: ResourcePath + 'de> PathDeserializer<'de, T> {
|
|
||||||
pub fn new(path: &'de Path<T>) -> Self {
|
|
||||||
PathDeserializer { path }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de, T: ResourcePath + 'de> Deserializer<'de> for PathDeserializer<'de, T> {
|
|
||||||
type Error = de::value::Error;
|
|
||||||
|
|
||||||
fn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
visitor.visit_map(ParamsDeserializer {
|
|
||||||
params: self.path.iter(),
|
|
||||||
current: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_struct<V>(
|
|
||||||
self,
|
|
||||||
_: &'static str,
|
|
||||||
_: &'static [&'static str],
|
|
||||||
visitor: V,
|
|
||||||
) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
self.deserialize_map(visitor)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
visitor.visit_unit()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_unit_struct<V>(
|
|
||||||
self,
|
|
||||||
_: &'static str,
|
|
||||||
visitor: V,
|
|
||||||
) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
self.deserialize_unit(visitor)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_newtype_struct<V>(
|
|
||||||
self,
|
|
||||||
_: &'static str,
|
|
||||||
visitor: V,
|
|
||||||
) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
visitor.visit_newtype_struct(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_tuple<V>(self, len: usize, visitor: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
if self.path.len() < len {
|
|
||||||
Err(de::value::Error::custom(
|
|
||||||
format!(
|
|
||||||
"wrong number of parameters: {} expected {}",
|
|
||||||
self.path.len(),
|
|
||||||
len
|
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
visitor.visit_seq(ParamsSeq {
|
|
||||||
params: self.path.iter(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_tuple_struct<V>(
|
|
||||||
self,
|
|
||||||
_: &'static str,
|
|
||||||
len: usize,
|
|
||||||
visitor: V,
|
|
||||||
) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
if self.path.len() < len {
|
|
||||||
Err(de::value::Error::custom(
|
|
||||||
format!(
|
|
||||||
"wrong number of parameters: {} expected {}",
|
|
||||||
self.path.len(),
|
|
||||||
len
|
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
visitor.visit_seq(ParamsSeq {
|
|
||||||
params: self.path.iter(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_enum<V>(
|
|
||||||
self,
|
|
||||||
_: &'static str,
|
|
||||||
_: &'static [&'static str],
|
|
||||||
visitor: V,
|
|
||||||
) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
if self.path.is_empty() {
|
|
||||||
Err(de::value::Error::custom("expected at least one parameters"))
|
|
||||||
} else {
|
|
||||||
visitor.visit_enum(ValueEnum {
|
|
||||||
value: &self.path[0],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
if self.path.len() != 1 {
|
|
||||||
Err(de::value::Error::custom(
|
|
||||||
format!("wrong number of parameters: {} expected 1", self.path.len()).as_str(),
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
visitor.visit_str(&self.path[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
visitor.visit_seq(ParamsSeq {
|
|
||||||
params: self.path.iter(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
unsupported_type!(deserialize_any, "'any'");
|
|
||||||
unsupported_type!(deserialize_bytes, "bytes");
|
|
||||||
unsupported_type!(deserialize_option, "Option<T>");
|
|
||||||
unsupported_type!(deserialize_identifier, "identifier");
|
|
||||||
unsupported_type!(deserialize_ignored_any, "ignored_any");
|
|
||||||
|
|
||||||
parse_single_value!(deserialize_bool, visit_bool, "bool");
|
|
||||||
parse_single_value!(deserialize_i8, visit_i8, "i8");
|
|
||||||
parse_single_value!(deserialize_i16, visit_i16, "i16");
|
|
||||||
parse_single_value!(deserialize_i32, visit_i32, "i32");
|
|
||||||
parse_single_value!(deserialize_i64, visit_i64, "i64");
|
|
||||||
parse_single_value!(deserialize_u8, visit_u8, "u8");
|
|
||||||
parse_single_value!(deserialize_u16, visit_u16, "u16");
|
|
||||||
parse_single_value!(deserialize_u32, visit_u32, "u32");
|
|
||||||
parse_single_value!(deserialize_u64, visit_u64, "u64");
|
|
||||||
parse_single_value!(deserialize_f32, visit_f32, "f32");
|
|
||||||
parse_single_value!(deserialize_f64, visit_f64, "f64");
|
|
||||||
parse_single_value!(deserialize_string, visit_string, "String");
|
|
||||||
parse_single_value!(deserialize_byte_buf, visit_string, "String");
|
|
||||||
parse_single_value!(deserialize_char, visit_char, "char");
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ParamsDeserializer<'de, T: ResourcePath> {
|
|
||||||
params: PathIter<'de, T>,
|
|
||||||
current: Option<(&'de str, &'de str)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de, T: ResourcePath> de::MapAccess<'de> for ParamsDeserializer<'de, T> {
|
|
||||||
type Error = de::value::Error;
|
|
||||||
|
|
||||||
fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Self::Error>
|
|
||||||
where
|
|
||||||
K: de::DeserializeSeed<'de>,
|
|
||||||
{
|
|
||||||
self.current = self.params.next().map(|ref item| (item.0, item.1));
|
|
||||||
match self.current {
|
|
||||||
Some((key, _)) => Ok(Some(seed.deserialize(Key { key })?)),
|
|
||||||
None => Ok(None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: de::DeserializeSeed<'de>,
|
|
||||||
{
|
|
||||||
if let Some((_, value)) = self.current.take() {
|
|
||||||
seed.deserialize(Value { value })
|
|
||||||
} else {
|
|
||||||
Err(de::value::Error::custom("unexpected item"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Key<'de> {
|
|
||||||
key: &'de str,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserializer<'de> for Key<'de> {
|
|
||||||
type Error = de::value::Error;
|
|
||||||
|
|
||||||
fn deserialize_identifier<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
visitor.visit_str(self.key)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_any<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
Err(de::value::Error::custom("Unexpected"))
|
|
||||||
}
|
|
||||||
|
|
||||||
forward_to_deserialize_any! {
|
|
||||||
bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes
|
|
||||||
byte_buf option unit unit_struct newtype_struct seq tuple
|
|
||||||
tuple_struct map struct enum ignored_any
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! parse_value {
|
|
||||||
($trait_fn:ident, $visit_fn:ident, $tp:tt) => {
|
|
||||||
fn $trait_fn<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
let v = self.value.parse().map_err(|_| {
|
|
||||||
de::value::Error::custom(format!("can not parse {:?} to a {}", self.value, $tp))
|
|
||||||
})?;
|
|
||||||
visitor.$visit_fn(v)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Value<'de> {
|
|
||||||
value: &'de str,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserializer<'de> for Value<'de> {
|
|
||||||
type Error = de::value::Error;
|
|
||||||
|
|
||||||
parse_value!(deserialize_bool, visit_bool, "bool");
|
|
||||||
parse_value!(deserialize_i8, visit_i8, "i8");
|
|
||||||
parse_value!(deserialize_i16, visit_i16, "i16");
|
|
||||||
parse_value!(deserialize_i32, visit_i32, "i16");
|
|
||||||
parse_value!(deserialize_i64, visit_i64, "i64");
|
|
||||||
parse_value!(deserialize_u8, visit_u8, "u8");
|
|
||||||
parse_value!(deserialize_u16, visit_u16, "u16");
|
|
||||||
parse_value!(deserialize_u32, visit_u32, "u32");
|
|
||||||
parse_value!(deserialize_u64, visit_u64, "u64");
|
|
||||||
parse_value!(deserialize_f32, visit_f32, "f32");
|
|
||||||
parse_value!(deserialize_f64, visit_f64, "f64");
|
|
||||||
parse_value!(deserialize_string, visit_string, "String");
|
|
||||||
parse_value!(deserialize_byte_buf, visit_string, "String");
|
|
||||||
parse_value!(deserialize_char, visit_char, "char");
|
|
||||||
|
|
||||||
fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
visitor.visit_unit()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
visitor.visit_unit()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_unit_struct<V>(
|
|
||||||
self,
|
|
||||||
_: &'static str,
|
|
||||||
visitor: V,
|
|
||||||
) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
visitor.visit_unit()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
visitor.visit_borrowed_bytes(self.value.as_bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
visitor.visit_borrowed_str(self.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
visitor.visit_some(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_enum<V>(
|
|
||||||
self,
|
|
||||||
_: &'static str,
|
|
||||||
_: &'static [&'static str],
|
|
||||||
visitor: V,
|
|
||||||
) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
visitor.visit_enum(ValueEnum { value: self.value })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_newtype_struct<V>(
|
|
||||||
self,
|
|
||||||
_: &'static str,
|
|
||||||
visitor: V,
|
|
||||||
) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
visitor.visit_newtype_struct(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_tuple<V>(self, _: usize, _: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
Err(de::value::Error::custom("unsupported type: tuple"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_struct<V>(
|
|
||||||
self,
|
|
||||||
_: &'static str,
|
|
||||||
_: &'static [&'static str],
|
|
||||||
_: V,
|
|
||||||
) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
Err(de::value::Error::custom("unsupported type: struct"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_tuple_struct<V>(
|
|
||||||
self,
|
|
||||||
_: &'static str,
|
|
||||||
_: usize,
|
|
||||||
_: V,
|
|
||||||
) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
Err(de::value::Error::custom("unsupported type: tuple struct"))
|
|
||||||
}
|
|
||||||
|
|
||||||
unsupported_type!(deserialize_any, "any");
|
|
||||||
unsupported_type!(deserialize_seq, "seq");
|
|
||||||
unsupported_type!(deserialize_map, "map");
|
|
||||||
unsupported_type!(deserialize_identifier, "identifier");
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ParamsSeq<'de, T: ResourcePath> {
|
|
||||||
params: PathIter<'de, T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de, T: ResourcePath> de::SeqAccess<'de> for ParamsSeq<'de, T> {
|
|
||||||
type Error = de::value::Error;
|
|
||||||
|
|
||||||
fn next_element_seed<U>(&mut self, seed: U) -> Result<Option<U::Value>, Self::Error>
|
|
||||||
where
|
|
||||||
U: de::DeserializeSeed<'de>,
|
|
||||||
{
|
|
||||||
match self.params.next() {
|
|
||||||
Some(item) => Ok(Some(seed.deserialize(Value { value: item.1 })?)),
|
|
||||||
None => Ok(None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ValueEnum<'de> {
|
|
||||||
value: &'de str,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> de::EnumAccess<'de> for ValueEnum<'de> {
|
|
||||||
type Error = de::value::Error;
|
|
||||||
type Variant = UnitVariant;
|
|
||||||
|
|
||||||
fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error>
|
|
||||||
where
|
|
||||||
V: de::DeserializeSeed<'de>,
|
|
||||||
{
|
|
||||||
Ok((seed.deserialize(Key { key: self.value })?, UnitVariant))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct UnitVariant;
|
|
||||||
|
|
||||||
impl<'de> de::VariantAccess<'de> for UnitVariant {
|
|
||||||
type Error = de::value::Error;
|
|
||||||
|
|
||||||
fn unit_variant(self) -> Result<(), Self::Error> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn newtype_variant_seed<T>(self, _seed: T) -> Result<T::Value, Self::Error>
|
|
||||||
where
|
|
||||||
T: de::DeserializeSeed<'de>,
|
|
||||||
{
|
|
||||||
Err(de::value::Error::custom("not supported"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tuple_variant<V>(self, _len: usize, _visitor: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
Err(de::value::Error::custom("not supported"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn struct_variant<V>(
|
|
||||||
self,
|
|
||||||
_: &'static [&'static str],
|
|
||||||
_: V,
|
|
||||||
) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
Err(de::value::Error::custom("not supported"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use serde::de;
|
|
||||||
use serde_derive::Deserialize;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::path::Path;
|
|
||||||
use crate::router::Router;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct MyStruct {
|
|
||||||
key: String,
|
|
||||||
value: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct Id {
|
|
||||||
_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct Test1(String, u32);
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct Test2 {
|
|
||||||
key: String,
|
|
||||||
value: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, PartialEq)]
|
|
||||||
#[serde(rename_all = "lowercase")]
|
|
||||||
enum TestEnum {
|
|
||||||
Val1,
|
|
||||||
Val2,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct Test3 {
|
|
||||||
val: TestEnum,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_request_extract() {
|
|
||||||
let mut router = Router::<()>::build();
|
|
||||||
router.path("/{key}/{value}/", ());
|
|
||||||
let router = router.finish();
|
|
||||||
|
|
||||||
let mut path = Path::new("/name/user1/");
|
|
||||||
assert!(router.recognize(&mut path).is_some());
|
|
||||||
|
|
||||||
let s: MyStruct = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
|
|
||||||
assert_eq!(s.key, "name");
|
|
||||||
assert_eq!(s.value, "user1");
|
|
||||||
|
|
||||||
let s: (String, String) =
|
|
||||||
de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
|
|
||||||
assert_eq!(s.0, "name");
|
|
||||||
assert_eq!(s.1, "user1");
|
|
||||||
|
|
||||||
let mut router = Router::<()>::build();
|
|
||||||
router.path("/{key}/{value}/", ());
|
|
||||||
let router = router.finish();
|
|
||||||
|
|
||||||
let mut path = Path::new("/name/32/");
|
|
||||||
assert!(router.recognize(&mut path).is_some());
|
|
||||||
|
|
||||||
let s: Test1 = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
|
|
||||||
assert_eq!(s.0, "name");
|
|
||||||
assert_eq!(s.1, 32);
|
|
||||||
|
|
||||||
let s: Test2 = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
|
|
||||||
assert_eq!(s.key, "name");
|
|
||||||
assert_eq!(s.value, 32);
|
|
||||||
|
|
||||||
let s: (String, u8) =
|
|
||||||
de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
|
|
||||||
assert_eq!(s.0, "name");
|
|
||||||
assert_eq!(s.1, 32);
|
|
||||||
|
|
||||||
let res: Vec<String> =
|
|
||||||
de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
|
|
||||||
assert_eq!(res[0], "name".to_owned());
|
|
||||||
assert_eq!(res[1], "32".to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_extract_path_single() {
|
|
||||||
let mut router = Router::<()>::build();
|
|
||||||
router.path("/{value}/", ());
|
|
||||||
let router = router.finish();
|
|
||||||
|
|
||||||
let mut path = Path::new("/32/");
|
|
||||||
assert!(router.recognize(&mut path).is_some());
|
|
||||||
let i: i8 = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
|
|
||||||
assert_eq!(i, 32);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_extract_enum() {
|
|
||||||
let mut router = Router::<()>::build();
|
|
||||||
router.path("/{val}/", ());
|
|
||||||
let router = router.finish();
|
|
||||||
|
|
||||||
let mut path = Path::new("/val1/");
|
|
||||||
assert!(router.recognize(&mut path).is_some());
|
|
||||||
let i: TestEnum = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
|
|
||||||
assert_eq!(i, TestEnum::Val1);
|
|
||||||
|
|
||||||
let mut router = Router::<()>::build();
|
|
||||||
router.path("/{val1}/{val2}/", ());
|
|
||||||
let router = router.finish();
|
|
||||||
|
|
||||||
let mut path = Path::new("/val1/val2/");
|
|
||||||
assert!(router.recognize(&mut path).is_some());
|
|
||||||
let i: (TestEnum, TestEnum) =
|
|
||||||
de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
|
|
||||||
assert_eq!(i, (TestEnum::Val1, TestEnum::Val2));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_extract_enum_value() {
|
|
||||||
let mut router = Router::<()>::build();
|
|
||||||
router.path("/{val}/", ());
|
|
||||||
let router = router.finish();
|
|
||||||
|
|
||||||
let mut path = Path::new("/val1/");
|
|
||||||
assert!(router.recognize(&mut path).is_some());
|
|
||||||
let i: Test3 = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
|
|
||||||
assert_eq!(i.val, TestEnum::Val1);
|
|
||||||
|
|
||||||
let mut path = Path::new("/val3/");
|
|
||||||
assert!(router.recognize(&mut path).is_some());
|
|
||||||
let i: Result<Test3, de::value::Error> =
|
|
||||||
de::Deserialize::deserialize(PathDeserializer::new(&path));
|
|
||||||
assert!(i.is_err());
|
|
||||||
assert!(format!("{:?}", i).contains("unknown variant"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_extract_errors() {
|
|
||||||
let mut router = Router::<()>::build();
|
|
||||||
router.path("/{value}/", ());
|
|
||||||
let router = router.finish();
|
|
||||||
|
|
||||||
let mut path = Path::new("/name/");
|
|
||||||
assert!(router.recognize(&mut path).is_some());
|
|
||||||
|
|
||||||
let s: Result<Test1, de::value::Error> =
|
|
||||||
de::Deserialize::deserialize(PathDeserializer::new(&path));
|
|
||||||
assert!(s.is_err());
|
|
||||||
assert!(format!("{:?}", s).contains("wrong number of parameters"));
|
|
||||||
|
|
||||||
let s: Result<Test2, de::value::Error> =
|
|
||||||
de::Deserialize::deserialize(PathDeserializer::new(&path));
|
|
||||||
assert!(s.is_err());
|
|
||||||
assert!(format!("{:?}", s).contains("can not parse"));
|
|
||||||
|
|
||||||
let s: Result<(String, String), de::value::Error> =
|
|
||||||
de::Deserialize::deserialize(PathDeserializer::new(&path));
|
|
||||||
assert!(s.is_err());
|
|
||||||
assert!(format!("{:?}", s).contains("wrong number of parameters"));
|
|
||||||
|
|
||||||
let s: Result<u32, de::value::Error> =
|
|
||||||
de::Deserialize::deserialize(PathDeserializer::new(&path));
|
|
||||||
assert!(s.is_err());
|
|
||||||
assert!(format!("{:?}", s).contains("can not parse"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn test_extract_path_decode() {
|
|
||||||
// let mut router = Router::<()>::default();
|
|
||||||
// router.register_resource(Resource::new(ResourceDef::new("/{value}/")));
|
|
||||||
|
|
||||||
// macro_rules! test_single_value {
|
|
||||||
// ($value:expr, $expected:expr) => {{
|
|
||||||
// let req = TestRequest::with_uri($value).finish();
|
|
||||||
// let info = router.recognize(&req, &(), 0);
|
|
||||||
// let req = req.with_route_info(info);
|
|
||||||
// assert_eq!(
|
|
||||||
// *Path::<String>::from_request(&req, &PathConfig::default()).unwrap(),
|
|
||||||
// $expected
|
|
||||||
// );
|
|
||||||
// }};
|
|
||||||
// }
|
|
||||||
|
|
||||||
// test_single_value!("/%25/", "%");
|
|
||||||
// test_single_value!("/%40%C2%A3%24%25%5E%26%2B%3D/", "@£$%^&+=");
|
|
||||||
// test_single_value!("/%2B/", "+");
|
|
||||||
// test_single_value!("/%252B/", "%2B");
|
|
||||||
// test_single_value!("/%2F/", "/");
|
|
||||||
// test_single_value!("/%252F/", "%2F");
|
|
||||||
// test_single_value!(
|
|
||||||
// "/http%3A%2F%2Flocalhost%3A80%2Ffoo/",
|
|
||||||
// "http://localhost:80/foo"
|
|
||||||
// );
|
|
||||||
// test_single_value!("/%2Fvar%2Flog%2Fsyslog/", "/var/log/syslog");
|
|
||||||
// test_single_value!(
|
|
||||||
// "/http%3A%2F%2Flocalhost%3A80%2Ffile%2F%252Fvar%252Flog%252Fsyslog/",
|
|
||||||
// "http://localhost:80/file/%2Fvar%2Flog%2Fsyslog"
|
|
||||||
// );
|
|
||||||
|
|
||||||
// let req = TestRequest::with_uri("/%25/7/?id=test").finish();
|
|
||||||
|
|
||||||
// let mut router = Router::<()>::default();
|
|
||||||
// router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/")));
|
|
||||||
// let info = router.recognize(&req, &(), 0);
|
|
||||||
// let req = req.with_route_info(info);
|
|
||||||
|
|
||||||
// let s = Path::<Test2>::from_request(&req, &PathConfig::default()).unwrap();
|
|
||||||
// assert_eq!(s.key, "%");
|
|
||||||
// assert_eq!(s.value, 7);
|
|
||||||
|
|
||||||
// let s = Path::<(String, String)>::from_request(&req, &PathConfig::default()).unwrap();
|
|
||||||
// assert_eq!(s.0, "%");
|
|
||||||
// assert_eq!(s.1, "7");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn test_extract_path_no_decode() {
|
|
||||||
// let mut router = Router::<()>::default();
|
|
||||||
// router.register_resource(Resource::new(ResourceDef::new("/{value}/")));
|
|
||||||
|
|
||||||
// let req = TestRequest::with_uri("/%25/").finish();
|
|
||||||
// let info = router.recognize(&req, &(), 0);
|
|
||||||
// let req = req.with_route_info(info);
|
|
||||||
// assert_eq!(
|
|
||||||
// *Path::<String>::from_request(&req, &&PathConfig::default().disable_decoding())
|
|
||||||
// .unwrap(),
|
|
||||||
// "%25"
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
}
|
|
@@ -1,152 +0,0 @@
|
|||||||
//! Resource path matching library.
|
|
||||||
|
|
||||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
|
||||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
|
||||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
|
||||||
|
|
||||||
mod de;
|
|
||||||
mod path;
|
|
||||||
mod resource;
|
|
||||||
mod router;
|
|
||||||
|
|
||||||
pub use self::de::PathDeserializer;
|
|
||||||
pub use self::path::Path;
|
|
||||||
pub use self::resource::ResourceDef;
|
|
||||||
pub use self::router::{ResourceInfo, Router, RouterBuilder};
|
|
||||||
|
|
||||||
pub trait Resource<T: ResourcePath> {
|
|
||||||
fn resource_path(&mut self) -> &mut Path<T>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ResourcePath {
|
|
||||||
fn path(&self) -> &str;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ResourcePath for String {
|
|
||||||
fn path(&self) -> &str {
|
|
||||||
self.as_str()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ResourcePath for &'a str {
|
|
||||||
fn path(&self) -> &str {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ResourcePath for bytestring::ByteString {
|
|
||||||
fn path(&self) -> &str {
|
|
||||||
&*self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper trait for type that could be converted to path pattern
|
|
||||||
pub trait IntoPattern {
|
|
||||||
fn is_single(&self) -> bool;
|
|
||||||
|
|
||||||
fn patterns(&self) -> Vec<String>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoPattern for String {
|
|
||||||
fn is_single(&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn patterns(&self) -> Vec<String> {
|
|
||||||
vec![self.clone()]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> IntoPattern for &'a String {
|
|
||||||
fn is_single(&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn patterns(&self) -> Vec<String> {
|
|
||||||
vec![self.as_str().to_string()]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> IntoPattern for &'a str {
|
|
||||||
fn is_single(&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn patterns(&self) -> Vec<String> {
|
|
||||||
vec![(*self).to_string()]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: AsRef<str>> IntoPattern for Vec<T> {
|
|
||||||
fn is_single(&self) -> bool {
|
|
||||||
self.len() == 1
|
|
||||||
}
|
|
||||||
|
|
||||||
fn patterns(&self) -> Vec<String> {
|
|
||||||
self.iter().map(|v| v.as_ref().to_string()).collect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! array_patterns (($tp:ty, $num:tt) => {
|
|
||||||
impl IntoPattern for [$tp; $num] {
|
|
||||||
fn is_single(&self) -> bool {
|
|
||||||
$num == 1
|
|
||||||
}
|
|
||||||
|
|
||||||
fn patterns(&self) -> Vec<String> {
|
|
||||||
self.iter().map(|v| v.to_string()).collect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
array_patterns!(&str, 1);
|
|
||||||
array_patterns!(&str, 2);
|
|
||||||
array_patterns!(&str, 3);
|
|
||||||
array_patterns!(&str, 4);
|
|
||||||
array_patterns!(&str, 5);
|
|
||||||
array_patterns!(&str, 6);
|
|
||||||
array_patterns!(&str, 7);
|
|
||||||
array_patterns!(&str, 8);
|
|
||||||
array_patterns!(&str, 9);
|
|
||||||
array_patterns!(&str, 10);
|
|
||||||
array_patterns!(&str, 11);
|
|
||||||
array_patterns!(&str, 12);
|
|
||||||
array_patterns!(&str, 13);
|
|
||||||
array_patterns!(&str, 14);
|
|
||||||
array_patterns!(&str, 15);
|
|
||||||
array_patterns!(&str, 16);
|
|
||||||
|
|
||||||
array_patterns!(String, 1);
|
|
||||||
array_patterns!(String, 2);
|
|
||||||
array_patterns!(String, 3);
|
|
||||||
array_patterns!(String, 4);
|
|
||||||
array_patterns!(String, 5);
|
|
||||||
array_patterns!(String, 6);
|
|
||||||
array_patterns!(String, 7);
|
|
||||||
array_patterns!(String, 8);
|
|
||||||
array_patterns!(String, 9);
|
|
||||||
array_patterns!(String, 10);
|
|
||||||
array_patterns!(String, 11);
|
|
||||||
array_patterns!(String, 12);
|
|
||||||
array_patterns!(String, 13);
|
|
||||||
array_patterns!(String, 14);
|
|
||||||
array_patterns!(String, 15);
|
|
||||||
array_patterns!(String, 16);
|
|
||||||
|
|
||||||
#[cfg(feature = "http")]
|
|
||||||
mod url;
|
|
||||||
|
|
||||||
#[cfg(feature = "http")]
|
|
||||||
pub use self::url::{Quoter, Url};
|
|
||||||
|
|
||||||
#[cfg(feature = "http")]
|
|
||||||
mod http_support {
|
|
||||||
use super::ResourcePath;
|
|
||||||
use http::Uri;
|
|
||||||
|
|
||||||
impl ResourcePath for Uri {
|
|
||||||
fn path(&self) -> &str {
|
|
||||||
self.path()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,235 +0,0 @@
|
|||||||
use std::borrow::Cow;
|
|
||||||
use std::ops::Index;
|
|
||||||
|
|
||||||
use serde::de;
|
|
||||||
|
|
||||||
use crate::de::PathDeserializer;
|
|
||||||
use crate::{Resource, ResourcePath};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub(crate) enum PathItem {
|
|
||||||
Static(Cow<'static, str>),
|
|
||||||
Segment(u16, u16),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for PathItem {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Static(Cow::Borrowed(""))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resource path match information
|
|
||||||
///
|
|
||||||
/// If resource path contains variable patterns, `Path` stores them.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Path<T> {
|
|
||||||
path: T,
|
|
||||||
pub(crate) skip: u16,
|
|
||||||
pub(crate) segments: Vec<(Cow<'static, str>, PathItem)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Default> Default for Path<T> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Path {
|
|
||||||
path: T::default(),
|
|
||||||
skip: 0,
|
|
||||||
segments: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Clone> Clone for Path<T> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Path {
|
|
||||||
path: self.path.clone(),
|
|
||||||
skip: self.skip,
|
|
||||||
segments: self.segments.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: ResourcePath> Path<T> {
|
|
||||||
pub fn new(path: T) -> Path<T> {
|
|
||||||
Path {
|
|
||||||
path,
|
|
||||||
skip: 0,
|
|
||||||
segments: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get reference to inner path instance
|
|
||||||
#[inline]
|
|
||||||
pub fn get_ref(&self) -> &T {
|
|
||||||
&self.path
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get mutable reference to inner path instance
|
|
||||||
#[inline]
|
|
||||||
pub fn get_mut(&mut self) -> &mut T {
|
|
||||||
&mut self.path
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Path
|
|
||||||
#[inline]
|
|
||||||
pub fn path(&self) -> &str {
|
|
||||||
let skip = self.skip as usize;
|
|
||||||
let path = self.path.path();
|
|
||||||
if skip <= path.len() {
|
|
||||||
&path[skip..]
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set new path
|
|
||||||
#[inline]
|
|
||||||
pub fn set(&mut self, path: T) {
|
|
||||||
self.skip = 0;
|
|
||||||
self.path = path;
|
|
||||||
self.segments.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reset state
|
|
||||||
#[inline]
|
|
||||||
pub fn reset(&mut self) {
|
|
||||||
self.skip = 0;
|
|
||||||
self.segments.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Skip first `n` chars in path
|
|
||||||
#[inline]
|
|
||||||
pub fn skip(&mut self, n: u16) {
|
|
||||||
self.skip += n;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn add(&mut self, name: impl Into<Cow<'static, str>>, value: PathItem) {
|
|
||||||
match value {
|
|
||||||
PathItem::Static(s) => self.segments.push((name.into(), PathItem::Static(s))),
|
|
||||||
PathItem::Segment(begin, end) => self.segments.push((
|
|
||||||
name.into(),
|
|
||||||
PathItem::Segment(self.skip + begin, self.skip + end),
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub fn add_static(
|
|
||||||
&mut self,
|
|
||||||
name: impl Into<Cow<'static, str>>,
|
|
||||||
value: impl Into<Cow<'static, str>>,
|
|
||||||
) {
|
|
||||||
self.segments
|
|
||||||
.push((name.into(), PathItem::Static(value.into())));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if there are any matched patterns
|
|
||||||
#[inline]
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.segments.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check number of extracted parameters
|
|
||||||
#[inline]
|
|
||||||
pub fn len(&self) -> usize {
|
|
||||||
self.segments.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get matched parameter by name without type conversion
|
|
||||||
pub fn get(&self, key: &str) -> Option<&str> {
|
|
||||||
for item in self.segments.iter() {
|
|
||||||
if key == item.0 {
|
|
||||||
return match item.1 {
|
|
||||||
PathItem::Static(ref s) => Some(&s),
|
|
||||||
PathItem::Segment(s, e) => {
|
|
||||||
Some(&self.path.path()[(s as usize)..(e as usize)])
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if key == "tail" {
|
|
||||||
Some(&self.path.path()[(self.skip as usize)..])
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get unprocessed part of the path
|
|
||||||
pub fn unprocessed(&self) -> &str {
|
|
||||||
&self.path.path()[(self.skip as usize)..]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get matched parameter by name.
|
|
||||||
///
|
|
||||||
/// If keyed parameter is not available empty string is used as default
|
|
||||||
/// value.
|
|
||||||
pub fn query(&self, key: &str) -> &str {
|
|
||||||
if let Some(s) = self.get(key) {
|
|
||||||
s
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return iterator to items in parameter container
|
|
||||||
pub fn iter(&self) -> PathIter<'_, T> {
|
|
||||||
PathIter {
|
|
||||||
idx: 0,
|
|
||||||
params: self,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to deserialize matching parameters to a specified type `U`
|
|
||||||
pub fn load<'de, U: serde::Deserialize<'de>>(&'de self) -> Result<U, de::value::Error> {
|
|
||||||
de::Deserialize::deserialize(PathDeserializer::new(self))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct PathIter<'a, T> {
|
|
||||||
idx: usize,
|
|
||||||
params: &'a Path<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T: ResourcePath> Iterator for PathIter<'a, T> {
|
|
||||||
type Item = (&'a str, &'a str);
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn next(&mut self) -> Option<(&'a str, &'a str)> {
|
|
||||||
if self.idx < self.params.len() {
|
|
||||||
let idx = self.idx;
|
|
||||||
let res = match self.params.segments[idx].1 {
|
|
||||||
PathItem::Static(ref s) => &s,
|
|
||||||
PathItem::Segment(s, e) => &self.params.path.path()[(s as usize)..(e as usize)],
|
|
||||||
};
|
|
||||||
self.idx += 1;
|
|
||||||
return Some((&self.params.segments[idx].0, res));
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T: ResourcePath> Index<&'a str> for Path<T> {
|
|
||||||
type Output = str;
|
|
||||||
|
|
||||||
fn index(&self, name: &'a str) -> &str {
|
|
||||||
self.get(name)
|
|
||||||
.expect("Value for parameter is not available")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: ResourcePath> Index<usize> for Path<T> {
|
|
||||||
type Output = str;
|
|
||||||
|
|
||||||
fn index(&self, idx: usize) -> &str {
|
|
||||||
match self.segments[idx].1 {
|
|
||||||
PathItem::Static(ref s) => &s,
|
|
||||||
PathItem::Segment(s, e) => &self.path.path()[(s as usize)..(e as usize)],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: ResourcePath> Resource<T> for Path<T> {
|
|
||||||
fn resource_path(&mut self) -> &mut Self {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,965 +0,0 @@
|
|||||||
use std::cmp::min;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::hash::{Hash, Hasher};
|
|
||||||
use std::mem;
|
|
||||||
|
|
||||||
use regex::{escape, Regex, RegexSet};
|
|
||||||
|
|
||||||
use crate::path::{Path, PathItem};
|
|
||||||
use crate::{IntoPattern, Resource, ResourcePath};
|
|
||||||
|
|
||||||
const MAX_DYNAMIC_SEGMENTS: usize = 16;
|
|
||||||
|
|
||||||
/// Regex flags to allow '.' in regex to match '\n'
|
|
||||||
///
|
|
||||||
/// See the docs under: https://docs.rs/regex/1.5.4/regex/#grouping-and-flags
|
|
||||||
const REGEX_FLAGS: &str = "(?s-m)";
|
|
||||||
|
|
||||||
/// ResourceDef describes an entry in resources table
|
|
||||||
///
|
|
||||||
/// Resource definition can contain only 16 dynamic segments
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct ResourceDef {
|
|
||||||
id: u16,
|
|
||||||
tp: PatternType,
|
|
||||||
name: String,
|
|
||||||
pattern: String,
|
|
||||||
elements: Vec<PatternElement>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
enum PatternElement {
|
|
||||||
Str(String),
|
|
||||||
Var(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
#[allow(clippy::large_enum_variant)]
|
|
||||||
enum PatternType {
|
|
||||||
Static(String),
|
|
||||||
Prefix(String),
|
|
||||||
Dynamic(Regex, Vec<&'static str>, usize),
|
|
||||||
DynamicSet(RegexSet, Vec<(Regex, Vec<&'static str>, usize)>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ResourceDef {
|
|
||||||
/// Parse path pattern and create new `Pattern` instance.
|
|
||||||
///
|
|
||||||
/// Panics if path pattern is malformed.
|
|
||||||
pub fn new<T: IntoPattern>(path: T) -> Self {
|
|
||||||
if path.is_single() {
|
|
||||||
let patterns = path.patterns();
|
|
||||||
ResourceDef::with_prefix(&patterns[0], false)
|
|
||||||
} else {
|
|
||||||
let set = path.patterns();
|
|
||||||
let mut data = Vec::new();
|
|
||||||
let mut re_set = Vec::new();
|
|
||||||
|
|
||||||
for path in set {
|
|
||||||
let (pattern, _, _, len) = ResourceDef::parse(&path, false);
|
|
||||||
|
|
||||||
let re = match Regex::new(&pattern) {
|
|
||||||
Ok(re) => re,
|
|
||||||
Err(err) => panic!("Wrong path pattern: \"{}\" {}", path, err),
|
|
||||||
};
|
|
||||||
// actix creates one router per thread
|
|
||||||
let names: Vec<_> = re
|
|
||||||
.capture_names()
|
|
||||||
.filter_map(|name| {
|
|
||||||
name.map(|name| Box::leak(Box::new(name.to_owned())).as_str())
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
data.push((re, names, len));
|
|
||||||
re_set.push(pattern);
|
|
||||||
}
|
|
||||||
|
|
||||||
ResourceDef {
|
|
||||||
id: 0,
|
|
||||||
tp: PatternType::DynamicSet(RegexSet::new(re_set).unwrap(), data),
|
|
||||||
elements: Vec::new(),
|
|
||||||
name: String::new(),
|
|
||||||
pattern: "".to_owned(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse path pattern and create new `Pattern` instance.
|
|
||||||
///
|
|
||||||
/// Use `prefix` type instead of `static`.
|
|
||||||
///
|
|
||||||
/// Panics if path regex pattern is malformed.
|
|
||||||
pub fn prefix(path: &str) -> Self {
|
|
||||||
ResourceDef::with_prefix(path, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse path pattern and create new `Pattern` instance.
|
|
||||||
/// Inserts `/` to begging of the pattern.
|
|
||||||
///
|
|
||||||
///
|
|
||||||
/// Use `prefix` type instead of `static`.
|
|
||||||
///
|
|
||||||
/// Panics if path regex pattern is malformed.
|
|
||||||
pub fn root_prefix(path: &str) -> Self {
|
|
||||||
ResourceDef::with_prefix(&insert_slash(path), true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resource id
|
|
||||||
pub fn id(&self) -> u16 {
|
|
||||||
self.id
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set resource id
|
|
||||||
pub fn set_id(&mut self, id: u16) {
|
|
||||||
self.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse path pattern and create new `Pattern` instance with custom prefix
|
|
||||||
fn with_prefix(path: &str, for_prefix: bool) -> Self {
|
|
||||||
let path = path.to_owned();
|
|
||||||
let (pattern, elements, is_dynamic, len) = ResourceDef::parse(&path, for_prefix);
|
|
||||||
|
|
||||||
let tp = if is_dynamic {
|
|
||||||
let re = match Regex::new(&pattern) {
|
|
||||||
Ok(re) => re,
|
|
||||||
Err(err) => panic!("Wrong path pattern: \"{}\" {}", path, err),
|
|
||||||
};
|
|
||||||
// actix creates one router per thread
|
|
||||||
let names = re
|
|
||||||
.capture_names()
|
|
||||||
.filter_map(|name| {
|
|
||||||
name.map(|name| Box::leak(Box::new(name.to_owned())).as_str())
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
PatternType::Dynamic(re, names, len)
|
|
||||||
} else if for_prefix {
|
|
||||||
PatternType::Prefix(pattern)
|
|
||||||
} else {
|
|
||||||
PatternType::Static(pattern)
|
|
||||||
};
|
|
||||||
|
|
||||||
ResourceDef {
|
|
||||||
tp,
|
|
||||||
elements,
|
|
||||||
id: 0,
|
|
||||||
name: String::new(),
|
|
||||||
pattern: path,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resource pattern name
|
|
||||||
pub fn name(&self) -> &str {
|
|
||||||
&self.name
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mutable reference to a name of a resource definition.
|
|
||||||
pub fn name_mut(&mut self) -> &mut String {
|
|
||||||
&mut self.name
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Path pattern of the resource
|
|
||||||
pub fn pattern(&self) -> &str {
|
|
||||||
&self.pattern
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if path matches this pattern.
|
|
||||||
#[inline]
|
|
||||||
pub fn is_match(&self, path: &str) -> bool {
|
|
||||||
match self.tp {
|
|
||||||
PatternType::Static(ref s) => s == path,
|
|
||||||
PatternType::Prefix(ref s) => path.starts_with(s),
|
|
||||||
PatternType::Dynamic(ref re, _, _) => re.is_match(path),
|
|
||||||
PatternType::DynamicSet(ref re, _) => re.is_match(path),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Is prefix path a match against this resource.
|
|
||||||
pub fn is_prefix_match(&self, path: &str) -> Option<usize> {
|
|
||||||
let p_len = path.len();
|
|
||||||
let path = if path.is_empty() { "/" } else { path };
|
|
||||||
|
|
||||||
match self.tp {
|
|
||||||
PatternType::Static(ref s) => {
|
|
||||||
if s == path {
|
|
||||||
Some(p_len)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PatternType::Dynamic(ref re, _, len) => {
|
|
||||||
if let Some(captures) = re.captures(path) {
|
|
||||||
let mut pos = 0;
|
|
||||||
let mut passed = false;
|
|
||||||
for capture in captures.iter() {
|
|
||||||
if let Some(ref m) = capture {
|
|
||||||
if !passed {
|
|
||||||
passed = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
pos = m.end();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(pos + len)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PatternType::Prefix(ref s) => {
|
|
||||||
let len = if path == s {
|
|
||||||
s.len()
|
|
||||||
} else if path.starts_with(s)
|
|
||||||
&& (s.ends_with('/') || path.split_at(s.len()).1.starts_with('/'))
|
|
||||||
{
|
|
||||||
if s.ends_with('/') {
|
|
||||||
s.len() - 1
|
|
||||||
} else {
|
|
||||||
s.len()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
Some(min(p_len, len))
|
|
||||||
}
|
|
||||||
PatternType::DynamicSet(ref re, ref params) => {
|
|
||||||
if let Some(idx) = re.matches(path).into_iter().next() {
|
|
||||||
let (ref pattern, _, len) = params[idx];
|
|
||||||
if let Some(captures) = pattern.captures(path) {
|
|
||||||
let mut pos = 0;
|
|
||||||
let mut passed = false;
|
|
||||||
for capture in captures.iter() {
|
|
||||||
if let Some(ref m) = capture {
|
|
||||||
if !passed {
|
|
||||||
passed = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
pos = m.end();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(pos + len)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Is the given path and parameters a match against this pattern.
|
|
||||||
pub fn match_path<T: ResourcePath>(&self, path: &mut Path<T>) -> bool {
|
|
||||||
match self.tp {
|
|
||||||
PatternType::Static(ref s) => {
|
|
||||||
if s == path.path() {
|
|
||||||
path.skip(path.len() as u16);
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PatternType::Prefix(ref s) => {
|
|
||||||
let r_path = path.path();
|
|
||||||
let len = if s == r_path {
|
|
||||||
s.len()
|
|
||||||
} else if r_path.starts_with(s)
|
|
||||||
&& (s.ends_with('/') || r_path.split_at(s.len()).1.starts_with('/'))
|
|
||||||
{
|
|
||||||
if s.ends_with('/') {
|
|
||||||
s.len() - 1
|
|
||||||
} else {
|
|
||||||
s.len()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
let r_path_len = r_path.len();
|
|
||||||
path.skip(min(r_path_len, len) as u16);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
PatternType::Dynamic(ref re, ref names, len) => {
|
|
||||||
let mut pos = 0;
|
|
||||||
let mut segments: [PathItem; MAX_DYNAMIC_SEGMENTS] = Default::default();
|
|
||||||
|
|
||||||
if let Some(captures) = re.captures(path.path()) {
|
|
||||||
for (no, name) in names.iter().enumerate() {
|
|
||||||
if let Some(m) = captures.name(&name) {
|
|
||||||
pos = m.end();
|
|
||||||
segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16);
|
|
||||||
} else {
|
|
||||||
log::error!(
|
|
||||||
"Dynamic path match but not all segments found: {}",
|
|
||||||
name
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for i in 0..names.len() {
|
|
||||||
path.add(names[i], mem::take(&mut segments[i]));
|
|
||||||
}
|
|
||||||
path.skip((pos + len) as u16);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
PatternType::DynamicSet(ref re, ref params) => {
|
|
||||||
if let Some(idx) = re.matches(path.path()).into_iter().next() {
|
|
||||||
let (ref pattern, ref names, len) = params[idx];
|
|
||||||
let mut pos = 0;
|
|
||||||
let mut segments: [PathItem; MAX_DYNAMIC_SEGMENTS] = Default::default();
|
|
||||||
|
|
||||||
if let Some(captures) = pattern.captures(path.path()) {
|
|
||||||
for (no, name) in names.iter().enumerate() {
|
|
||||||
if let Some(m) = captures.name(&name) {
|
|
||||||
pos = m.end();
|
|
||||||
segments[no] =
|
|
||||||
PathItem::Segment(m.start() as u16, m.end() as u16);
|
|
||||||
} else {
|
|
||||||
log::error!(
|
|
||||||
"Dynamic path match but not all segments found: {}",
|
|
||||||
name
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for i in 0..names.len() {
|
|
||||||
path.add(names[i], mem::take(&mut segments[i]));
|
|
||||||
}
|
|
||||||
path.skip((pos + len) as u16);
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Is the given path and parameters a match against this pattern?
|
|
||||||
pub fn match_path_checked<R, T, F, U>(
|
|
||||||
&self,
|
|
||||||
res: &mut R,
|
|
||||||
check: &F,
|
|
||||||
user_data: &Option<U>,
|
|
||||||
) -> bool
|
|
||||||
where
|
|
||||||
T: ResourcePath,
|
|
||||||
R: Resource<T>,
|
|
||||||
F: Fn(&R, &Option<U>) -> bool,
|
|
||||||
{
|
|
||||||
match self.tp {
|
|
||||||
PatternType::Static(ref s) => {
|
|
||||||
if s == res.resource_path().path() && check(res, user_data) {
|
|
||||||
let path = res.resource_path();
|
|
||||||
path.skip(path.len() as u16);
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PatternType::Prefix(ref s) => {
|
|
||||||
let len = {
|
|
||||||
let r_path = res.resource_path().path();
|
|
||||||
if s == r_path {
|
|
||||||
s.len()
|
|
||||||
} else if r_path.starts_with(s)
|
|
||||||
&& (s.ends_with('/') || r_path.split_at(s.len()).1.starts_with('/'))
|
|
||||||
{
|
|
||||||
if s.ends_with('/') {
|
|
||||||
s.len() - 1
|
|
||||||
} else {
|
|
||||||
s.len()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if !check(res, user_data) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let path = res.resource_path();
|
|
||||||
path.skip(min(path.path().len(), len) as u16);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
PatternType::Dynamic(ref re, ref names, len) => {
|
|
||||||
let mut pos = 0;
|
|
||||||
let mut segments: [PathItem; MAX_DYNAMIC_SEGMENTS] = Default::default();
|
|
||||||
|
|
||||||
if let Some(captures) = re.captures(res.resource_path().path()) {
|
|
||||||
for (no, name) in names.iter().enumerate() {
|
|
||||||
if let Some(m) = captures.name(&name) {
|
|
||||||
pos = m.end();
|
|
||||||
segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16);
|
|
||||||
} else {
|
|
||||||
log::error!(
|
|
||||||
"Dynamic path match but not all segments found: {}",
|
|
||||||
name
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !check(res, user_data) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let path = res.resource_path();
|
|
||||||
for i in 0..names.len() {
|
|
||||||
path.add(names[i], mem::take(&mut segments[i]));
|
|
||||||
}
|
|
||||||
path.skip((pos + len) as u16);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
PatternType::DynamicSet(ref re, ref params) => {
|
|
||||||
let path = res.resource_path().path();
|
|
||||||
if let Some(idx) = re.matches(path).into_iter().next() {
|
|
||||||
let (ref pattern, ref names, len) = params[idx];
|
|
||||||
let mut pos = 0;
|
|
||||||
let mut segments: [PathItem; MAX_DYNAMIC_SEGMENTS] = Default::default();
|
|
||||||
|
|
||||||
if let Some(captures) = pattern.captures(path) {
|
|
||||||
for (no, name) in names.iter().enumerate() {
|
|
||||||
if let Some(m) = captures.name(&name) {
|
|
||||||
pos = m.end();
|
|
||||||
segments[no] =
|
|
||||||
PathItem::Segment(m.start() as u16, m.end() as u16);
|
|
||||||
} else {
|
|
||||||
log::error!(
|
|
||||||
"Dynamic path match but not all segments found: {}",
|
|
||||||
name
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !check(res, user_data) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let path = res.resource_path();
|
|
||||||
for i in 0..names.len() {
|
|
||||||
path.add(names[i], mem::take(&mut segments[i]));
|
|
||||||
}
|
|
||||||
path.skip((pos + len) as u16);
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Build resource path from elements. Returns `true` on success.
|
|
||||||
pub fn resource_path<U, I>(&self, path: &mut String, elements: &mut U) -> bool
|
|
||||||
where
|
|
||||||
U: Iterator<Item = I>,
|
|
||||||
I: AsRef<str>,
|
|
||||||
{
|
|
||||||
match self.tp {
|
|
||||||
PatternType::Prefix(ref p) => path.push_str(p),
|
|
||||||
PatternType::Static(ref p) => path.push_str(p),
|
|
||||||
PatternType::Dynamic(..) => {
|
|
||||||
for el in &self.elements {
|
|
||||||
match *el {
|
|
||||||
PatternElement::Str(ref s) => path.push_str(s),
|
|
||||||
PatternElement::Var(_) => {
|
|
||||||
if let Some(val) = elements.next() {
|
|
||||||
path.push_str(val.as_ref())
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PatternType::DynamicSet(..) => {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Build resource path from elements. Returns `true` on success.
|
|
||||||
pub fn resource_path_named<K, V, S>(
|
|
||||||
&self,
|
|
||||||
path: &mut String,
|
|
||||||
elements: &HashMap<K, V, S>,
|
|
||||||
) -> bool
|
|
||||||
where
|
|
||||||
K: std::borrow::Borrow<str> + Eq + Hash,
|
|
||||||
V: AsRef<str>,
|
|
||||||
S: std::hash::BuildHasher,
|
|
||||||
{
|
|
||||||
match self.tp {
|
|
||||||
PatternType::Prefix(ref p) => path.push_str(p),
|
|
||||||
PatternType::Static(ref p) => path.push_str(p),
|
|
||||||
PatternType::Dynamic(..) => {
|
|
||||||
for el in &self.elements {
|
|
||||||
match *el {
|
|
||||||
PatternElement::Str(ref s) => path.push_str(s),
|
|
||||||
PatternElement::Var(ref name) => {
|
|
||||||
if let Some(val) = elements.get(name) {
|
|
||||||
path.push_str(val.as_ref())
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PatternType::DynamicSet(..) => {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_param(pattern: &str) -> (PatternElement, String, &str, bool) {
|
|
||||||
const DEFAULT_PATTERN: &str = "[^/]+";
|
|
||||||
const DEFAULT_PATTERN_TAIL: &str = ".*";
|
|
||||||
let mut params_nesting = 0usize;
|
|
||||||
let close_idx = pattern
|
|
||||||
.find(|c| match c {
|
|
||||||
'{' => {
|
|
||||||
params_nesting += 1;
|
|
||||||
false
|
|
||||||
}
|
|
||||||
'}' => {
|
|
||||||
params_nesting -= 1;
|
|
||||||
params_nesting == 0
|
|
||||||
}
|
|
||||||
_ => false,
|
|
||||||
})
|
|
||||||
.expect("malformed dynamic segment");
|
|
||||||
let (mut param, mut rem) = pattern.split_at(close_idx + 1);
|
|
||||||
param = ¶m[1..param.len() - 1]; // Remove outer brackets
|
|
||||||
let tail = rem == "*";
|
|
||||||
|
|
||||||
let (name, pattern) = match param.find(':') {
|
|
||||||
Some(idx) => {
|
|
||||||
if tail {
|
|
||||||
panic!("Custom regex is not supported for remainder match");
|
|
||||||
}
|
|
||||||
let (name, pattern) = param.split_at(idx);
|
|
||||||
(name, &pattern[1..])
|
|
||||||
}
|
|
||||||
None => (
|
|
||||||
param,
|
|
||||||
if tail {
|
|
||||||
rem = &rem[1..];
|
|
||||||
DEFAULT_PATTERN_TAIL
|
|
||||||
} else {
|
|
||||||
DEFAULT_PATTERN
|
|
||||||
},
|
|
||||||
),
|
|
||||||
};
|
|
||||||
(
|
|
||||||
PatternElement::Var(name.to_string()),
|
|
||||||
format!(r"(?P<{}>{})", &name, &pattern),
|
|
||||||
rem,
|
|
||||||
tail,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse(
|
|
||||||
mut pattern: &str,
|
|
||||||
mut for_prefix: bool,
|
|
||||||
) -> (String, Vec<PatternElement>, bool, usize) {
|
|
||||||
if pattern.find('{').is_none() {
|
|
||||||
return if let Some(path) = pattern.strip_suffix('*') {
|
|
||||||
let re = format!("{}^{}(.*)", REGEX_FLAGS, path);
|
|
||||||
(re, vec![PatternElement::Str(String::from(path))], true, 0)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
String::from(pattern),
|
|
||||||
vec![PatternElement::Str(String::from(pattern))],
|
|
||||||
false,
|
|
||||||
pattern.chars().count(),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut elements = Vec::new();
|
|
||||||
let mut re = format!("{}^", REGEX_FLAGS);
|
|
||||||
let mut dyn_elements = 0;
|
|
||||||
|
|
||||||
while let Some(idx) = pattern.find('{') {
|
|
||||||
let (prefix, rem) = pattern.split_at(idx);
|
|
||||||
elements.push(PatternElement::Str(String::from(prefix)));
|
|
||||||
re.push_str(&escape(prefix));
|
|
||||||
let (param_pattern, re_part, rem, tail) = Self::parse_param(rem);
|
|
||||||
if tail {
|
|
||||||
for_prefix = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
elements.push(param_pattern);
|
|
||||||
re.push_str(&re_part);
|
|
||||||
pattern = rem;
|
|
||||||
dyn_elements += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
elements.push(PatternElement::Str(String::from(pattern)));
|
|
||||||
re.push_str(&escape(pattern));
|
|
||||||
|
|
||||||
if dyn_elements > MAX_DYNAMIC_SEGMENTS {
|
|
||||||
panic!(
|
|
||||||
"Only {} dynamic segments are allowed, provided: {}",
|
|
||||||
MAX_DYNAMIC_SEGMENTS, dyn_elements
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !for_prefix {
|
|
||||||
re.push('$');
|
|
||||||
}
|
|
||||||
(re, elements, true, pattern.chars().count())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for ResourceDef {}
|
|
||||||
|
|
||||||
impl PartialEq for ResourceDef {
|
|
||||||
fn eq(&self, other: &ResourceDef) -> bool {
|
|
||||||
self.pattern == other.pattern
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hash for ResourceDef {
|
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
||||||
self.pattern.hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<&'a str> for ResourceDef {
|
|
||||||
fn from(path: &'a str) -> ResourceDef {
|
|
||||||
ResourceDef::new(path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<String> for ResourceDef {
|
|
||||||
fn from(path: String) -> ResourceDef {
|
|
||||||
ResourceDef::new(path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn insert_slash(path: &str) -> String {
|
|
||||||
let mut path = path.to_owned();
|
|
||||||
if !path.is_empty() && !path.starts_with('/') {
|
|
||||||
path.insert(0, '/');
|
|
||||||
};
|
|
||||||
path
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_static() {
|
|
||||||
let re = ResourceDef::new("/");
|
|
||||||
assert!(re.is_match("/"));
|
|
||||||
assert!(!re.is_match("/a"));
|
|
||||||
|
|
||||||
let re = ResourceDef::new("/name");
|
|
||||||
assert!(re.is_match("/name"));
|
|
||||||
assert!(!re.is_match("/name1"));
|
|
||||||
assert!(!re.is_match("/name/"));
|
|
||||||
assert!(!re.is_match("/name~"));
|
|
||||||
|
|
||||||
assert_eq!(re.is_prefix_match("/name"), Some(5));
|
|
||||||
assert_eq!(re.is_prefix_match("/name1"), None);
|
|
||||||
assert_eq!(re.is_prefix_match("/name/"), None);
|
|
||||||
assert_eq!(re.is_prefix_match("/name~"), None);
|
|
||||||
|
|
||||||
let re = ResourceDef::new("/name/");
|
|
||||||
assert!(re.is_match("/name/"));
|
|
||||||
assert!(!re.is_match("/name"));
|
|
||||||
assert!(!re.is_match("/name/gs"));
|
|
||||||
|
|
||||||
let re = ResourceDef::new("/user/profile");
|
|
||||||
assert!(re.is_match("/user/profile"));
|
|
||||||
assert!(!re.is_match("/user/profile/profile"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_param() {
|
|
||||||
let re = ResourceDef::new("/user/{id}");
|
|
||||||
assert!(re.is_match("/user/profile"));
|
|
||||||
assert!(re.is_match("/user/2345"));
|
|
||||||
assert!(!re.is_match("/user/2345/"));
|
|
||||||
assert!(!re.is_match("/user/2345/sdg"));
|
|
||||||
|
|
||||||
let mut path = Path::new("/user/profile");
|
|
||||||
assert!(re.match_path(&mut path));
|
|
||||||
assert_eq!(path.get("id").unwrap(), "profile");
|
|
||||||
|
|
||||||
let mut path = Path::new("/user/1245125");
|
|
||||||
assert!(re.match_path(&mut path));
|
|
||||||
assert_eq!(path.get("id").unwrap(), "1245125");
|
|
||||||
|
|
||||||
let re = ResourceDef::new("/v{version}/resource/{id}");
|
|
||||||
assert!(re.is_match("/v1/resource/320120"));
|
|
||||||
assert!(!re.is_match("/v/resource/1"));
|
|
||||||
assert!(!re.is_match("/resource"));
|
|
||||||
|
|
||||||
let mut path = Path::new("/v151/resource/adage32");
|
|
||||||
assert!(re.match_path(&mut path));
|
|
||||||
assert_eq!(path.get("version").unwrap(), "151");
|
|
||||||
assert_eq!(path.get("id").unwrap(), "adage32");
|
|
||||||
|
|
||||||
let re = ResourceDef::new("/{id:[[:digit:]]{6}}");
|
|
||||||
assert!(re.is_match("/012345"));
|
|
||||||
assert!(!re.is_match("/012"));
|
|
||||||
assert!(!re.is_match("/01234567"));
|
|
||||||
assert!(!re.is_match("/XXXXXX"));
|
|
||||||
|
|
||||||
let mut path = Path::new("/012345");
|
|
||||||
assert!(re.match_path(&mut path));
|
|
||||||
assert_eq!(path.get("id").unwrap(), "012345");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::cognitive_complexity)]
|
|
||||||
#[test]
|
|
||||||
fn test_dynamic_set() {
|
|
||||||
let re = ResourceDef::new(vec![
|
|
||||||
"/user/{id}",
|
|
||||||
"/v{version}/resource/{id}",
|
|
||||||
"/{id:[[:digit:]]{6}}",
|
|
||||||
]);
|
|
||||||
assert!(re.is_match("/user/profile"));
|
|
||||||
assert!(re.is_match("/user/2345"));
|
|
||||||
assert!(!re.is_match("/user/2345/"));
|
|
||||||
assert!(!re.is_match("/user/2345/sdg"));
|
|
||||||
|
|
||||||
let mut path = Path::new("/user/profile");
|
|
||||||
assert!(re.match_path(&mut path));
|
|
||||||
assert_eq!(path.get("id").unwrap(), "profile");
|
|
||||||
|
|
||||||
let mut path = Path::new("/user/1245125");
|
|
||||||
assert!(re.match_path(&mut path));
|
|
||||||
assert_eq!(path.get("id").unwrap(), "1245125");
|
|
||||||
|
|
||||||
assert!(re.is_match("/v1/resource/320120"));
|
|
||||||
assert!(!re.is_match("/v/resource/1"));
|
|
||||||
assert!(!re.is_match("/resource"));
|
|
||||||
|
|
||||||
let mut path = Path::new("/v151/resource/adage32");
|
|
||||||
assert!(re.match_path(&mut path));
|
|
||||||
assert_eq!(path.get("version").unwrap(), "151");
|
|
||||||
assert_eq!(path.get("id").unwrap(), "adage32");
|
|
||||||
|
|
||||||
assert!(re.is_match("/012345"));
|
|
||||||
assert!(!re.is_match("/012"));
|
|
||||||
assert!(!re.is_match("/01234567"));
|
|
||||||
assert!(!re.is_match("/XXXXXX"));
|
|
||||||
|
|
||||||
let mut path = Path::new("/012345");
|
|
||||||
assert!(re.match_path(&mut path));
|
|
||||||
assert_eq!(path.get("id").unwrap(), "012345");
|
|
||||||
|
|
||||||
let re = ResourceDef::new([
|
|
||||||
"/user/{id}",
|
|
||||||
"/v{version}/resource/{id}",
|
|
||||||
"/{id:[[:digit:]]{6}}",
|
|
||||||
]);
|
|
||||||
assert!(re.is_match("/user/profile"));
|
|
||||||
assert!(re.is_match("/user/2345"));
|
|
||||||
assert!(!re.is_match("/user/2345/"));
|
|
||||||
assert!(!re.is_match("/user/2345/sdg"));
|
|
||||||
|
|
||||||
let re = ResourceDef::new([
|
|
||||||
"/user/{id}".to_string(),
|
|
||||||
"/v{version}/resource/{id}".to_string(),
|
|
||||||
"/{id:[[:digit:]]{6}}".to_string(),
|
|
||||||
]);
|
|
||||||
assert!(re.is_match("/user/profile"));
|
|
||||||
assert!(re.is_match("/user/2345"));
|
|
||||||
assert!(!re.is_match("/user/2345/"));
|
|
||||||
assert!(!re.is_match("/user/2345/sdg"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_tail() {
|
|
||||||
let re = ResourceDef::new("/user/-{id}*");
|
|
||||||
|
|
||||||
let mut path = Path::new("/user/-profile");
|
|
||||||
assert!(re.match_path(&mut path));
|
|
||||||
assert_eq!(path.get("id").unwrap(), "profile");
|
|
||||||
|
|
||||||
let mut path = Path::new("/user/-2345");
|
|
||||||
assert!(re.match_path(&mut path));
|
|
||||||
assert_eq!(path.get("id").unwrap(), "2345");
|
|
||||||
|
|
||||||
let mut path = Path::new("/user/-2345/");
|
|
||||||
assert!(re.match_path(&mut path));
|
|
||||||
assert_eq!(path.get("id").unwrap(), "2345/");
|
|
||||||
|
|
||||||
let mut path = Path::new("/user/-2345/sdg");
|
|
||||||
assert!(re.match_path(&mut path));
|
|
||||||
assert_eq!(path.get("id").unwrap(), "2345/sdg");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_static_tail() {
|
|
||||||
let re = ResourceDef::new("/user*");
|
|
||||||
assert!(re.is_match("/user/profile"));
|
|
||||||
assert!(re.is_match("/user/2345"));
|
|
||||||
assert!(re.is_match("/user/2345/"));
|
|
||||||
assert!(re.is_match("/user/2345/sdg"));
|
|
||||||
|
|
||||||
let re = ResourceDef::new("/user/*");
|
|
||||||
assert!(re.is_match("/user/profile"));
|
|
||||||
assert!(re.is_match("/user/2345"));
|
|
||||||
assert!(re.is_match("/user/2345/"));
|
|
||||||
assert!(re.is_match("/user/2345/sdg"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_newline() {
|
|
||||||
let re = ResourceDef::new("/user/a\nb");
|
|
||||||
assert!(re.is_match("/user/a\nb"));
|
|
||||||
assert!(!re.is_match("/user/a\nb/profile"));
|
|
||||||
|
|
||||||
let re = ResourceDef::new("/a{x}b/test/a{y}b");
|
|
||||||
let mut path = Path::new("/a\nb/test/a\nb");
|
|
||||||
assert!(re.match_path(&mut path));
|
|
||||||
assert_eq!(path.get("x").unwrap(), "\n");
|
|
||||||
assert_eq!(path.get("y").unwrap(), "\n");
|
|
||||||
|
|
||||||
let re = ResourceDef::new("/user/*");
|
|
||||||
assert!(re.is_match("/user/a\nb/"));
|
|
||||||
|
|
||||||
let re = ResourceDef::new("/user/{id}*");
|
|
||||||
let mut path = Path::new("/user/a\nb/a\nb");
|
|
||||||
assert!(re.match_path(&mut path));
|
|
||||||
assert_eq!(path.get("id").unwrap(), "a\nb/a\nb");
|
|
||||||
|
|
||||||
let re = ResourceDef::new("/user/{id:.*}");
|
|
||||||
let mut path = Path::new("/user/a\nb/a\nb");
|
|
||||||
assert!(re.match_path(&mut path));
|
|
||||||
assert_eq!(path.get("id").unwrap(), "a\nb/a\nb");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "http")]
|
|
||||||
#[test]
|
|
||||||
fn test_parse_urlencoded_param() {
|
|
||||||
use std::convert::TryFrom;
|
|
||||||
|
|
||||||
let re = ResourceDef::new("/user/{id}/test");
|
|
||||||
|
|
||||||
let mut path = Path::new("/user/2345/test");
|
|
||||||
assert!(re.match_path(&mut path));
|
|
||||||
assert_eq!(path.get("id").unwrap(), "2345");
|
|
||||||
|
|
||||||
let mut path = Path::new("/user/qwe%25/test");
|
|
||||||
assert!(re.match_path(&mut path));
|
|
||||||
assert_eq!(path.get("id").unwrap(), "qwe%25");
|
|
||||||
|
|
||||||
let uri = http::Uri::try_from("/user/qwe%25/test").unwrap();
|
|
||||||
let mut path = Path::new(uri);
|
|
||||||
assert!(re.match_path(&mut path));
|
|
||||||
assert_eq!(path.get("id").unwrap(), "qwe%25");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_resource_prefix() {
|
|
||||||
let re = ResourceDef::prefix("/name");
|
|
||||||
assert!(re.is_match("/name"));
|
|
||||||
assert!(re.is_match("/name/"));
|
|
||||||
assert!(re.is_match("/name/test/test"));
|
|
||||||
assert!(re.is_match("/name1"));
|
|
||||||
assert!(re.is_match("/name~"));
|
|
||||||
|
|
||||||
assert_eq!(re.is_prefix_match("/name"), Some(5));
|
|
||||||
assert_eq!(re.is_prefix_match("/name/"), Some(5));
|
|
||||||
assert_eq!(re.is_prefix_match("/name/test/test"), Some(5));
|
|
||||||
assert_eq!(re.is_prefix_match("/name1"), None);
|
|
||||||
assert_eq!(re.is_prefix_match("/name~"), None);
|
|
||||||
|
|
||||||
let re = ResourceDef::prefix("/name/");
|
|
||||||
assert!(re.is_match("/name/"));
|
|
||||||
assert!(re.is_match("/name/gs"));
|
|
||||||
assert!(!re.is_match("/name"));
|
|
||||||
|
|
||||||
let re = ResourceDef::root_prefix("name/");
|
|
||||||
assert!(re.is_match("/name/"));
|
|
||||||
assert!(re.is_match("/name/gs"));
|
|
||||||
assert!(!re.is_match("/name"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_resource_prefix_dynamic() {
|
|
||||||
let re = ResourceDef::prefix("/{name}/");
|
|
||||||
assert!(re.is_match("/name/"));
|
|
||||||
assert!(re.is_match("/name/gs"));
|
|
||||||
assert!(!re.is_match("/name"));
|
|
||||||
|
|
||||||
assert_eq!(re.is_prefix_match("/name/"), Some(6));
|
|
||||||
assert_eq!(re.is_prefix_match("/name/gs"), Some(6));
|
|
||||||
assert_eq!(re.is_prefix_match("/name"), None);
|
|
||||||
|
|
||||||
let mut path = Path::new("/test2/");
|
|
||||||
assert!(re.match_path(&mut path));
|
|
||||||
assert_eq!(&path["name"], "test2");
|
|
||||||
assert_eq!(&path[0], "test2");
|
|
||||||
|
|
||||||
let mut path = Path::new("/test2/subpath1/subpath2/index.html");
|
|
||||||
assert!(re.match_path(&mut path));
|
|
||||||
assert_eq!(&path["name"], "test2");
|
|
||||||
assert_eq!(&path[0], "test2");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_resource_path() {
|
|
||||||
let mut s = String::new();
|
|
||||||
let resource = ResourceDef::new("/user/{item1}/test");
|
|
||||||
assert!(resource.resource_path(&mut s, &mut (&["user1"]).iter()));
|
|
||||||
assert_eq!(s, "/user/user1/test");
|
|
||||||
|
|
||||||
let mut s = String::new();
|
|
||||||
let resource = ResourceDef::new("/user/{item1}/{item2}/test");
|
|
||||||
assert!(resource.resource_path(&mut s, &mut (&["item", "item2"]).iter()));
|
|
||||||
assert_eq!(s, "/user/item/item2/test");
|
|
||||||
|
|
||||||
let mut s = String::new();
|
|
||||||
let resource = ResourceDef::new("/user/{item1}/{item2}");
|
|
||||||
assert!(resource.resource_path(&mut s, &mut (&["item", "item2"]).iter()));
|
|
||||||
assert_eq!(s, "/user/item/item2");
|
|
||||||
|
|
||||||
let mut s = String::new();
|
|
||||||
let resource = ResourceDef::new("/user/{item1}/{item2}/");
|
|
||||||
assert!(resource.resource_path(&mut s, &mut (&["item", "item2"]).iter()));
|
|
||||||
assert_eq!(s, "/user/item/item2/");
|
|
||||||
|
|
||||||
let mut s = String::new();
|
|
||||||
assert!(!resource.resource_path(&mut s, &mut (&["item"]).iter()));
|
|
||||||
|
|
||||||
let mut s = String::new();
|
|
||||||
assert!(resource.resource_path(&mut s, &mut (&["item", "item2"]).iter()));
|
|
||||||
assert_eq!(s, "/user/item/item2/");
|
|
||||||
assert!(!resource.resource_path(&mut s, &mut (&["item"]).iter()));
|
|
||||||
|
|
||||||
let mut s = String::new();
|
|
||||||
assert!(resource.resource_path(&mut s, &mut vec!["item", "item2"].into_iter()));
|
|
||||||
assert_eq!(s, "/user/item/item2/");
|
|
||||||
|
|
||||||
let mut map = HashMap::new();
|
|
||||||
map.insert("item1", "item");
|
|
||||||
|
|
||||||
let mut s = String::new();
|
|
||||||
assert!(!resource.resource_path_named(&mut s, &map));
|
|
||||||
|
|
||||||
let mut s = String::new();
|
|
||||||
map.insert("item2", "item2");
|
|
||||||
assert!(resource.resource_path_named(&mut s, &map));
|
|
||||||
assert_eq!(s, "/user/item/item2/");
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,259 +0,0 @@
|
|||||||
use crate::{IntoPattern, Resource, ResourceDef, ResourcePath};
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
||||||
pub struct ResourceId(pub u16);
|
|
||||||
|
|
||||||
/// Information about current resource
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct ResourceInfo {
|
|
||||||
resource: ResourceId,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resource router.
|
|
||||||
pub struct Router<T, U = ()>(Vec<(ResourceDef, T, Option<U>)>);
|
|
||||||
|
|
||||||
impl<T, U> Router<T, U> {
|
|
||||||
pub fn build() -> RouterBuilder<T, U> {
|
|
||||||
RouterBuilder {
|
|
||||||
resources: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn recognize<R, P>(&self, resource: &mut R) -> Option<(&T, ResourceId)>
|
|
||||||
where
|
|
||||||
R: Resource<P>,
|
|
||||||
P: ResourcePath,
|
|
||||||
{
|
|
||||||
for item in self.0.iter() {
|
|
||||||
if item.0.match_path(resource.resource_path()) {
|
|
||||||
return Some((&item.1, ResourceId(item.0.id())));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn recognize_mut<R, P>(&mut self, resource: &mut R) -> Option<(&mut T, ResourceId)>
|
|
||||||
where
|
|
||||||
R: Resource<P>,
|
|
||||||
P: ResourcePath,
|
|
||||||
{
|
|
||||||
for item in self.0.iter_mut() {
|
|
||||||
if item.0.match_path(resource.resource_path()) {
|
|
||||||
return Some((&mut item.1, ResourceId(item.0.id())));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn recognize_checked<R, P, F>(
|
|
||||||
&self,
|
|
||||||
resource: &mut R,
|
|
||||||
check: F,
|
|
||||||
) -> Option<(&T, ResourceId)>
|
|
||||||
where
|
|
||||||
F: Fn(&R, &Option<U>) -> bool,
|
|
||||||
R: Resource<P>,
|
|
||||||
P: ResourcePath,
|
|
||||||
{
|
|
||||||
for item in self.0.iter() {
|
|
||||||
if item.0.match_path_checked(resource, &check, &item.2) {
|
|
||||||
return Some((&item.1, ResourceId(item.0.id())));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn recognize_mut_checked<R, P, F>(
|
|
||||||
&mut self,
|
|
||||||
resource: &mut R,
|
|
||||||
check: F,
|
|
||||||
) -> Option<(&mut T, ResourceId)>
|
|
||||||
where
|
|
||||||
F: Fn(&R, &Option<U>) -> bool,
|
|
||||||
R: Resource<P>,
|
|
||||||
P: ResourcePath,
|
|
||||||
{
|
|
||||||
for item in self.0.iter_mut() {
|
|
||||||
if item.0.match_path_checked(resource, &check, &item.2) {
|
|
||||||
return Some((&mut item.1, ResourceId(item.0.id())));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct RouterBuilder<T, U = ()> {
|
|
||||||
resources: Vec<(ResourceDef, T, Option<U>)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, U> RouterBuilder<T, U> {
|
|
||||||
/// Register resource for specified path.
|
|
||||||
pub fn path<P: IntoPattern>(
|
|
||||||
&mut self,
|
|
||||||
path: P,
|
|
||||||
resource: T,
|
|
||||||
) -> &mut (ResourceDef, T, Option<U>) {
|
|
||||||
self.resources
|
|
||||||
.push((ResourceDef::new(path), resource, None));
|
|
||||||
self.resources.last_mut().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Register resource for specified path prefix.
|
|
||||||
pub fn prefix(&mut self, prefix: &str, resource: T) -> &mut (ResourceDef, T, Option<U>) {
|
|
||||||
self.resources
|
|
||||||
.push((ResourceDef::prefix(prefix), resource, None));
|
|
||||||
self.resources.last_mut().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Register resource for ResourceDef
|
|
||||||
pub fn rdef(&mut self, rdef: ResourceDef, resource: T) -> &mut (ResourceDef, T, Option<U>) {
|
|
||||||
self.resources.push((rdef, resource, None));
|
|
||||||
self.resources.last_mut().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Finish configuration and create router instance.
|
|
||||||
pub fn finish(self) -> Router<T, U> {
|
|
||||||
Router(self.resources)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use crate::path::Path;
|
|
||||||
use crate::router::{ResourceId, Router};
|
|
||||||
|
|
||||||
#[allow(clippy::cognitive_complexity)]
|
|
||||||
#[test]
|
|
||||||
fn test_recognizer_1() {
|
|
||||||
let mut router = Router::<usize>::build();
|
|
||||||
router.path("/name", 10).0.set_id(0);
|
|
||||||
router.path("/name/{val}", 11).0.set_id(1);
|
|
||||||
router.path("/name/{val}/index.html", 12).0.set_id(2);
|
|
||||||
router.path("/file/{file}.{ext}", 13).0.set_id(3);
|
|
||||||
router.path("/v{val}/{val2}/index.html", 14).0.set_id(4);
|
|
||||||
router.path("/v/{tail:.*}", 15).0.set_id(5);
|
|
||||||
router.path("/test2/{test}.html", 16).0.set_id(6);
|
|
||||||
router.path("/{test}/index.html", 17).0.set_id(7);
|
|
||||||
let mut router = router.finish();
|
|
||||||
|
|
||||||
let mut path = Path::new("/unknown");
|
|
||||||
assert!(router.recognize_mut(&mut path).is_none());
|
|
||||||
|
|
||||||
let mut path = Path::new("/name");
|
|
||||||
let (h, info) = router.recognize_mut(&mut path).unwrap();
|
|
||||||
assert_eq!(*h, 10);
|
|
||||||
assert_eq!(info, ResourceId(0));
|
|
||||||
assert!(path.is_empty());
|
|
||||||
|
|
||||||
let mut path = Path::new("/name/value");
|
|
||||||
let (h, info) = router.recognize_mut(&mut path).unwrap();
|
|
||||||
assert_eq!(*h, 11);
|
|
||||||
assert_eq!(info, ResourceId(1));
|
|
||||||
assert_eq!(path.get("val").unwrap(), "value");
|
|
||||||
assert_eq!(&path["val"], "value");
|
|
||||||
|
|
||||||
let mut path = Path::new("/name/value2/index.html");
|
|
||||||
let (h, info) = router.recognize_mut(&mut path).unwrap();
|
|
||||||
assert_eq!(*h, 12);
|
|
||||||
assert_eq!(info, ResourceId(2));
|
|
||||||
assert_eq!(path.get("val").unwrap(), "value2");
|
|
||||||
|
|
||||||
let mut path = Path::new("/file/file.gz");
|
|
||||||
let (h, info) = router.recognize_mut(&mut path).unwrap();
|
|
||||||
assert_eq!(*h, 13);
|
|
||||||
assert_eq!(info, ResourceId(3));
|
|
||||||
assert_eq!(path.get("file").unwrap(), "file");
|
|
||||||
assert_eq!(path.get("ext").unwrap(), "gz");
|
|
||||||
|
|
||||||
let mut path = Path::new("/vtest/ttt/index.html");
|
|
||||||
let (h, info) = router.recognize_mut(&mut path).unwrap();
|
|
||||||
assert_eq!(*h, 14);
|
|
||||||
assert_eq!(info, ResourceId(4));
|
|
||||||
assert_eq!(path.get("val").unwrap(), "test");
|
|
||||||
assert_eq!(path.get("val2").unwrap(), "ttt");
|
|
||||||
|
|
||||||
let mut path = Path::new("/v/blah-blah/index.html");
|
|
||||||
let (h, info) = router.recognize_mut(&mut path).unwrap();
|
|
||||||
assert_eq!(*h, 15);
|
|
||||||
assert_eq!(info, ResourceId(5));
|
|
||||||
assert_eq!(path.get("tail").unwrap(), "blah-blah/index.html");
|
|
||||||
|
|
||||||
let mut path = Path::new("/test2/index.html");
|
|
||||||
let (h, info) = router.recognize_mut(&mut path).unwrap();
|
|
||||||
assert_eq!(*h, 16);
|
|
||||||
assert_eq!(info, ResourceId(6));
|
|
||||||
assert_eq!(path.get("test").unwrap(), "index");
|
|
||||||
|
|
||||||
let mut path = Path::new("/bbb/index.html");
|
|
||||||
let (h, info) = router.recognize_mut(&mut path).unwrap();
|
|
||||||
assert_eq!(*h, 17);
|
|
||||||
assert_eq!(info, ResourceId(7));
|
|
||||||
assert_eq!(path.get("test").unwrap(), "bbb");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_recognizer_2() {
|
|
||||||
let mut router = Router::<usize>::build();
|
|
||||||
router.path("/index.json", 10);
|
|
||||||
router.path("/{source}.json", 11);
|
|
||||||
let mut router = router.finish();
|
|
||||||
|
|
||||||
let mut path = Path::new("/index.json");
|
|
||||||
let (h, _) = router.recognize_mut(&mut path).unwrap();
|
|
||||||
assert_eq!(*h, 10);
|
|
||||||
|
|
||||||
let mut path = Path::new("/test.json");
|
|
||||||
let (h, _) = router.recognize_mut(&mut path).unwrap();
|
|
||||||
assert_eq!(*h, 11);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_recognizer_with_prefix() {
|
|
||||||
let mut router = Router::<usize>::build();
|
|
||||||
router.path("/name", 10).0.set_id(0);
|
|
||||||
router.path("/name/{val}", 11).0.set_id(1);
|
|
||||||
let mut router = router.finish();
|
|
||||||
|
|
||||||
let mut path = Path::new("/name");
|
|
||||||
path.skip(5);
|
|
||||||
assert!(router.recognize_mut(&mut path).is_none());
|
|
||||||
|
|
||||||
let mut path = Path::new("/test/name");
|
|
||||||
path.skip(5);
|
|
||||||
let (h, _) = router.recognize_mut(&mut path).unwrap();
|
|
||||||
assert_eq!(*h, 10);
|
|
||||||
|
|
||||||
let mut path = Path::new("/test/name/value");
|
|
||||||
path.skip(5);
|
|
||||||
let (h, id) = router.recognize_mut(&mut path).unwrap();
|
|
||||||
assert_eq!(*h, 11);
|
|
||||||
assert_eq!(id, ResourceId(1));
|
|
||||||
assert_eq!(path.get("val").unwrap(), "value");
|
|
||||||
assert_eq!(&path["val"], "value");
|
|
||||||
|
|
||||||
// same patterns
|
|
||||||
let mut router = Router::<usize>::build();
|
|
||||||
router.path("/name", 10);
|
|
||||||
router.path("/name/{val}", 11);
|
|
||||||
let mut router = router.finish();
|
|
||||||
|
|
||||||
let mut path = Path::new("/name");
|
|
||||||
path.skip(6);
|
|
||||||
assert!(router.recognize_mut(&mut path).is_none());
|
|
||||||
|
|
||||||
let mut path = Path::new("/test2/name");
|
|
||||||
path.skip(6);
|
|
||||||
let (h, _) = router.recognize_mut(&mut path).unwrap();
|
|
||||||
assert_eq!(*h, 10);
|
|
||||||
|
|
||||||
let mut path = Path::new("/test2/name-test");
|
|
||||||
path.skip(6);
|
|
||||||
assert!(router.recognize_mut(&mut path).is_none());
|
|
||||||
|
|
||||||
let mut path = Path::new("/test2/name/ttt");
|
|
||||||
path.skip(6);
|
|
||||||
let (h, _) = router.recognize_mut(&mut path).unwrap();
|
|
||||||
assert_eq!(*h, 11);
|
|
||||||
assert_eq!(&path["val"], "ttt");
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,288 +0,0 @@
|
|||||||
use crate::ResourcePath;
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
const GEN_DELIMS: &[u8] = b":/?#[]@";
|
|
||||||
#[allow(dead_code)]
|
|
||||||
const SUB_DELIMS_WITHOUT_QS: &[u8] = b"!$'()*,";
|
|
||||||
#[allow(dead_code)]
|
|
||||||
const SUB_DELIMS: &[u8] = b"!$'()*,+?=;";
|
|
||||||
#[allow(dead_code)]
|
|
||||||
const RESERVED: &[u8] = b":/?#[]@!$'()*,+?=;";
|
|
||||||
#[allow(dead_code)]
|
|
||||||
const UNRESERVED: &[u8] = b"abcdefghijklmnopqrstuvwxyz
|
|
||||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
|
||||||
1234567890
|
|
||||||
-._~";
|
|
||||||
const ALLOWED: &[u8] = b"abcdefghijklmnopqrstuvwxyz
|
|
||||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
|
||||||
1234567890
|
|
||||||
-._~
|
|
||||||
!$'()*,";
|
|
||||||
const QS: &[u8] = b"+&=;b";
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn bit_at(array: &[u8], ch: u8) -> bool {
|
|
||||||
array[(ch >> 3) as usize] & (1 << (ch & 7)) != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_bit(array: &mut [u8], ch: u8) {
|
|
||||||
array[(ch >> 3) as usize] |= 1 << (ch & 7)
|
|
||||||
}
|
|
||||||
|
|
||||||
thread_local! {
|
|
||||||
static DEFAULT_QUOTER: Quoter = Quoter::new(b"@:", b"%/+");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug)]
|
|
||||||
pub struct Url {
|
|
||||||
uri: http::Uri,
|
|
||||||
path: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Url {
|
|
||||||
pub fn new(uri: http::Uri) -> Url {
|
|
||||||
let path = DEFAULT_QUOTER.with(|q| q.requote(uri.path().as_bytes()));
|
|
||||||
|
|
||||||
Url { uri, path }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_quoter(uri: http::Uri, quoter: &Quoter) -> Url {
|
|
||||||
Url {
|
|
||||||
path: quoter.requote(uri.path().as_bytes()),
|
|
||||||
uri,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn uri(&self) -> &http::Uri {
|
|
||||||
&self.uri
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn path(&self) -> &str {
|
|
||||||
if let Some(ref s) = self.path {
|
|
||||||
s
|
|
||||||
} else {
|
|
||||||
self.uri.path()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn update(&mut self, uri: &http::Uri) {
|
|
||||||
self.uri = uri.clone();
|
|
||||||
self.path = DEFAULT_QUOTER.with(|q| q.requote(uri.path().as_bytes()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn update_with_quoter(&mut self, uri: &http::Uri, quoter: &Quoter) {
|
|
||||||
self.uri = uri.clone();
|
|
||||||
self.path = quoter.requote(uri.path().as_bytes());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ResourcePath for Url {
|
|
||||||
#[inline]
|
|
||||||
fn path(&self) -> &str {
|
|
||||||
self.path()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Quoter {
|
|
||||||
safe_table: [u8; 16],
|
|
||||||
protected_table: [u8; 16],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Quoter {
|
|
||||||
pub fn new(safe: &[u8], protected: &[u8]) -> Quoter {
|
|
||||||
let mut q = Quoter {
|
|
||||||
safe_table: [0; 16],
|
|
||||||
protected_table: [0; 16],
|
|
||||||
};
|
|
||||||
|
|
||||||
// prepare safe table
|
|
||||||
for i in 0..128 {
|
|
||||||
if ALLOWED.contains(&i) {
|
|
||||||
set_bit(&mut q.safe_table, i);
|
|
||||||
}
|
|
||||||
if QS.contains(&i) {
|
|
||||||
set_bit(&mut q.safe_table, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for ch in safe {
|
|
||||||
set_bit(&mut q.safe_table, *ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepare protected table
|
|
||||||
for ch in protected {
|
|
||||||
set_bit(&mut q.safe_table, *ch);
|
|
||||||
set_bit(&mut q.protected_table, *ch);
|
|
||||||
}
|
|
||||||
|
|
||||||
q
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn requote(&self, val: &[u8]) -> Option<String> {
|
|
||||||
let mut has_pct = 0;
|
|
||||||
let mut pct = [b'%', 0, 0];
|
|
||||||
let mut idx = 0;
|
|
||||||
let mut cloned: Option<Vec<u8>> = None;
|
|
||||||
|
|
||||||
let len = val.len();
|
|
||||||
while idx < len {
|
|
||||||
let ch = val[idx];
|
|
||||||
|
|
||||||
if has_pct != 0 {
|
|
||||||
pct[has_pct] = val[idx];
|
|
||||||
has_pct += 1;
|
|
||||||
if has_pct == 3 {
|
|
||||||
has_pct = 0;
|
|
||||||
let buf = cloned.as_mut().unwrap();
|
|
||||||
|
|
||||||
if let Some(ch) = restore_ch(pct[1], pct[2]) {
|
|
||||||
if ch < 128 {
|
|
||||||
if bit_at(&self.protected_table, ch) {
|
|
||||||
buf.extend_from_slice(&pct);
|
|
||||||
idx += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if bit_at(&self.safe_table, ch) {
|
|
||||||
buf.push(ch);
|
|
||||||
idx += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buf.push(ch);
|
|
||||||
} else {
|
|
||||||
buf.extend_from_slice(&pct[..]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if ch == b'%' {
|
|
||||||
has_pct = 1;
|
|
||||||
if cloned.is_none() {
|
|
||||||
let mut c = Vec::with_capacity(len);
|
|
||||||
c.extend_from_slice(&val[..idx]);
|
|
||||||
cloned = Some(c);
|
|
||||||
}
|
|
||||||
} else if let Some(ref mut cloned) = cloned {
|
|
||||||
cloned.push(ch)
|
|
||||||
}
|
|
||||||
idx += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
cloned.map(|data| String::from_utf8_lossy(&data).into_owned())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn from_hex(v: u8) -> Option<u8> {
|
|
||||||
if (b'0'..=b'9').contains(&v) {
|
|
||||||
Some(v - 0x30) // ord('0') == 0x30
|
|
||||||
} else if (b'A'..=b'F').contains(&v) {
|
|
||||||
Some(v - 0x41 + 10) // ord('A') == 0x41
|
|
||||||
} else if (b'a'..=b'f').contains(&v) {
|
|
||||||
Some(v - 0x61 + 10) // ord('a') == 0x61
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn restore_ch(d1: u8, d2: u8) -> Option<u8> {
|
|
||||||
from_hex(d1).and_then(|d1| from_hex(d2).map(move |d2| d1 << 4 | d2))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use http::Uri;
|
|
||||||
use std::convert::TryFrom;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::{Path, ResourceDef};
|
|
||||||
|
|
||||||
const PROTECTED: &[u8] = b"%/+";
|
|
||||||
|
|
||||||
fn match_url(pattern: &'static str, url: impl AsRef<str>) -> Path<Url> {
|
|
||||||
let re = ResourceDef::new(pattern);
|
|
||||||
let uri = Uri::try_from(url.as_ref()).unwrap();
|
|
||||||
let mut path = Path::new(Url::new(uri));
|
|
||||||
assert!(re.match_path(&mut path));
|
|
||||||
path
|
|
||||||
}
|
|
||||||
|
|
||||||
fn percent_encode(data: &[u8]) -> String {
|
|
||||||
data.into_iter().map(|c| format!("%{:02X}", c)).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_url() {
|
|
||||||
let re = "/user/{id}/test";
|
|
||||||
|
|
||||||
let path = match_url(re, "/user/2345/test");
|
|
||||||
assert_eq!(path.get("id").unwrap(), "2345");
|
|
||||||
|
|
||||||
// "%25" should never be decoded into '%' to gurantee the output is a valid
|
|
||||||
// percent-encoded format
|
|
||||||
let path = match_url(re, "/user/qwe%25/test");
|
|
||||||
assert_eq!(path.get("id").unwrap(), "qwe%25");
|
|
||||||
|
|
||||||
let path = match_url(re, "/user/qwe%25rty/test");
|
|
||||||
assert_eq!(path.get("id").unwrap(), "qwe%25rty");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_protected_chars() {
|
|
||||||
let encoded = percent_encode(PROTECTED);
|
|
||||||
let path = match_url("/user/{id}/test", format!("/user/{}/test", encoded));
|
|
||||||
assert_eq!(path.get("id").unwrap(), &encoded);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_non_protecteed_ascii() {
|
|
||||||
let nonprotected_ascii = ('\u{0}'..='\u{7F}')
|
|
||||||
.filter(|&c| c.is_ascii() && !PROTECTED.contains(&(c as u8)))
|
|
||||||
.collect::<String>();
|
|
||||||
let encoded = percent_encode(nonprotected_ascii.as_bytes());
|
|
||||||
let path = match_url("/user/{id}/test", format!("/user/{}/test", encoded));
|
|
||||||
assert_eq!(path.get("id").unwrap(), &nonprotected_ascii);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_valid_utf8_multibyte() {
|
|
||||||
let test = ('\u{FF00}'..='\u{FFFF}').collect::<String>();
|
|
||||||
let encoded = percent_encode(test.as_bytes());
|
|
||||||
let path = match_url("/a/{id}/b", format!("/a/{}/b", &encoded));
|
|
||||||
assert_eq!(path.get("id").unwrap(), &test);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_invalid_utf8() {
|
|
||||||
let invalid_utf8 = percent_encode((0x80..=0xff).collect::<Vec<_>>().as_slice());
|
|
||||||
let uri = Uri::try_from(format!("/{}", invalid_utf8)).unwrap();
|
|
||||||
let path = Path::new(Url::new(uri));
|
|
||||||
|
|
||||||
// We should always get a valid utf8 string
|
|
||||||
assert!(String::from_utf8(path.path().as_bytes().to_owned()).is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_from_hex() {
|
|
||||||
let hex = b"0123456789abcdefABCDEF";
|
|
||||||
|
|
||||||
for i in 0..256 {
|
|
||||||
let c = i as u8;
|
|
||||||
if hex.contains(&c) {
|
|
||||||
assert!(from_hex(c).is_some())
|
|
||||||
} else {
|
|
||||||
assert!(from_hex(c).is_none())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let expected = [
|
|
||||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 10, 11, 12, 13, 14, 15,
|
|
||||||
];
|
|
||||||
for i in 0..hex.len() {
|
|
||||||
assert_eq!(from_hex(hex[i]).unwrap(), expected[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -3,12 +3,20 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 2.3.0 - 2021-10-11
|
||||||
|
* The `spawn` method can now resolve with non-unit outputs. [#369]
|
||||||
|
* Add experimental (semver-exempt) `io-uring` feature for enabling async file I/O on linux. [#374]
|
||||||
|
|
||||||
|
[#369]: https://github.com/actix/actix-net/pull/369
|
||||||
|
[#374]: https://github.com/actix/actix-net/pull/374
|
||||||
|
|
||||||
|
|
||||||
## 2.2.0 - 2021-03-29
|
## 2.2.0 - 2021-03-29
|
||||||
* **BREAKING** `ActixStream::{poll_read_ready, poll_write_ready}` methods now return
|
* **BREAKING** `ActixStream::{poll_read_ready, poll_write_ready}` methods now return
|
||||||
`Ready` object in ok variant. [#293]
|
`Ready` object in ok variant. [#293]
|
||||||
* Breakage is acceptable since `ActixStream` was not intended to be public.
|
* Breakage is acceptable since `ActixStream` was not intended to be public.
|
||||||
|
|
||||||
[#293] https://github.com/actix/actix-net/pull/293
|
[#293]: https://github.com/actix/actix-net/pull/293
|
||||||
|
|
||||||
|
|
||||||
## 2.1.0 - 2021-02-24
|
## 2.1.0 - 2021-02-24
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-rt"
|
name = "actix-rt"
|
||||||
version = "2.2.0"
|
version = "2.3.0"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"Rob Ede <robjtede@icloud.com>",
|
"Rob Ede <robjtede@icloud.com>",
|
||||||
@@ -8,8 +8,7 @@ authors = [
|
|||||||
description = "Tokio-based single-threaded async runtime for the Actix ecosystem"
|
description = "Tokio-based single-threaded async runtime for the Actix ecosystem"
|
||||||
keywords = ["async", "futures", "io", "runtime"]
|
keywords = ["async", "futures", "io", "runtime"]
|
||||||
homepage = "https://actix.rs"
|
homepage = "https://actix.rs"
|
||||||
repository = "https://github.com/actix/actix-net"
|
repository = "https://github.com/actix/actix-net.git"
|
||||||
documentation = "https://docs.rs/actix-rt"
|
|
||||||
categories = ["network-programming", "asynchronous"]
|
categories = ["network-programming", "asynchronous"]
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
@@ -21,13 +20,17 @@ path = "src/lib.rs"
|
|||||||
[features]
|
[features]
|
||||||
default = ["macros"]
|
default = ["macros"]
|
||||||
macros = ["actix-macros"]
|
macros = ["actix-macros"]
|
||||||
|
io-uring = ["tokio-uring"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-macros = { version = "0.2.0", optional = true }
|
actix-macros = { version = "0.2.3", optional = true }
|
||||||
|
|
||||||
futures-core = { version = "0.3", default-features = false }
|
futures-core = { version = "0.3", default-features = false }
|
||||||
tokio = { version = "1.3", features = ["rt", "net", "parking_lot", "signal", "sync", "time"] }
|
tokio = { version = "1.5.1", features = ["rt", "net", "parking_lot", "signal", "sync", "time"] }
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
|
tokio-uring = { version = "0.1", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { version = "1.2", features = ["full"] }
|
tokio = { version = "1.5.1", features = ["full"] }
|
||||||
hyper = { version = "0.14", default-features = false, features = ["server", "tcp", "http1"] }
|
hyper = { version = "0.14", default-features = false, features = ["server", "tcp", "http1"] }
|
||||||
|
@@ -3,11 +3,11 @@
|
|||||||
> Tokio-based single-threaded async runtime for the Actix ecosystem.
|
> Tokio-based single-threaded async runtime for the Actix ecosystem.
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-rt)
|
[](https://crates.io/crates/actix-rt)
|
||||||
[](https://docs.rs/actix-rt/2.2.0)
|
[](https://docs.rs/actix-rt/2.3.0)
|
||||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
||||||

|

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

|

|
||||||
[](https://discord.gg/WghFtEH6Hb)
|
[](https://discord.gg/WghFtEH6Hb)
|
||||||
|
|
||||||
|
@@ -9,12 +9,9 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use futures_core::ready;
|
use futures_core::ready;
|
||||||
use tokio::{sync::mpsc, task::LocalSet};
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
use crate::{
|
use crate::system::{System, SystemCommand};
|
||||||
runtime::{default_tokio_runtime, Runtime},
|
|
||||||
system::{System, SystemCommand},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub(crate) static COUNT: AtomicUsize = AtomicUsize::new(0);
|
pub(crate) static COUNT: AtomicUsize = AtomicUsize::new(0);
|
||||||
|
|
||||||
@@ -98,16 +95,19 @@ impl Arbiter {
|
|||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
/// Panics if a [System] is not registered on the current thread.
|
/// Panics if a [System] is not registered on the current thread.
|
||||||
|
#[cfg(not(all(target_os = "linux", feature = "io-uring")))]
|
||||||
#[allow(clippy::new_without_default)]
|
#[allow(clippy::new_without_default)]
|
||||||
pub fn new() -> Arbiter {
|
pub fn new() -> Arbiter {
|
||||||
Self::with_tokio_rt(|| {
|
Self::with_tokio_rt(|| {
|
||||||
default_tokio_runtime().expect("Cannot create new Arbiter's Runtime.")
|
crate::runtime::default_tokio_runtime()
|
||||||
|
.expect("Cannot create new Arbiter's Runtime.")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Spawn a new Arbiter using the [Tokio Runtime](tokio-runtime) returned from a closure.
|
/// Spawn a new Arbiter using the [Tokio Runtime](tokio-runtime) returned from a closure.
|
||||||
///
|
///
|
||||||
/// [tokio-runtime]: tokio::runtime::Runtime
|
/// [tokio-runtime]: tokio::runtime::Runtime
|
||||||
|
#[cfg(not(all(target_os = "linux", feature = "io-uring")))]
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn with_tokio_rt<F>(runtime_factory: F) -> Arbiter
|
pub fn with_tokio_rt<F>(runtime_factory: F) -> Arbiter
|
||||||
where
|
where
|
||||||
@@ -127,7 +127,7 @@ impl Arbiter {
|
|||||||
.spawn({
|
.spawn({
|
||||||
let tx = tx.clone();
|
let tx = tx.clone();
|
||||||
move || {
|
move || {
|
||||||
let rt = Runtime::from(runtime_factory());
|
let rt = crate::runtime::Runtime::from(runtime_factory());
|
||||||
let hnd = ArbiterHandle::new(tx);
|
let hnd = ArbiterHandle::new(tx);
|
||||||
|
|
||||||
System::set_current(sys);
|
System::set_current(sys);
|
||||||
@@ -159,15 +159,67 @@ impl Arbiter {
|
|||||||
Arbiter { tx, thread_handle }
|
Arbiter { tx, thread_handle }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets up an Arbiter runner in a new System using the provided runtime local task set.
|
/// Spawn a new Arbiter thread and start its event loop with `tokio-uring` runtime.
|
||||||
pub(crate) fn in_new_system(local: &LocalSet) -> ArbiterHandle {
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// Panics if a [System] is not registered on the current thread.
|
||||||
|
#[cfg(all(target_os = "linux", feature = "io-uring"))]
|
||||||
|
#[allow(clippy::new_without_default)]
|
||||||
|
pub fn new() -> Arbiter {
|
||||||
|
let sys = System::current();
|
||||||
|
let system_id = sys.id();
|
||||||
|
let arb_id = COUNT.fetch_add(1, Ordering::Relaxed);
|
||||||
|
|
||||||
|
let name = format!("actix-rt|system:{}|arbiter:{}", system_id, arb_id);
|
||||||
|
let (tx, rx) = mpsc::unbounded_channel();
|
||||||
|
|
||||||
|
let (ready_tx, ready_rx) = std::sync::mpsc::channel::<()>();
|
||||||
|
|
||||||
|
let thread_handle = thread::Builder::new()
|
||||||
|
.name(name.clone())
|
||||||
|
.spawn({
|
||||||
|
let tx = tx.clone();
|
||||||
|
move || {
|
||||||
|
let hnd = ArbiterHandle::new(tx);
|
||||||
|
|
||||||
|
System::set_current(sys);
|
||||||
|
|
||||||
|
HANDLE.with(|cell| *cell.borrow_mut() = Some(hnd.clone()));
|
||||||
|
|
||||||
|
// register arbiter
|
||||||
|
let _ = System::current()
|
||||||
|
.tx()
|
||||||
|
.send(SystemCommand::RegisterArbiter(arb_id, hnd));
|
||||||
|
|
||||||
|
ready_tx.send(()).unwrap();
|
||||||
|
|
||||||
|
// run arbiter event processing loop
|
||||||
|
tokio_uring::start(ArbiterRunner { rx });
|
||||||
|
|
||||||
|
// deregister arbiter
|
||||||
|
let _ = System::current()
|
||||||
|
.tx()
|
||||||
|
.send(SystemCommand::DeregisterArbiter(arb_id));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|err| {
|
||||||
|
panic!("Cannot spawn Arbiter's thread: {:?}. {:?}", &name, err)
|
||||||
|
});
|
||||||
|
|
||||||
|
ready_rx.recv().unwrap();
|
||||||
|
|
||||||
|
Arbiter { tx, thread_handle }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets up an Arbiter runner in a new System using the environment's local set.
|
||||||
|
pub(crate) fn in_new_system() -> ArbiterHandle {
|
||||||
let (tx, rx) = mpsc::unbounded_channel();
|
let (tx, rx) = mpsc::unbounded_channel();
|
||||||
|
|
||||||
let hnd = ArbiterHandle::new(tx);
|
let hnd = ArbiterHandle::new(tx);
|
||||||
|
|
||||||
HANDLE.with(|cell| *cell.borrow_mut() = Some(hnd.clone()));
|
HANDLE.with(|cell| *cell.borrow_mut() = Some(hnd.clone()));
|
||||||
|
|
||||||
local.spawn_local(ArbiterRunner { rx });
|
crate::spawn(ArbiterRunner { rx });
|
||||||
|
|
||||||
hnd
|
hnd
|
||||||
}
|
}
|
||||||
|
@@ -32,6 +32,10 @@
|
|||||||
//! arbiter.stop();
|
//! arbiter.stop();
|
||||||
//! arbiter.join().unwrap();
|
//! arbiter.join().unwrap();
|
||||||
//! ```
|
//! ```
|
||||||
|
//!
|
||||||
|
//! # `io-uring` Support
|
||||||
|
//! There is experimental support for using io-uring with this crate by enabling the
|
||||||
|
//! `io-uring` feature. For now, it is semver exempt.
|
||||||
|
|
||||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||||
#![allow(clippy::type_complexity)]
|
#![allow(clippy::type_complexity)]
|
||||||
@@ -39,6 +43,9 @@
|
|||||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||||
|
|
||||||
|
#[cfg(all(not(target_os = "linux"), feature = "io-uring"))]
|
||||||
|
compile_error!("io_uring is a linux only feature.");
|
||||||
|
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
@@ -46,7 +53,10 @@ use tokio::task::JoinHandle;
|
|||||||
// Cannot define a main macro when compiled into test harness.
|
// Cannot define a main macro when compiled into test harness.
|
||||||
// Workaround for https://github.com/rust-lang/rust/issues/62127.
|
// Workaround for https://github.com/rust-lang/rust/issues/62127.
|
||||||
#[cfg(all(feature = "macros", not(test)))]
|
#[cfg(all(feature = "macros", not(test)))]
|
||||||
pub use actix_macros::{main, test};
|
pub use actix_macros::main;
|
||||||
|
|
||||||
|
#[cfg(feature = "macros")]
|
||||||
|
pub use actix_macros::test;
|
||||||
|
|
||||||
mod arbiter;
|
mod arbiter;
|
||||||
mod runtime;
|
mod runtime;
|
||||||
@@ -155,14 +165,41 @@ pub mod task {
|
|||||||
pub use tokio::task::{spawn_blocking, yield_now, JoinError, JoinHandle};
|
pub use tokio::task::{spawn_blocking, yield_now, JoinError, JoinHandle};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Spawns a future on the current thread.
|
/// Spawns a future on the current thread as a new task.
|
||||||
|
///
|
||||||
|
/// If not immediately awaited, the task can be cancelled using [`JoinHandle::abort`].
|
||||||
|
///
|
||||||
|
/// The provided future is spawned as a new task; therefore, panics are caught.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
/// Panics if Actix system is not running.
|
/// Panics if Actix system is not running.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// # use std::time::Duration;
|
||||||
|
/// # actix_rt::Runtime::new().unwrap().block_on(async {
|
||||||
|
/// // task resolves successfully
|
||||||
|
/// assert_eq!(actix_rt::spawn(async { 1 }).await.unwrap(), 1);
|
||||||
|
///
|
||||||
|
/// // task panics
|
||||||
|
/// assert!(actix_rt::spawn(async {
|
||||||
|
/// panic!("panic is caught at task boundary");
|
||||||
|
/// })
|
||||||
|
/// .await
|
||||||
|
/// .unwrap_err()
|
||||||
|
/// .is_panic());
|
||||||
|
///
|
||||||
|
/// // task is cancelled before completion
|
||||||
|
/// let handle = actix_rt::spawn(actix_rt::time::sleep(Duration::from_secs(100)));
|
||||||
|
/// handle.abort();
|
||||||
|
/// assert!(handle.await.unwrap_err().is_cancelled());
|
||||||
|
/// # });
|
||||||
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn spawn<Fut>(f: Fut) -> JoinHandle<()>
|
pub fn spawn<Fut>(f: Fut) -> JoinHandle<Fut::Output>
|
||||||
where
|
where
|
||||||
Fut: Future<Output = ()> + 'static,
|
Fut: Future + 'static,
|
||||||
|
Fut::Output: 'static,
|
||||||
{
|
{
|
||||||
tokio::task::spawn_local(f)
|
tokio::task::spawn_local(f)
|
||||||
}
|
}
|
||||||
|
@@ -31,11 +31,6 @@ impl Runtime {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reference to local task set.
|
|
||||||
pub(crate) fn local_set(&self) -> &LocalSet {
|
|
||||||
&self.local
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Offload a future onto the single-threaded runtime.
|
/// Offload a future onto the single-threaded runtime.
|
||||||
///
|
///
|
||||||
/// The returned join handle can be used to await the future's result.
|
/// The returned join handle can be used to await the future's result.
|
||||||
|
@@ -54,7 +54,7 @@ impl System {
|
|||||||
let (sys_tx, sys_rx) = mpsc::unbounded_channel();
|
let (sys_tx, sys_rx) = mpsc::unbounded_channel();
|
||||||
|
|
||||||
let rt = Runtime::from(runtime_factory());
|
let rt = Runtime::from(runtime_factory());
|
||||||
let sys_arbiter = Arbiter::in_new_system(rt.local_set());
|
let sys_arbiter = rt.block_on(async { Arbiter::in_new_system() });
|
||||||
let system = System::construct(sys_tx, sys_arbiter.clone());
|
let system = System::construct(sys_tx, sys_arbiter.clone());
|
||||||
|
|
||||||
system
|
system
|
||||||
@@ -155,6 +155,7 @@ impl System {
|
|||||||
pub struct SystemRunner {
|
pub struct SystemRunner {
|
||||||
rt: Runtime,
|
rt: Runtime,
|
||||||
stop_rx: oneshot::Receiver<i32>,
|
stop_rx: oneshot::Receiver<i32>,
|
||||||
|
#[allow(dead_code)]
|
||||||
system: System,
|
system: System,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
17
actix-rt/tests/test-macro-import-conflict.rs
Normal file
17
actix-rt/tests/test-macro-import-conflict.rs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
//! Checks that test macro does not cause problems in the presence of imports named "test" that
|
||||||
|
//! could be either a module with test items or the "test with runtime" macro itself.
|
||||||
|
//!
|
||||||
|
//! Before actix/actix-net#399 was implemented, this macro was running twice. The first run output
|
||||||
|
//! `#[test]` and it got run again and since it was in scope.
|
||||||
|
//!
|
||||||
|
//! Prevented by using the fully-qualified test marker (`#[::core::prelude::v1::test]`).
|
||||||
|
|
||||||
|
#![cfg(feature = "macros")]
|
||||||
|
|
||||||
|
use actix_rt::time as test;
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_naming_conflict() {
|
||||||
|
use test as time;
|
||||||
|
time::sleep(std::time::Duration::from_millis(2)).await;
|
||||||
|
}
|
@@ -1,14 +1,11 @@
|
|||||||
use std::{
|
use std::{
|
||||||
sync::{
|
future::Future,
|
||||||
atomic::{AtomicBool, Ordering},
|
sync::mpsc::channel,
|
||||||
mpsc::channel,
|
|
||||||
Arc,
|
|
||||||
},
|
|
||||||
thread,
|
thread,
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
use actix_rt::{Arbiter, System};
|
use actix_rt::{task::JoinError, Arbiter, System};
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -220,8 +217,8 @@ fn system_stop_stops_arbiters() {
|
|||||||
System::current().stop();
|
System::current().stop();
|
||||||
sys.run().unwrap();
|
sys.run().unwrap();
|
||||||
|
|
||||||
// account for slightly slow thread de-spawns (only observed on windows)
|
// account for slightly slow thread de-spawns
|
||||||
thread::sleep(Duration::from_millis(100));
|
thread::sleep(Duration::from_millis(500));
|
||||||
|
|
||||||
// arbiter should be dead and return false
|
// arbiter should be dead and return false
|
||||||
assert!(!Arbiter::current().spawn_fn(|| {}));
|
assert!(!Arbiter::current().spawn_fn(|| {}));
|
||||||
@@ -230,6 +227,7 @@ fn system_stop_stops_arbiters() {
|
|||||||
arb.join().unwrap();
|
arb.join().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "io-uring"))]
|
||||||
#[test]
|
#[test]
|
||||||
fn new_system_with_tokio() {
|
fn new_system_with_tokio() {
|
||||||
let (tx, rx) = channel();
|
let (tx, rx) = channel();
|
||||||
@@ -262,8 +260,14 @@ fn new_system_with_tokio() {
|
|||||||
assert_eq!(rx.recv().unwrap(), 42);
|
assert_eq!(rx.recv().unwrap(), 42);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "io-uring"))]
|
||||||
#[test]
|
#[test]
|
||||||
fn new_arbiter_with_tokio() {
|
fn new_arbiter_with_tokio() {
|
||||||
|
use std::sync::{
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
Arc,
|
||||||
|
};
|
||||||
|
|
||||||
let _ = System::new();
|
let _ = System::new();
|
||||||
|
|
||||||
let arb = Arbiter::with_tokio_rt(|| {
|
let arb = Arbiter::with_tokio_rt(|| {
|
||||||
@@ -286,7 +290,7 @@ fn new_arbiter_with_tokio() {
|
|||||||
|
|
||||||
arb.join().unwrap();
|
arb.join().unwrap();
|
||||||
|
|
||||||
assert_eq!(false, counter.load(Ordering::SeqCst));
|
assert!(!counter.load(Ordering::SeqCst));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -298,3 +302,56 @@ fn try_current_no_system() {
|
|||||||
fn try_current_with_system() {
|
fn try_current_with_system() {
|
||||||
System::new().block_on(async { assert!(System::try_current().is_some()) });
|
System::new().block_on(async { assert!(System::try_current().is_some()) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::unit_cmp)]
|
||||||
|
#[test]
|
||||||
|
fn spawn_local() {
|
||||||
|
System::new().block_on(async {
|
||||||
|
// demonstrate that spawn -> R is strictly more capable than spawn -> ()
|
||||||
|
|
||||||
|
assert_eq!(actix_rt::spawn(async {}).await.unwrap(), ());
|
||||||
|
assert_eq!(actix_rt::spawn(async { 1 }).await.unwrap(), 1);
|
||||||
|
assert!(actix_rt::spawn(async { panic!("") }).await.is_err());
|
||||||
|
|
||||||
|
actix_rt::spawn(async { tokio::time::sleep(Duration::from_millis(50)).await })
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
fn g<F: Future<Output = Result<(), JoinError>>>(_f: F) {}
|
||||||
|
g(actix_rt::spawn(async {}));
|
||||||
|
// g(actix_rt::spawn(async { 1 })); // compile err
|
||||||
|
|
||||||
|
fn h<F: Future<Output = Result<R, JoinError>>, R>(_f: F) {}
|
||||||
|
h(actix_rt::spawn(async {}));
|
||||||
|
h(actix_rt::spawn(async { 1 }));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(target_os = "linux", feature = "io-uring"))]
|
||||||
|
#[test]
|
||||||
|
fn tokio_uring_arbiter() {
|
||||||
|
let system = System::new();
|
||||||
|
let (tx, rx) = std::sync::mpsc::channel();
|
||||||
|
|
||||||
|
Arbiter::new().spawn(async move {
|
||||||
|
let handle = actix_rt::spawn(async move {
|
||||||
|
let f = tokio_uring::fs::File::create("test.txt").await.unwrap();
|
||||||
|
let buf = b"Hello World!";
|
||||||
|
|
||||||
|
let (res, _) = f.write_at(&buf[..], 0).await;
|
||||||
|
assert!(res.is_ok());
|
||||||
|
|
||||||
|
f.sync_all().await.unwrap();
|
||||||
|
f.close().await.unwrap();
|
||||||
|
|
||||||
|
std::fs::remove_file("test.txt").unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
handle.await.unwrap();
|
||||||
|
tx.send(true).unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
assert!(rx.recv().unwrap());
|
||||||
|
|
||||||
|
drop(system);
|
||||||
|
}
|
||||||
|
@@ -1,16 +1,26 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
* Remove `config` module. `ServiceConfig`, `ServiceRuntime` public types are removed due to this change. [#349]
|
* Minimum supported Rust version (MSRV) is now 1.52.
|
||||||
|
|
||||||
|
|
||||||
|
## 2.0.0-beta.6 - 2021-10-11
|
||||||
|
* Add experimental (semver-exempt) `io-uring` feature for enabling async file I/O on linux. [#374]
|
||||||
|
* Server no long listens to `SIGHUP` signal. Previously, the received was not used but did block
|
||||||
|
subsequent exit signals from working. [#389]
|
||||||
|
* Remove `config` module. `ServiceConfig`, `ServiceRuntime` public types are removed due to
|
||||||
|
this change. [#349]
|
||||||
* Remove `ServerBuilder::configure` [#349]
|
* Remove `ServerBuilder::configure` [#349]
|
||||||
|
|
||||||
|
[#374]: https://github.com/actix/actix-net/pull/374
|
||||||
[#349]: https://github.com/actix/actix-net/pull/349
|
[#349]: https://github.com/actix/actix-net/pull/349
|
||||||
|
[#389]: https://github.com/actix/actix-net/pull/389
|
||||||
|
|
||||||
|
|
||||||
## 2.0.0-beta.5 - 2021-04-20
|
## 2.0.0-beta.5 - 2021-04-20
|
||||||
* Server shutdown would notify all workers to exit regardless if shutdown is graceful.
|
* Server shutdown notifies all workers to exit regardless if shutdown is graceful. This causes all
|
||||||
This would make all worker shutdown immediately in force shutdown case. [#333]
|
workers to shutdown immediately in force shutdown case. [#333]
|
||||||
|
|
||||||
[#333]: https://github.com/actix/actix-net/pull/333
|
[#333]: https://github.com/actix/actix-net/pull/333
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-server"
|
name = "actix-server"
|
||||||
version = "2.0.0-beta.5"
|
version = "2.0.0-beta.6"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"fakeshadow <24548779@qq.com>",
|
"fakeshadow <24548779@qq.com>",
|
||||||
]
|
]
|
||||||
description = "General purpose TCP server built for the Actix ecosystem"
|
description = "General purpose TCP server built for the Actix ecosystem"
|
||||||
keywords = ["network", "framework", "async", "futures"]
|
keywords = ["network", "framework", "async", "futures"]
|
||||||
repository = "https://github.com/actix/actix-net"
|
repository = "https://github.com/actix/actix-net.git"
|
||||||
categories = ["network-programming", "asynchronous"]
|
categories = ["network-programming", "asynchronous"]
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
@@ -18,6 +18,7 @@ path = "src/lib.rs"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
io-uring = ["actix-rt/io-uring"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-rt = { version = "2.0.0", default-features = false }
|
actix-rt = { version = "2.0.0", default-features = false }
|
||||||
@@ -28,13 +29,13 @@ futures-core = { version = "0.3.7", default-features = false, features = ["alloc
|
|||||||
log = "0.4"
|
log = "0.4"
|
||||||
mio = { version = "0.7.6", features = ["os-poll", "net"] }
|
mio = { version = "0.7.6", features = ["os-poll", "net"] }
|
||||||
num_cpus = "1.13"
|
num_cpus = "1.13"
|
||||||
tokio = { version = "1.2", features = ["sync"] }
|
tokio = { version = "1.5.1", features = ["sync"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-codec = "0.4.0-beta.1"
|
actix-codec = "0.4.0-beta.1"
|
||||||
actix-rt = "2.0.0"
|
actix-rt = "2.0.0"
|
||||||
|
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
env_logger = "0.8"
|
env_logger = "0.9"
|
||||||
futures-util = { version = "0.3.7", default-features = false, features = ["sink"] }
|
futures-util = { version = "0.3.7", default-features = false, features = ["sink"] }
|
||||||
tokio = { version = "1", features = ["io-util"] }
|
tokio = { version = "1.5.1", features = ["io-util"] }
|
||||||
|
@@ -81,14 +81,9 @@ struct Accept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Array of u128 with every bit as marker for a worker handle's availability.
|
/// Array of u128 with every bit as marker for a worker handle's availability.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
struct Availability([u128; 4]);
|
struct Availability([u128; 4]);
|
||||||
|
|
||||||
impl Default for Availability {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self([0; 4])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Availability {
|
impl Availability {
|
||||||
/// Check if any worker handle is available
|
/// Check if any worker handle is available
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
@@ -312,30 +312,31 @@ impl ServerBuilder {
|
|||||||
// Handle `SIGINT`, `SIGTERM`, `SIGQUIT` signals and stop actix system
|
// Handle `SIGINT`, `SIGTERM`, `SIGQUIT` signals and stop actix system
|
||||||
match sig {
|
match sig {
|
||||||
Signal::Int => {
|
Signal::Int => {
|
||||||
info!("SIGINT received, exiting");
|
info!("SIGINT received, starting forced shutdown");
|
||||||
self.exit = true;
|
self.exit = true;
|
||||||
self.handle_cmd(ServerCommand::Stop {
|
self.handle_cmd(ServerCommand::Stop {
|
||||||
graceful: false,
|
graceful: false,
|
||||||
completion: None,
|
completion: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
Signal::Term => {
|
Signal::Term => {
|
||||||
info!("SIGTERM received, stopping");
|
info!("SIGTERM received, starting graceful shutdown");
|
||||||
self.exit = true;
|
self.exit = true;
|
||||||
self.handle_cmd(ServerCommand::Stop {
|
self.handle_cmd(ServerCommand::Stop {
|
||||||
graceful: true,
|
graceful: true,
|
||||||
completion: None,
|
completion: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
Signal::Quit => {
|
Signal::Quit => {
|
||||||
info!("SIGQUIT received, exiting");
|
info!("SIGQUIT received, starting forced shutdown");
|
||||||
self.exit = true;
|
self.exit = true;
|
||||||
self.handle_cmd(ServerCommand::Stop {
|
self.handle_cmd(ServerCommand::Stop {
|
||||||
graceful: false,
|
graceful: false,
|
||||||
completion: None,
|
completion: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ServerCommand::Notify(tx) => {
|
ServerCommand::Notify(tx) => {
|
||||||
@@ -360,12 +361,14 @@ impl ServerBuilder {
|
|||||||
|
|
||||||
rt::spawn(async move {
|
rt::spawn(async move {
|
||||||
if graceful {
|
if graceful {
|
||||||
|
// wait for all workers to shut down
|
||||||
let _ = join_all(stop).await;
|
let _ = join_all(stop).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(tx) = completion {
|
if let Some(tx) = completion {
|
||||||
let _ = tx.send(());
|
let _ = tx.send(());
|
||||||
}
|
}
|
||||||
|
|
||||||
for tx in notify {
|
for tx in notify {
|
||||||
let _ = tx.send(());
|
let _ = tx.send(());
|
||||||
}
|
}
|
||||||
|
@@ -15,8 +15,8 @@ pub(crate) enum ServerCommand {
|
|||||||
Pause(oneshot::Sender<()>),
|
Pause(oneshot::Sender<()>),
|
||||||
Resume(oneshot::Sender<()>),
|
Resume(oneshot::Sender<()>),
|
||||||
Signal(Signal),
|
Signal(Signal),
|
||||||
/// Whether to try and shut down gracefully
|
|
||||||
Stop {
|
Stop {
|
||||||
|
/// True if shut down should be graceful.
|
||||||
graceful: bool,
|
graceful: bool,
|
||||||
completion: Option<oneshot::Sender<()>>,
|
completion: Option<oneshot::Sender<()>>,
|
||||||
},
|
},
|
||||||
@@ -24,6 +24,13 @@ pub(crate) enum ServerCommand {
|
|||||||
Notify(oneshot::Sender<()>),
|
Notify(oneshot::Sender<()>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Server handle.
|
||||||
|
///
|
||||||
|
/// # Shutdown Signals
|
||||||
|
/// On UNIX systems, `SIGQUIT` will start a graceful shutdown and `SIGTERM` or `SIGINT` will start a
|
||||||
|
/// forced shutdown. On Windows, a CTRL-C signal will start a forced shutdown.
|
||||||
|
///
|
||||||
|
/// A graceful shutdown will wait for all workers to stop first.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Server(
|
pub struct Server(
|
||||||
UnboundedSender<ServerCommand>,
|
UnboundedSender<ServerCommand>,
|
||||||
|
@@ -4,29 +4,33 @@ use std::task::{Context, Poll};
|
|||||||
|
|
||||||
use crate::server::Server;
|
use crate::server::Server;
|
||||||
|
|
||||||
/// Different types of process signals
|
/// Types of process signals.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(PartialEq, Clone, Copy, Debug)]
|
#[derive(PartialEq, Clone, Copy, Debug)]
|
||||||
pub(crate) enum Signal {
|
pub(crate) enum Signal {
|
||||||
/// SIGHUP
|
/// `SIGINT`
|
||||||
Hup,
|
|
||||||
/// SIGINT
|
|
||||||
Int,
|
Int,
|
||||||
/// SIGTERM
|
|
||||||
|
/// `SIGTERM`
|
||||||
Term,
|
Term,
|
||||||
/// SIGQUIT
|
|
||||||
|
/// `SIGQUIT`
|
||||||
Quit,
|
Quit,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Process signal listener.
|
||||||
pub(crate) struct Signals {
|
pub(crate) struct Signals {
|
||||||
srv: Server,
|
srv: Server,
|
||||||
|
|
||||||
#[cfg(not(unix))]
|
#[cfg(not(unix))]
|
||||||
signals: futures_core::future::LocalBoxFuture<'static, std::io::Result<()>>,
|
signals: futures_core::future::LocalBoxFuture<'static, std::io::Result<()>>,
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
signals: Vec<(Signal, actix_rt::signal::unix::Signal)>,
|
signals: Vec<(Signal, actix_rt::signal::unix::Signal)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Signals {
|
impl Signals {
|
||||||
|
/// Spawns a signal listening future that is able to send commands to the `Server`.
|
||||||
pub(crate) fn start(srv: Server) {
|
pub(crate) fn start(srv: Server) {
|
||||||
#[cfg(not(unix))]
|
#[cfg(not(unix))]
|
||||||
{
|
{
|
||||||
@@ -35,13 +39,13 @@ impl Signals {
|
|||||||
signals: Box::pin(actix_rt::signal::ctrl_c()),
|
signals: Box::pin(actix_rt::signal::ctrl_c()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
use actix_rt::signal::unix;
|
use actix_rt::signal::unix;
|
||||||
|
|
||||||
let sig_map = [
|
let sig_map = [
|
||||||
(unix::SignalKind::interrupt(), Signal::Int),
|
(unix::SignalKind::interrupt(), Signal::Int),
|
||||||
(unix::SignalKind::hangup(), Signal::Hup),
|
|
||||||
(unix::SignalKind::terminate(), Signal::Term),
|
(unix::SignalKind::terminate(), Signal::Term),
|
||||||
(unix::SignalKind::quit(), Signal::Quit),
|
(unix::SignalKind::quit(), Signal::Quit),
|
||||||
];
|
];
|
||||||
@@ -79,6 +83,7 @@ impl Future for Signals {
|
|||||||
}
|
}
|
||||||
Poll::Pending => Poll::Pending,
|
Poll::Pending => Poll::Pending,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
for (sig, fut) in self.signals.iter_mut() {
|
for (sig, fut) in self.signals.iter_mut() {
|
||||||
|
@@ -5,13 +5,12 @@ use actix_rt::{net::TcpStream, System};
|
|||||||
|
|
||||||
use crate::{Server, ServerBuilder, ServiceFactory};
|
use crate::{Server, ServerBuilder, ServiceFactory};
|
||||||
|
|
||||||
/// The `TestServer` type.
|
/// A testing server.
|
||||||
///
|
///
|
||||||
/// `TestServer` is very simple test server that simplify process of writing
|
/// `TestServer` is very simple test server that simplify process of writing integration tests for
|
||||||
/// integration tests for actix-net applications.
|
/// network applications.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
|
||||||
/// ```
|
/// ```
|
||||||
/// use actix_service::fn_service;
|
/// use actix_service::fn_service;
|
||||||
/// use actix_server::TestServer;
|
/// use actix_server::TestServer;
|
||||||
@@ -39,7 +38,7 @@ pub struct TestServerRuntime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TestServer {
|
impl TestServer {
|
||||||
/// Start new server with server builder
|
/// Start new server with server builder.
|
||||||
pub fn start<F>(mut factory: F) -> TestServerRuntime
|
pub fn start<F>(mut factory: F) -> TestServerRuntime
|
||||||
where
|
where
|
||||||
F: FnMut(ServerBuilder) -> ServerBuilder + Send + 'static,
|
F: FnMut(ServerBuilder) -> ServerBuilder + Send + 'static,
|
||||||
@@ -64,7 +63,7 @@ impl TestServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start new test server with application factory
|
/// Start new test server with application factory.
|
||||||
pub fn with<F: ServiceFactory<TcpStream>>(factory: F) -> TestServerRuntime {
|
pub fn with<F: ServiceFactory<TcpStream>>(factory: F) -> TestServerRuntime {
|
||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
|
|
||||||
@@ -99,7 +98,7 @@ impl TestServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get first available unused local address
|
/// Get first available unused local address.
|
||||||
pub fn unused_addr() -> net::SocketAddr {
|
pub fn unused_addr() -> net::SocketAddr {
|
||||||
let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
|
let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
|
||||||
let socket = mio::net::TcpSocket::new_v4().unwrap();
|
let socket = mio::net::TcpSocket::new_v4().unwrap();
|
||||||
@@ -111,27 +110,27 @@ impl TestServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TestServerRuntime {
|
impl TestServerRuntime {
|
||||||
/// Test server host
|
/// Test server host.
|
||||||
pub fn host(&self) -> &str {
|
pub fn host(&self) -> &str {
|
||||||
&self.host
|
&self.host
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test server port
|
/// Test server port.
|
||||||
pub fn port(&self) -> u16 {
|
pub fn port(&self) -> u16 {
|
||||||
self.port
|
self.port
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get test server address
|
/// Get test server address.
|
||||||
pub fn addr(&self) -> net::SocketAddr {
|
pub fn addr(&self) -> net::SocketAddr {
|
||||||
self.addr
|
self.addr
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stop http server
|
/// Stop server.
|
||||||
fn stop(&mut self) {
|
fn stop(&mut self) {
|
||||||
self.system.stop();
|
self.system.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Connect to server, return tokio TcpStream
|
/// Connect to server, returning a Tokio `TcpStream`.
|
||||||
pub fn connect(&self) -> std::io::Result<TcpStream> {
|
pub fn connect(&self) -> std::io::Result<TcpStream> {
|
||||||
TcpStream::from_std(net::TcpStream::connect(self.addr)?)
|
TcpStream::from_std(net::TcpStream::connect(self.addr)?)
|
||||||
}
|
}
|
||||||
|
@@ -73,7 +73,7 @@ fn handle_pair(
|
|||||||
/// On reaching counter limit worker would use `mio::Waker` and `WakerQueue` to wake up `Accept`
|
/// On reaching counter limit worker would use `mio::Waker` and `WakerQueue` to wake up `Accept`
|
||||||
/// and notify it to update cached `Availability` again to mark worker as able to accept work again.
|
/// and notify it to update cached `Availability` again to mark worker as able to accept work again.
|
||||||
///
|
///
|
||||||
/// Hense a wake up would only happen after `Accept` increment it to limit.
|
/// Hence, a wake up would only happen after `Accept` increment it to limit.
|
||||||
/// And a decrement to limit always wake up `Accept`.
|
/// And a decrement to limit always wake up `Accept`.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct Counter {
|
pub(crate) struct Counter {
|
||||||
@@ -179,8 +179,10 @@ impl WorkerHandleAccept {
|
|||||||
/// Handle to worker than can send stop message to worker.
|
/// Handle to worker than can send stop message to worker.
|
||||||
///
|
///
|
||||||
/// Held by [ServerBuilder](crate::builder::ServerBuilder).
|
/// Held by [ServerBuilder](crate::builder::ServerBuilder).
|
||||||
|
#[derive(Debug)]
|
||||||
pub(crate) struct WorkerHandleServer {
|
pub(crate) struct WorkerHandleServer {
|
||||||
pub idx: usize,
|
#[allow(dead_code)]
|
||||||
|
idx: usize,
|
||||||
tx: UnboundedSender<Stop>,
|
tx: UnboundedSender<Stop>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,14 +281,24 @@ impl ServerWorker {
|
|||||||
let counter_clone = counter.clone();
|
let counter_clone = counter.clone();
|
||||||
// every worker runs in it's own arbiter.
|
// every worker runs in it's own arbiter.
|
||||||
// use a custom tokio runtime builder to change the settings of runtime.
|
// use a custom tokio runtime builder to change the settings of runtime.
|
||||||
Arbiter::with_tokio_rt(move || {
|
#[cfg(all(target_os = "linux", feature = "io-uring"))]
|
||||||
|
let arbiter = {
|
||||||
|
// TODO: pass max blocking thread config when tokio-uring enable configuration
|
||||||
|
// on building runtime.
|
||||||
|
let _ = config.max_blocking_threads;
|
||||||
|
Arbiter::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(not(all(target_os = "linux", feature = "io-uring")))]
|
||||||
|
let arbiter = Arbiter::with_tokio_rt(move || {
|
||||||
tokio::runtime::Builder::new_current_thread()
|
tokio::runtime::Builder::new_current_thread()
|
||||||
.enable_all()
|
.enable_all()
|
||||||
.max_blocking_threads(config.max_blocking_threads)
|
.max_blocking_threads(config.max_blocking_threads)
|
||||||
.build()
|
.build()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
})
|
});
|
||||||
.spawn(async move {
|
|
||||||
|
arbiter.spawn(async move {
|
||||||
let fut = factories
|
let fut = factories
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
@@ -418,13 +430,15 @@ struct Restart {
|
|||||||
fut: LocalBoxFuture<'static, Result<(usize, BoxedServerService), ()>>,
|
fut: LocalBoxFuture<'static, Result<(usize, BoxedServerService), ()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown keep states necessary for server shutdown:
|
/// State necessary for server shutdown.
|
||||||
// Sleep for interval check the shutdown progress.
|
|
||||||
// Instant for the start time of shutdown.
|
|
||||||
// Sender for send back the shutdown outcome(force/grace) to StopCommand caller.
|
|
||||||
struct Shutdown {
|
struct Shutdown {
|
||||||
|
// Interval for checking the shutdown progress.
|
||||||
timer: Pin<Box<Sleep>>,
|
timer: Pin<Box<Sleep>>,
|
||||||
|
|
||||||
|
/// Start time of shutdown.
|
||||||
start_from: Instant,
|
start_from: Instant,
|
||||||
|
|
||||||
|
/// Notify of the shutdown outcome (force/grace) to stop caller.
|
||||||
tx: oneshot::Sender<bool>,
|
tx: oneshot::Sender<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -510,23 +524,25 @@ impl Future for ServerWorker {
|
|||||||
self.poll(cx)
|
self.poll(cx)
|
||||||
}
|
}
|
||||||
WorkerState::Shutdown(ref mut shutdown) => {
|
WorkerState::Shutdown(ref mut shutdown) => {
|
||||||
// Wait for 1 second.
|
// wait for 1 second
|
||||||
ready!(shutdown.timer.as_mut().poll(cx));
|
ready!(shutdown.timer.as_mut().poll(cx));
|
||||||
|
|
||||||
if this.counter.total() == 0 {
|
if this.counter.total() == 0 {
|
||||||
// Graceful shutdown.
|
// graceful shutdown
|
||||||
if let WorkerState::Shutdown(shutdown) = mem::take(&mut this.state) {
|
if let WorkerState::Shutdown(shutdown) = mem::take(&mut this.state) {
|
||||||
let _ = shutdown.tx.send(true);
|
let _ = shutdown.tx.send(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Poll::Ready(())
|
Poll::Ready(())
|
||||||
} else if shutdown.start_from.elapsed() >= this.shutdown_timeout {
|
} else if shutdown.start_from.elapsed() >= this.shutdown_timeout {
|
||||||
// Timeout forceful shutdown.
|
// timeout forceful shutdown
|
||||||
if let WorkerState::Shutdown(shutdown) = mem::take(&mut this.state) {
|
if let WorkerState::Shutdown(shutdown) = mem::take(&mut this.state) {
|
||||||
let _ = shutdown.tx.send(false);
|
let _ = shutdown.tx.send(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
Poll::Ready(())
|
Poll::Ready(())
|
||||||
} else {
|
} else {
|
||||||
// Reset timer and wait for 1 second.
|
// reset timer and wait for 1 second
|
||||||
let time = Instant::now() + Duration::from_secs(1);
|
let time = Instant::now() + Duration::from_secs(1);
|
||||||
shutdown.timer.as_mut().reset(time);
|
shutdown.timer.as_mut().reset(time);
|
||||||
shutdown.timer.as_mut().poll(cx)
|
shutdown.timer.as_mut().poll(cx)
|
||||||
|
@@ -3,6 +3,10 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 2.0.1 - 2021-10-11
|
||||||
|
* Documentation fix.
|
||||||
|
|
||||||
|
|
||||||
## 2.0.0 - 2021-04-16
|
## 2.0.0 - 2021-04-16
|
||||||
* Removed pipeline and related structs/functions. [#335]
|
* Removed pipeline and related structs/functions. [#335]
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-service"
|
name = "actix-service"
|
||||||
version = "2.0.0"
|
version = "2.0.1"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"Rob Ede <robjtede@icloud.com>",
|
"Rob Ede <robjtede@icloud.com>",
|
||||||
@@ -8,7 +8,7 @@ authors = [
|
|||||||
]
|
]
|
||||||
description = "Service trait and combinators for representing asynchronous request/response operations."
|
description = "Service trait and combinators for representing asynchronous request/response operations."
|
||||||
keywords = ["network", "framework", "async", "futures", "service"]
|
keywords = ["network", "framework", "async", "futures", "service"]
|
||||||
categories = ["network-programming", "asynchronous"]
|
categories = ["network-programming", "asynchronous", "no-std"]
|
||||||
repository = "https://github.com/actix/actix-net"
|
repository = "https://github.com/actix/actix-net"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
@@ -3,10 +3,10 @@
|
|||||||
> Service trait and combinators for representing asynchronous request/response operations.
|
> Service trait and combinators for representing asynchronous request/response operations.
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-service)
|
[](https://crates.io/crates/actix-service)
|
||||||
[](https://docs.rs/actix-service/2.0.0)
|
[](https://docs.rs/actix-service/2.0.1)
|
||||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
||||||

|

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

|

|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
|
33
actix-service/examples/clone.rs
Normal file
33
actix-service/examples/clone.rs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
use std::{future::Future, sync::mpsc, time::Duration};
|
||||||
|
|
||||||
|
async fn oracle<F, Fut>(f: F) -> (u32, u32)
|
||||||
|
where
|
||||||
|
F: FnOnce() -> Fut + Clone + Send + 'static,
|
||||||
|
Fut: Future<Output = u32> + 'static,
|
||||||
|
{
|
||||||
|
let f1 = actix_rt::spawn(f.clone()());
|
||||||
|
let f2 = actix_rt::spawn(f());
|
||||||
|
|
||||||
|
(f1.await.unwrap(), f2.await.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::main]
|
||||||
|
async fn main() {
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
|
||||||
|
let (r1, r2) = oracle({
|
||||||
|
let tx = tx.clone();
|
||||||
|
|
||||||
|
|| async move {
|
||||||
|
tx.send(()).unwrap();
|
||||||
|
4 * 4
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
assert_eq!(r1, r2);
|
||||||
|
|
||||||
|
tx.send(()).unwrap();
|
||||||
|
|
||||||
|
rx.recv_timeout(Duration::from_millis(100)).unwrap();
|
||||||
|
rx.recv_timeout(Duration::from_millis(100)).unwrap();
|
||||||
|
}
|
@@ -52,9 +52,9 @@ pub fn fn_factory<F, Cfg, Srv, Req, Fut, Err>(
|
|||||||
f: F,
|
f: F,
|
||||||
) -> FnServiceNoConfig<F, Cfg, Srv, Req, Fut, Err>
|
) -> FnServiceNoConfig<F, Cfg, Srv, Req, Fut, Err>
|
||||||
where
|
where
|
||||||
Srv: Service<Req>,
|
|
||||||
F: Fn() -> Fut,
|
F: Fn() -> Fut,
|
||||||
Fut: Future<Output = Result<Srv, Err>>,
|
Fut: Future<Output = Result<Srv, Err>>,
|
||||||
|
Srv: Service<Req>,
|
||||||
{
|
{
|
||||||
FnServiceNoConfig::new(f)
|
FnServiceNoConfig::new(f)
|
||||||
}
|
}
|
||||||
|
@@ -77,7 +77,7 @@ use self::ready::{err, ok, ready, Ready};
|
|||||||
///
|
///
|
||||||
/// fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { ... }
|
/// fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { ... }
|
||||||
///
|
///
|
||||||
/// fn call(&self, req: Self::Request) -> Self::Future { ... }
|
/// fn call(&self, req: u8) -> Self::Future { ... }
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
|
@@ -1,5 +1,9 @@
|
|||||||
/// An implementation of [`poll_ready`]() that always signals readiness.
|
/// An implementation of [`poll_ready`]() that always signals readiness.
|
||||||
///
|
///
|
||||||
|
/// This should only be used for basic leaf services that have no concept of un-readiness.
|
||||||
|
/// For wrapper or other serivice types, use [`forward_ready!`] for simple cases or write a bespoke
|
||||||
|
/// `poll_ready` implementation.
|
||||||
|
///
|
||||||
/// [`poll_ready`]: crate::Service::poll_ready
|
/// [`poll_ready`]: crate::Service::poll_ready
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
* Minimum supported Rust version (MSRV) is now 1.52.
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.5 - 2021-03-29
|
## 3.0.0-beta.5 - 2021-03-29
|
||||||
|
@@ -4,9 +4,7 @@ version = "3.0.0-beta.5"
|
|||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "TLS acceptor and connector services for Actix ecosystem"
|
description = "TLS acceptor and connector services for Actix ecosystem"
|
||||||
keywords = ["network", "tls", "ssl", "async", "transport"]
|
keywords = ["network", "tls", "ssl", "async", "transport"]
|
||||||
homepage = "https://actix.rs"
|
|
||||||
repository = "https://github.com/actix/actix-net.git"
|
repository = "https://github.com/actix/actix-net.git"
|
||||||
documentation = "https://docs.rs/actix-tls"
|
|
||||||
categories = ["network-programming", "asynchronous"]
|
categories = ["network-programming", "asynchronous"]
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
@@ -64,7 +62,7 @@ tokio-native-tls = { version = "0.3", optional = true }
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.2.0"
|
actix-rt = "2.2.0"
|
||||||
actix-server = "2.0.0-beta.5"
|
actix-server = "2.0.0-beta.6"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
env_logger = "0.8"
|
env_logger = "0.8"
|
||||||
futures-util = { version = "0.3.7", default-features = false, features = ["sink"] }
|
futures-util = { version = "0.3.7", default-features = false, features = ["sink"] }
|
||||||
|
@@ -19,7 +19,7 @@ pub trait Address: Unpin + 'static {
|
|||||||
|
|
||||||
impl Address for String {
|
impl Address for String {
|
||||||
fn hostname(&self) -> &str {
|
fn hostname(&self) -> &str {
|
||||||
&self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -164,7 +164,7 @@ impl<T: Address> Service<Connect<T>> for Resolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Self::Custom(resolver) => {
|
Self::Custom(resolver) => {
|
||||||
let resolver = Rc::clone(&resolver);
|
let resolver = Rc::clone(resolver);
|
||||||
ResolverFuture::LookupCustom(Box::pin(async move {
|
ResolverFuture::LookupCustom(Box::pin(async move {
|
||||||
let addrs = resolver
|
let addrs = resolver
|
||||||
.lookup(req.hostname(), req.port())
|
.lookup(req.hostname(), req.port())
|
||||||
|
@@ -5,6 +5,7 @@
|
|||||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||||
|
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
|
#[allow(unused_extern_crates)]
|
||||||
extern crate tls_openssl as openssl;
|
extern crate tls_openssl as openssl;
|
||||||
|
|
||||||
#[cfg(feature = "accept")]
|
#[cfg(feature = "accept")]
|
||||||
|
@@ -24,4 +24,5 @@ serde = { version = "1.0", optional = true }
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
ahash = { version = "0.7", default-features = false }
|
# TODO: remove when ahash MSRV is restored
|
||||||
|
ahash = { version = "=0.7.4", default-features = false }
|
||||||
|
1
clippy.toml
Normal file
1
clippy.toml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
msrv = "1.48"
|
@@ -18,4 +18,4 @@ futures-util = { version = "0.3.7", default-features = false }
|
|||||||
local-waker = "0.1"
|
local-waker = "0.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { version = "1", features = ["rt", "macros"] }
|
tokio = { version = "1.5.1", features = ["rt", "macros"] }
|
||||||
|
Reference in New Issue
Block a user