mirror of
https://github.com/fafhrd91/actix-net
synced 2025-08-13 02:47:08 +02:00
Compare commits
45 Commits
service-v2
...
service-v2
Author | SHA1 | Date | |
---|---|---|---|
|
b03fe7c5b6 | ||
|
6fed1c3e7d | ||
|
c3d697df97 | ||
|
80a362712f | ||
|
2b1edb95ea | ||
|
4644fa41cf | ||
|
98c37fe47d | ||
|
b9455d2ca9 | ||
|
0183b0f8cc | ||
|
b122a1ae1a | ||
|
4303058243 | ||
|
48b2e11509 | ||
|
5379a46a99 | ||
|
f8f1ac94bc | ||
|
82cd5b8290 | ||
|
c65e8524b2 | ||
|
a83dfaa162 | ||
|
e4ec956001 | ||
|
95cba659ff | ||
|
5687e81d9f | ||
|
a0fe2a9b2e | ||
|
ad22a93466 | ||
|
c2d5b2398a | ||
|
5b1ff30dd9 | ||
|
e1317bb3a0 | ||
|
dcea009158 | ||
|
13c18b8a51 | ||
|
06b17d6a43 | ||
|
605ec25143 | ||
|
3824493fd3 | ||
|
3be3e11aa5 | ||
|
6a5ea0342b | ||
|
23b1f63345 | ||
|
3aa037d07d | ||
|
cf21df14f2 | ||
|
a1bf8662c9 | ||
|
6f4d2220fa | ||
|
54b22f9fce | ||
|
983abec77d | ||
|
e4d4ae21ee | ||
|
8ad5f58d38 | ||
|
613b2be51f | ||
|
b2e9640952 | ||
|
76338a5822 | ||
|
978e4f25fb |
@@ -1,3 +1,23 @@
|
||||
[alias]
|
||||
chk = "hack check --workspace --all-features --tests --examples"
|
||||
lint = "hack --clean-per-run clippy --workspace --tests --examples"
|
||||
chk = "check --workspace --all-features --tests --examples --bins"
|
||||
lint = "clippy --workspace --all-features --tests --examples --bins -- -Dclippy::todo"
|
||||
|
||||
ci-doctest = "test --workspace --all-features --doc --no-fail-fast -- --nocapture"
|
||||
|
||||
# just check the library (without dev deps)
|
||||
ci-check-min = "hack --workspace check --no-default-features"
|
||||
ci-check-lib = "hack --workspace --feature-powerset --exclude-features io-uring check"
|
||||
ci-check-lib-linux = "hack --workspace --feature-powerset check"
|
||||
|
||||
# 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"
|
||||
|
93
.github/workflows/ci.yml
vendored
93
.github/workflows/ci.yml
vendored
@@ -75,38 +75,47 @@ jobs:
|
||||
command: install
|
||||
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
|
||||
with:
|
||||
command: hack
|
||||
args: check --workspace --no-default-features
|
||||
|
||||
- name: check minimal + tests
|
||||
with: { command: ci-check-lib }
|
||||
- name: check lib
|
||||
if: matrix.target.os == 'ubuntu-latest'
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: hack
|
||||
args: check --workspace --no-default-features --tests --examples
|
||||
|
||||
- name: check default
|
||||
with: { command: ci-check-lib-linux }
|
||||
- name: check lib
|
||||
if: matrix.target.triple == 'x86_64-pc-windows-gnu'
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --workspace --tests --examples
|
||||
|
||||
with: { command: ci-check-min }
|
||||
|
||||
- name: check full
|
||||
# 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
|
||||
with:
|
||||
command: check
|
||||
args: --workspace --all-features --tests --examples
|
||||
with: { command: ci-check }
|
||||
- name: check all
|
||||
if: matrix.target.os == 'ubuntu-latest'
|
||||
uses: actions-rs/cargo@v1
|
||||
with: { command: ci-check-linux }
|
||||
|
||||
- name: tests
|
||||
if: matrix.target.triple != 'x86_64-pc-windows-gnu'
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --workspace --all-features --no-fail-fast -- --nocapture
|
||||
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: Generate coverage file
|
||||
if: >
|
||||
@@ -122,10 +131,40 @@ jobs:
|
||||
&& matrix.version == 'stable'
|
||||
&& github.ref == 'refs/heads/master'
|
||||
uses: codecov/codecov-action@v1
|
||||
with:
|
||||
file: cobertura.xml
|
||||
with: { file: cobertura.xml }
|
||||
|
||||
- name: Clear the cargo caches
|
||||
run: |
|
||||
cargo install cargo-cache --no-default-features --features ci-autoclean
|
||||
cargo install cargo-cache --version 0.6.2 --no-default-features --features ci-autoclean
|
||||
cargo-cache
|
||||
|
||||
rustdoc:
|
||||
name: rustdoc
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
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
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --workspace --tests --all-features
|
||||
args: --workspace --all-features --tests --examples --bins -- -Dclippy::todo
|
||||
|
@@ -2,7 +2,6 @@
|
||||
members = [
|
||||
"actix-codec",
|
||||
"actix-macros",
|
||||
"actix-router",
|
||||
"actix-rt",
|
||||
"actix-server",
|
||||
"actix-service",
|
||||
@@ -17,7 +16,6 @@ members = [
|
||||
[patch.crates-io]
|
||||
actix-codec = { path = "actix-codec" }
|
||||
actix-macros = { path = "actix-macros" }
|
||||
actix-router = { path = "actix-router" }
|
||||
actix-rt = { path = "actix-rt" }
|
||||
actix-server = { path = "actix-server" }
|
||||
actix-service = { path = "actix-service" }
|
||||
@@ -27,3 +25,8 @@ actix-utils = { path = "actix-utils" }
|
||||
bytestring = { path = "bytestring" }
|
||||
local-channel = { path = "local-channel" }
|
||||
local-waker = { path = "local-waker" }
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
opt-level = 3
|
||||
codegen-units = 1
|
||||
|
@@ -3,6 +3,10 @@
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 0.4.0 - 2021-04-20
|
||||
* No significant changes since v0.4.0-beta.1.
|
||||
|
||||
|
||||
## 0.4.0-beta.1 - 2020-12-28
|
||||
* Replace `pin-project` with `pin-project-lite`. [#237]
|
||||
* Upgrade `tokio` dependency to `1`. [#237]
|
||||
@@ -23,28 +27,28 @@
|
||||
## 0.3.0-beta.1 - 2020-08-19
|
||||
* Use `.advance()` instead of `.split_to()`.
|
||||
* Upgrade `tokio-util` to `0.3`.
|
||||
* Improve `BytesCodec` `.encode()` performance
|
||||
* Simplify `BytesCodec` `.decode()`
|
||||
* Improve `BytesCodec::encode()` performance.
|
||||
* Simplify `BytesCodec::decode()`.
|
||||
* Rename methods on `Framed` to better describe their use.
|
||||
* Add method on `Framed` to get a pinned reference to the underlying I/O.
|
||||
* Add method on `Framed` check emptiness of read buffer.
|
||||
|
||||
|
||||
## 0.2.0 - 2019-12-10
|
||||
* Use specific futures dependencies
|
||||
* Use specific futures dependencies.
|
||||
|
||||
|
||||
## 0.2.0-alpha.4
|
||||
* Fix buffer remaining capacity calculation
|
||||
* Fix buffer remaining capacity calculation.
|
||||
|
||||
|
||||
## 0.2.0-alpha.3
|
||||
* Use tokio 0.2
|
||||
* Fix low/high watermark for write/read buffers
|
||||
* Use tokio 0.2.
|
||||
* Fix low/high watermark for write/read buffers.
|
||||
|
||||
|
||||
## 0.2.0-alpha.2
|
||||
* Migrated to `std::future`
|
||||
* Migrated to `std::future`.
|
||||
|
||||
|
||||
## 0.1.2 - 2019-03-27
|
||||
@@ -56,4 +60,4 @@
|
||||
|
||||
|
||||
## 0.1.0 - 2018-12-09
|
||||
* Move codec to separate crate
|
||||
* Move codec to separate crate.
|
||||
|
@@ -1,12 +1,10 @@
|
||||
[package]
|
||||
name = "actix-codec"
|
||||
version = "0.4.0-beta.1"
|
||||
version = "0.4.0"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Codec utilities for working with framed protocols"
|
||||
keywords = ["network", "framework", "async", "futures"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-net.git"
|
||||
documentation = "https://docs.rs/actix-codec"
|
||||
repository = "https://github.com/actix/actix-net"
|
||||
categories = ["network-programming", "asynchronous"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
|
@@ -21,14 +21,13 @@ bitflags::bitflags! {
|
||||
}
|
||||
|
||||
pin_project_lite::pin_project! {
|
||||
/// A unified `Stream` and `Sink` interface to an underlying I/O object, using
|
||||
/// the `Encoder` and `Decoder` traits to encode and decode frames.
|
||||
/// A unified `Stream` and `Sink` interface to an underlying I/O object, using the `Encoder` and
|
||||
/// `Decoder` traits to encode and decode frames.
|
||||
///
|
||||
/// Raw I/O objects work with byte sequences, but higher-level code usually
|
||||
/// wants to batch these into meaningful chunks, called "frames". This
|
||||
/// method layers framing on top of an I/O object, by using the `Encoder`/`Decoder`
|
||||
/// traits to handle encoding and decoding of message frames. Note that
|
||||
/// the incoming and outgoing frame types may be distinct.
|
||||
/// Raw I/O objects work with byte sequences, but higher-level code usually wants to batch these
|
||||
/// into meaningful chunks, called "frames". This method layers framing on top of an I/O object,
|
||||
/// by using the `Encoder`/`Decoder` traits to handle encoding and decoding of message frames.
|
||||
/// Note that the incoming and outgoing frame types may be distinct.
|
||||
pub struct Framed<T, U> {
|
||||
#[pin]
|
||||
io: T,
|
||||
@@ -44,10 +43,9 @@ where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
U: Decoder,
|
||||
{
|
||||
/// This function returns a *single* object that is both `Stream` and
|
||||
/// `Sink`; grouping this into a single object is often useful for layering
|
||||
/// things like gzip or TLS, which require both read and write access to the
|
||||
/// underlying object.
|
||||
/// This function returns a *single* object that is both `Stream` and `Sink`; grouping this into
|
||||
/// a single object is often useful for layering things like gzip or TLS, which require both
|
||||
/// read and write access to the underlying object.
|
||||
pub fn new(io: T, codec: U) -> Framed<T, U> {
|
||||
Framed {
|
||||
io,
|
||||
@@ -70,21 +68,18 @@ impl<T, U> Framed<T, U> {
|
||||
&mut self.codec
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying I/O stream wrapped by
|
||||
/// `Frame`.
|
||||
/// Returns a reference to the underlying I/O stream wrapped by `Frame`.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying stream
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
/// Note that care should be taken to not tamper with the underlying stream of data coming in as
|
||||
/// it may corrupt the stream of frames otherwise being worked with.
|
||||
pub fn io_ref(&self) -> &T {
|
||||
&self.io
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying I/O stream.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying stream
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
/// Note that care should be taken to not tamper with the underlying stream of data coming in as
|
||||
/// it may corrupt the stream of frames otherwise being worked with.
|
||||
pub fn io_mut(&mut self) -> &mut T {
|
||||
&mut self.io
|
||||
}
|
||||
@@ -184,10 +179,9 @@ impl<T, U> Framed<T, U> {
|
||||
{
|
||||
loop {
|
||||
let mut this = self.as_mut().project();
|
||||
// Repeatedly call `decode` or `decode_eof` as long as it is
|
||||
// "readable". Readable is defined as not having returned `None`. If
|
||||
// the upstream has returned EOF, and the decoder is no longer
|
||||
// readable, it can be assumed that the decoder will never become
|
||||
// Repeatedly call `decode` or `decode_eof` as long as it is "readable". Readable is
|
||||
// defined as not having returned `None`. If the upstream has returned EOF, and the
|
||||
// decoder is no longer readable, it can be assumed that the decoder will never become
|
||||
// readable again, at which point the stream is terminated.
|
||||
|
||||
if this.flags.contains(Flags::READABLE) {
|
||||
@@ -215,7 +209,7 @@ impl<T, U> Framed<T, U> {
|
||||
|
||||
debug_assert!(!this.flags.contains(Flags::EOF));
|
||||
|
||||
// Otherwise, try to read more data and try again. Make sure we've got room
|
||||
// Otherwise, try to read more data and try again. Make sure we've got room.
|
||||
let remaining = this.read_buf.capacity() - this.read_buf.len();
|
||||
if remaining < LW {
|
||||
this.read_buf.reserve(HW - remaining)
|
||||
@@ -341,13 +335,12 @@ where
|
||||
}
|
||||
|
||||
impl<T, U> Framed<T, U> {
|
||||
/// This function returns a *single* object that is both `Stream` and
|
||||
/// `Sink`; grouping this into a single object is often useful for layering
|
||||
/// things like gzip or TLS, which require both read and write access to the
|
||||
/// underlying object.
|
||||
/// This function returns a *single* object that is both `Stream` and `Sink`; grouping this into
|
||||
/// a single object is often useful for layering things like gzip or TLS, which require both
|
||||
/// read and write access to the underlying object.
|
||||
///
|
||||
/// These objects take a stream, a read buffer and a write buffer. These
|
||||
/// fields can be obtained from an existing `Framed` with the `into_parts` method.
|
||||
/// These objects take a stream, a read buffer and a write buffer. These fields can be obtained
|
||||
/// from an existing `Framed` with the `into_parts` method.
|
||||
pub fn from_parts(parts: FramedParts<T, U>) -> Framed<T, U> {
|
||||
Framed {
|
||||
io: parts.io,
|
||||
@@ -358,12 +351,11 @@ impl<T, U> Framed<T, U> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes the `Frame`, returning its underlying I/O stream, the buffer
|
||||
/// with unprocessed data, and the codec.
|
||||
/// Consumes the `Frame`, returning its underlying I/O stream, the buffer with unprocessed data,
|
||||
/// and the codec.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying stream
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
/// Note that care should be taken to not tamper with the underlying stream of data coming in as
|
||||
/// it may corrupt the stream of frames otherwise being worked with.
|
||||
pub fn into_parts(self) -> FramedParts<T, U> {
|
||||
FramedParts {
|
||||
io: self.io,
|
||||
@@ -376,14 +368,15 @@ impl<T, U> Framed<T, U> {
|
||||
}
|
||||
|
||||
/// `FramedParts` contains an export of the data of a Framed transport.
|
||||
/// It can be used to construct a new `Framed` with a different codec.
|
||||
/// It contains all current buffers and the inner transport.
|
||||
///
|
||||
/// It can be used to construct a new `Framed` with a different codec. It contains all current
|
||||
/// buffers and the inner transport.
|
||||
#[derive(Debug)]
|
||||
pub struct FramedParts<T, U> {
|
||||
/// The inner transport used to read bytes to and write bytes to
|
||||
/// The inner transport used to read bytes to and write bytes to.
|
||||
pub io: T,
|
||||
|
||||
/// The codec
|
||||
/// The codec object.
|
||||
pub codec: U,
|
||||
|
||||
/// The buffer with read but unprocessed data.
|
||||
@@ -396,7 +389,7 @@ pub struct FramedParts<T, U> {
|
||||
}
|
||||
|
||||
impl<T, U> FramedParts<T, U> {
|
||||
/// Create a new, default, `FramedParts`
|
||||
/// Creates a new default `FramedParts`.
|
||||
pub fn new(io: T, codec: U) -> FramedParts<T, U> {
|
||||
FramedParts {
|
||||
io,
|
||||
@@ -407,7 +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> {
|
||||
FramedParts {
|
||||
io,
|
||||
|
@@ -3,6 +3,12 @@
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 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
|
||||
* Update to latest `actix_rt::System::new` signature. [#261]
|
||||
|
||||
|
@@ -1,10 +1,12 @@
|
||||
[package]
|
||||
name = "actix-macros"
|
||||
version = "0.2.0"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
version = "0.2.1"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Ibraheem Ahmed <ibrah1440@gmail.com>",
|
||||
]
|
||||
description = "Macros for Actix system and runtime"
|
||||
repository = "https://github.com/actix/actix-net.git"
|
||||
documentation = "https://docs.rs/actix-macros"
|
||||
repository = "https://github.com/actix/actix-net"
|
||||
categories = ["network-programming", "asynchronous"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
|
@@ -27,8 +27,10 @@ use quote::quote;
|
||||
#[allow(clippy::needless_doctest_main)]
|
||||
#[proc_macro_attribute]
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
pub fn main(_: TokenStream, item: TokenStream) -> TokenStream {
|
||||
pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let mut input = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
|
||||
|
||||
let attrs = &input.attrs;
|
||||
let vis = &input.vis;
|
||||
let sig = &mut input.sig;
|
||||
@@ -43,13 +45,47 @@ pub fn main(_: TokenStream, item: TokenStream) -> TokenStream {
|
||||
.into();
|
||||
}
|
||||
|
||||
let mut system = syn::parse_str::<syn::Path>("::actix_rt::System").unwrap();
|
||||
|
||||
for arg in &args {
|
||||
match arg {
|
||||
syn::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;
|
||||
|
||||
(quote! {
|
||||
#(#attrs)*
|
||||
#vis #sig {
|
||||
actix_rt::System::new()
|
||||
.block_on(async move { #body })
|
||||
<#system>::new().block_on(async move { #body })
|
||||
}
|
||||
})
|
||||
.into()
|
||||
|
@@ -4,6 +4,9 @@ fn compile_macros() {
|
||||
t.pass("tests/trybuild/main-01-basic.rs");
|
||||
t.compile_fail("tests/trybuild/main-02-only-async.rs");
|
||||
t.pass("tests/trybuild/main-03-fn-params.rs");
|
||||
t.pass("tests/trybuild/main-04-system-path.rs");
|
||||
t.compile_fail("tests/trybuild/main-05-system-expect-path.rs");
|
||||
t.compile_fail("tests/trybuild/main-06-unknown-attr.rs");
|
||||
|
||||
t.pass("tests/trybuild/test-01-basic.rs");
|
||||
t.pass("tests/trybuild/test-02-keep-attrs.rs");
|
||||
|
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)]
|
||||
| ^^^^^^^^
|
@@ -1,67 +0,0 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 0.2.7 - 2021-02-06
|
||||
* Add `Router::recognize_checked` [#247]
|
||||
|
||||
[#247]: https://github.com/actix/actix-net/pull/247
|
||||
|
||||
|
||||
## 0.2.6 - 2021-01-09
|
||||
* Use `bytestring` version range compatible with Bytes v1.0. [#246]
|
||||
|
||||
[#246]: https://github.com/actix/actix-net/pull/246
|
||||
|
||||
|
||||
## 0.2.5 - 2020-09-20
|
||||
* Fix `from_hex()` method
|
||||
|
||||
|
||||
## 0.2.4 - 2019-12-31
|
||||
* Add `ResourceDef::resource_path_named()` path generation method
|
||||
|
||||
|
||||
## 0.2.3 - 2019-12-25
|
||||
* Add impl `IntoPattern` for `&String`
|
||||
|
||||
|
||||
## 0.2.2 - 2019-12-25
|
||||
* Use `IntoPattern` for `RouterBuilder::path()`
|
||||
|
||||
|
||||
## 0.2.1 - 2019-12-25
|
||||
* Add `IntoPattern` trait
|
||||
* Add multi-pattern resources
|
||||
|
||||
|
||||
## 0.2.0 - 2019-12-07
|
||||
* Update http to 0.2
|
||||
* Update regex to 1.3
|
||||
* Use bytestring instead of string
|
||||
|
||||
|
||||
## 0.1.5 - 2019-05-15
|
||||
* Remove debug prints
|
||||
|
||||
|
||||
## 0.1.4 - 2019-05-15
|
||||
* Fix checked resource match
|
||||
|
||||
|
||||
## 0.1.3 - 2019-04-22
|
||||
* Added support for `remainder match` (i.e "/path/{tail}*")
|
||||
|
||||
|
||||
## 0.1.2 - 2019-04-07
|
||||
* Export `Quoter` type
|
||||
* Allow to reset `Path` instance
|
||||
|
||||
|
||||
## 0.1.1 - 2019-04-03
|
||||
* Get dynamic segment by name instead of iterator.
|
||||
|
||||
|
||||
## 0.1.0 - 2019-03-09
|
||||
* Initial release
|
@@ -1,29 +0,0 @@
|
||||
[package]
|
||||
name = "actix-router"
|
||||
version = "0.2.7"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Resource path matching library"
|
||||
keywords = ["actix", "router", "routing"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-net.git"
|
||||
documentation = "https://docs.rs/actix-router"
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
name = "actix_router"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = ["http"]
|
||||
|
||||
[dependencies]
|
||||
regex = "1.3.1"
|
||||
serde = "1.0.104"
|
||||
bytestring = ">=0.1.5, <2"
|
||||
log = "0.4.8"
|
||||
http = { version = "0.2.2", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
http = "0.2.2"
|
||||
serde_derive = "1.0"
|
@@ -1 +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,222 +0,0 @@
|
||||
use std::ops::Index;
|
||||
|
||||
use serde::de;
|
||||
|
||||
use crate::de::PathDeserializer;
|
||||
use crate::{Resource, ResourcePath};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) enum PathItem {
|
||||
Static(&'static str),
|
||||
Segment(u16, u16),
|
||||
}
|
||||
|
||||
/// Resource path match information
|
||||
///
|
||||
/// If resource path contains variable patterns, `Path` stores them.
|
||||
#[derive(Debug)]
|
||||
pub struct Path<T> {
|
||||
path: T,
|
||||
pub(crate) skip: u16,
|
||||
pub(crate) segments: Vec<(&'static str, PathItem)>,
|
||||
}
|
||||
|
||||
impl<T: Default> Default for Path<T> {
|
||||
fn default() -> Self {
|
||||
Path {
|
||||
path: T::default(),
|
||||
skip: 0,
|
||||
segments: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> Clone for Path<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Path {
|
||||
path: self.path.clone(),
|
||||
skip: self.skip,
|
||||
segments: self.segments.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ResourcePath> Path<T> {
|
||||
pub fn new(path: T) -> Path<T> {
|
||||
Path {
|
||||
path,
|
||||
skip: 0,
|
||||
segments: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get reference to inner path instance
|
||||
#[inline]
|
||||
pub fn get_ref(&self) -> &T {
|
||||
&self.path
|
||||
}
|
||||
|
||||
/// Get mutable reference to inner path instance
|
||||
#[inline]
|
||||
pub fn get_mut(&mut self) -> &mut T {
|
||||
&mut self.path
|
||||
}
|
||||
|
||||
/// Path
|
||||
#[inline]
|
||||
pub fn path(&self) -> &str {
|
||||
let skip = self.skip as usize;
|
||||
let path = self.path.path();
|
||||
if skip <= path.len() {
|
||||
&path[skip..]
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
/// Set new path
|
||||
#[inline]
|
||||
pub fn set(&mut self, path: T) {
|
||||
self.skip = 0;
|
||||
self.path = path;
|
||||
self.segments.clear();
|
||||
}
|
||||
|
||||
/// Reset state
|
||||
#[inline]
|
||||
pub fn reset(&mut self) {
|
||||
self.skip = 0;
|
||||
self.segments.clear();
|
||||
}
|
||||
|
||||
/// Skip first `n` chars in path
|
||||
#[inline]
|
||||
pub fn skip(&mut self, n: u16) {
|
||||
self.skip += n;
|
||||
}
|
||||
|
||||
pub(crate) fn add(&mut self, name: &'static str, value: PathItem) {
|
||||
match value {
|
||||
PathItem::Static(s) => self.segments.push((name, PathItem::Static(s))),
|
||||
PathItem::Segment(begin, end) => self
|
||||
.segments
|
||||
.push((name, PathItem::Segment(self.skip + begin, self.skip + end))),
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn add_static(&mut self, name: &'static str, value: &'static str) {
|
||||
self.segments.push((name, PathItem::Static(value)));
|
||||
}
|
||||
|
||||
/// Check if there are any matched patterns
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.segments.is_empty()
|
||||
}
|
||||
|
||||
/// Check number of extracted parameters
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.segments.len()
|
||||
}
|
||||
|
||||
/// Get matched parameter by name without type conversion
|
||||
pub fn get(&self, key: &str) -> Option<&str> {
|
||||
for item in self.segments.iter() {
|
||||
if key == item.0 {
|
||||
return match item.1 {
|
||||
PathItem::Static(ref s) => Some(&s),
|
||||
PathItem::Segment(s, e) => {
|
||||
Some(&self.path.path()[(s as usize)..(e as usize)])
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
if key == "tail" {
|
||||
Some(&self.path.path()[(self.skip as usize)..])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Get unprocessed part of the path
|
||||
pub fn unprocessed(&self) -> &str {
|
||||
&self.path.path()[(self.skip as usize)..]
|
||||
}
|
||||
|
||||
/// Get matched parameter by name.
|
||||
///
|
||||
/// If keyed parameter is not available empty string is used as default
|
||||
/// value.
|
||||
pub fn query(&self, key: &str) -> &str {
|
||||
if let Some(s) = self.get(key) {
|
||||
s
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
/// Return iterator to items in parameter container
|
||||
pub fn iter(&self) -> PathIter<'_, T> {
|
||||
PathIter {
|
||||
idx: 0,
|
||||
params: self,
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to deserialize matching parameters to a specified type `U`
|
||||
pub fn load<'de, U: serde::Deserialize<'de>>(&'de self) -> Result<U, de::value::Error> {
|
||||
de::Deserialize::deserialize(PathDeserializer::new(self))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PathIter<'a, T> {
|
||||
idx: usize,
|
||||
params: &'a Path<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: ResourcePath> Iterator for PathIter<'a, T> {
|
||||
type Item = (&'a str, &'a str);
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<(&'a str, &'a str)> {
|
||||
if self.idx < self.params.len() {
|
||||
let idx = self.idx;
|
||||
let res = match self.params.segments[idx].1 {
|
||||
PathItem::Static(ref s) => &s,
|
||||
PathItem::Segment(s, e) => &self.params.path.path()[(s as usize)..(e as usize)],
|
||||
};
|
||||
self.idx += 1;
|
||||
return Some((&self.params.segments[idx].0, res));
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ResourcePath> Index<&'a str> for Path<T> {
|
||||
type Output = str;
|
||||
|
||||
fn index(&self, name: &'a str) -> &str {
|
||||
self.get(name)
|
||||
.expect("Value for parameter is not available")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ResourcePath> Index<usize> for Path<T> {
|
||||
type Output = str;
|
||||
|
||||
fn index(&self, idx: usize) -> &str {
|
||||
match self.segments[idx].1 {
|
||||
PathItem::Static(ref s) => &s,
|
||||
PathItem::Segment(s, e) => &self.path.path()[(s as usize)..(e as usize)],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ResourcePath> Resource<T> for Path<T> {
|
||||
fn resource_path(&mut self) -> &mut Self {
|
||||
self
|
||||
}
|
||||
}
|
@@ -1,945 +0,0 @@
|
||||
use std::cmp::min;
|
||||
use std::collections::HashMap;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
use regex::{escape, Regex, RegexSet};
|
||||
|
||||
use crate::path::{Path, PathItem};
|
||||
use crate::{IntoPattern, Resource, ResourcePath};
|
||||
|
||||
const MAX_DYNAMIC_SEGMENTS: usize = 16;
|
||||
|
||||
/// ResourceDef describes an entry in resources table
|
||||
///
|
||||
/// Resource definition can contain only 16 dynamic segments
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ResourceDef {
|
||||
id: u16,
|
||||
tp: PatternType,
|
||||
name: String,
|
||||
pattern: String,
|
||||
elements: Vec<PatternElement>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
enum PatternElement {
|
||||
Str(String),
|
||||
Var(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
enum PatternType {
|
||||
Static(String),
|
||||
Prefix(String),
|
||||
Dynamic(Regex, Vec<&'static str>, usize),
|
||||
DynamicSet(RegexSet, Vec<(Regex, Vec<&'static str>, usize)>),
|
||||
}
|
||||
|
||||
impl ResourceDef {
|
||||
/// Parse path pattern and create new `Pattern` instance.
|
||||
///
|
||||
/// Panics if path pattern is malformed.
|
||||
pub fn new<T: IntoPattern>(path: T) -> Self {
|
||||
if path.is_single() {
|
||||
let patterns = path.patterns();
|
||||
ResourceDef::with_prefix(&patterns[0], false)
|
||||
} else {
|
||||
let set = path.patterns();
|
||||
let mut data = Vec::new();
|
||||
let mut re_set = Vec::new();
|
||||
|
||||
for path in set {
|
||||
let (pattern, _, _, len) = ResourceDef::parse(&path, false);
|
||||
|
||||
let re = match Regex::new(&pattern) {
|
||||
Ok(re) => re,
|
||||
Err(err) => panic!("Wrong path pattern: \"{}\" {}", path, err),
|
||||
};
|
||||
// actix creates one router per thread
|
||||
let names: Vec<_> = re
|
||||
.capture_names()
|
||||
.filter_map(|name| {
|
||||
name.map(|name| Box::leak(Box::new(name.to_owned())).as_str())
|
||||
})
|
||||
.collect();
|
||||
data.push((re, names, len));
|
||||
re_set.push(pattern);
|
||||
}
|
||||
|
||||
ResourceDef {
|
||||
id: 0,
|
||||
tp: PatternType::DynamicSet(RegexSet::new(re_set).unwrap(), data),
|
||||
elements: Vec::new(),
|
||||
name: String::new(),
|
||||
pattern: "".to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse path pattern and create new `Pattern` instance.
|
||||
///
|
||||
/// Use `prefix` type instead of `static`.
|
||||
///
|
||||
/// Panics if path regex pattern is malformed.
|
||||
pub fn prefix(path: &str) -> Self {
|
||||
ResourceDef::with_prefix(path, true)
|
||||
}
|
||||
|
||||
/// Parse path pattern and create new `Pattern` instance.
|
||||
/// Inserts `/` to begging of the pattern.
|
||||
///
|
||||
///
|
||||
/// Use `prefix` type instead of `static`.
|
||||
///
|
||||
/// Panics if path regex pattern is malformed.
|
||||
pub fn root_prefix(path: &str) -> Self {
|
||||
ResourceDef::with_prefix(&insert_slash(path), true)
|
||||
}
|
||||
|
||||
/// Resource id
|
||||
pub fn id(&self) -> u16 {
|
||||
self.id
|
||||
}
|
||||
|
||||
/// Set resource id
|
||||
pub fn set_id(&mut self, id: u16) {
|
||||
self.id = id;
|
||||
}
|
||||
|
||||
/// Parse path pattern and create new `Pattern` instance with custom prefix
|
||||
fn with_prefix(path: &str, for_prefix: bool) -> Self {
|
||||
let path = path.to_owned();
|
||||
let (pattern, elements, is_dynamic, len) = ResourceDef::parse(&path, for_prefix);
|
||||
|
||||
let tp = if is_dynamic {
|
||||
let re = match Regex::new(&pattern) {
|
||||
Ok(re) => re,
|
||||
Err(err) => panic!("Wrong path pattern: \"{}\" {}", path, err),
|
||||
};
|
||||
// actix creates one router per thread
|
||||
let names = re
|
||||
.capture_names()
|
||||
.filter_map(|name| {
|
||||
name.map(|name| Box::leak(Box::new(name.to_owned())).as_str())
|
||||
})
|
||||
.collect();
|
||||
PatternType::Dynamic(re, names, len)
|
||||
} else if for_prefix {
|
||||
PatternType::Prefix(pattern)
|
||||
} else {
|
||||
PatternType::Static(pattern)
|
||||
};
|
||||
|
||||
ResourceDef {
|
||||
tp,
|
||||
elements,
|
||||
id: 0,
|
||||
name: String::new(),
|
||||
pattern: path,
|
||||
}
|
||||
}
|
||||
|
||||
/// Resource pattern name
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
/// Mutable reference to a name of a resource definition.
|
||||
pub fn name_mut(&mut self) -> &mut String {
|
||||
&mut self.name
|
||||
}
|
||||
|
||||
/// Path pattern of the resource
|
||||
pub fn pattern(&self) -> &str {
|
||||
&self.pattern
|
||||
}
|
||||
|
||||
/// Check if path matches this pattern.
|
||||
#[inline]
|
||||
pub fn is_match(&self, path: &str) -> bool {
|
||||
match self.tp {
|
||||
PatternType::Static(ref s) => s == path,
|
||||
PatternType::Prefix(ref s) => path.starts_with(s),
|
||||
PatternType::Dynamic(ref re, _, _) => re.is_match(path),
|
||||
PatternType::DynamicSet(ref re, _) => re.is_match(path),
|
||||
}
|
||||
}
|
||||
|
||||
/// Is prefix path a match against this resource.
|
||||
pub fn is_prefix_match(&self, path: &str) -> Option<usize> {
|
||||
let p_len = path.len();
|
||||
let path = if path.is_empty() { "/" } else { path };
|
||||
|
||||
match self.tp {
|
||||
PatternType::Static(ref s) => {
|
||||
if s == path {
|
||||
Some(p_len)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
PatternType::Dynamic(ref re, _, len) => {
|
||||
if let Some(captures) = re.captures(path) {
|
||||
let mut pos = 0;
|
||||
let mut passed = false;
|
||||
for capture in captures.iter() {
|
||||
if let Some(ref m) = capture {
|
||||
if !passed {
|
||||
passed = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
pos = m.end();
|
||||
}
|
||||
}
|
||||
Some(pos + len)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
PatternType::Prefix(ref s) => {
|
||||
let len = if path == s {
|
||||
s.len()
|
||||
} else if path.starts_with(s)
|
||||
&& (s.ends_with('/') || path.split_at(s.len()).1.starts_with('/'))
|
||||
{
|
||||
if s.ends_with('/') {
|
||||
s.len() - 1
|
||||
} else {
|
||||
s.len()
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
Some(min(p_len, len))
|
||||
}
|
||||
PatternType::DynamicSet(ref re, ref params) => {
|
||||
if let Some(idx) = re.matches(path).into_iter().next() {
|
||||
let (ref pattern, _, len) = params[idx];
|
||||
if let Some(captures) = pattern.captures(path) {
|
||||
let mut pos = 0;
|
||||
let mut passed = false;
|
||||
for capture in captures.iter() {
|
||||
if let Some(ref m) = capture {
|
||||
if !passed {
|
||||
passed = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
pos = m.end();
|
||||
}
|
||||
}
|
||||
Some(pos + len)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Is the given path and parameters a match against this pattern.
|
||||
pub fn match_path<T: ResourcePath>(&self, path: &mut Path<T>) -> bool {
|
||||
match self.tp {
|
||||
PatternType::Static(ref s) => {
|
||||
if s == path.path() {
|
||||
path.skip(path.len() as u16);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
PatternType::Prefix(ref s) => {
|
||||
let r_path = path.path();
|
||||
let len = if s == r_path {
|
||||
s.len()
|
||||
} else if r_path.starts_with(s)
|
||||
&& (s.ends_with('/') || r_path.split_at(s.len()).1.starts_with('/'))
|
||||
{
|
||||
if s.ends_with('/') {
|
||||
s.len() - 1
|
||||
} else {
|
||||
s.len()
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
let r_path_len = r_path.len();
|
||||
path.skip(min(r_path_len, len) as u16);
|
||||
true
|
||||
}
|
||||
PatternType::Dynamic(ref re, ref names, len) => {
|
||||
let mut idx = 0;
|
||||
let mut pos = 0;
|
||||
let mut segments: [PathItem; MAX_DYNAMIC_SEGMENTS] =
|
||||
[PathItem::Static(""); MAX_DYNAMIC_SEGMENTS];
|
||||
|
||||
if let Some(captures) = re.captures(path.path()) {
|
||||
for (no, name) in names.iter().enumerate() {
|
||||
if let Some(m) = captures.name(&name) {
|
||||
idx += 1;
|
||||
pos = m.end();
|
||||
segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16);
|
||||
} else {
|
||||
log::error!(
|
||||
"Dynamic path match but not all segments found: {}",
|
||||
name
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
for idx in 0..idx {
|
||||
path.add(names[idx], segments[idx]);
|
||||
}
|
||||
path.skip((pos + len) as u16);
|
||||
true
|
||||
}
|
||||
PatternType::DynamicSet(ref re, ref params) => {
|
||||
if let Some(idx) = re.matches(path.path()).into_iter().next() {
|
||||
let (ref pattern, ref names, len) = params[idx];
|
||||
let mut idx = 0;
|
||||
let mut pos = 0;
|
||||
let mut segments: [PathItem; MAX_DYNAMIC_SEGMENTS] =
|
||||
[PathItem::Static(""); MAX_DYNAMIC_SEGMENTS];
|
||||
|
||||
if let Some(captures) = pattern.captures(path.path()) {
|
||||
for (no, name) in names.iter().enumerate() {
|
||||
if let Some(m) = captures.name(&name) {
|
||||
idx += 1;
|
||||
pos = m.end();
|
||||
segments[no] =
|
||||
PathItem::Segment(m.start() as u16, m.end() as u16);
|
||||
} else {
|
||||
log::error!(
|
||||
"Dynamic path match but not all segments found: {}",
|
||||
name
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
for idx in 0..idx {
|
||||
path.add(names[idx], segments[idx]);
|
||||
}
|
||||
path.skip((pos + len) as u16);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Is the given path and parameters a match against this pattern?
|
||||
pub fn match_path_checked<R, T, F, U>(
|
||||
&self,
|
||||
res: &mut R,
|
||||
check: &F,
|
||||
user_data: &Option<U>,
|
||||
) -> bool
|
||||
where
|
||||
T: ResourcePath,
|
||||
R: Resource<T>,
|
||||
F: Fn(&R, &Option<U>) -> bool,
|
||||
{
|
||||
match self.tp {
|
||||
PatternType::Static(ref s) => {
|
||||
if s == res.resource_path().path() && check(res, user_data) {
|
||||
let path = res.resource_path();
|
||||
path.skip(path.len() as u16);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
PatternType::Prefix(ref s) => {
|
||||
let len = {
|
||||
let r_path = res.resource_path().path();
|
||||
if s == r_path {
|
||||
s.len()
|
||||
} else if r_path.starts_with(s)
|
||||
&& (s.ends_with('/') || r_path.split_at(s.len()).1.starts_with('/'))
|
||||
{
|
||||
if s.ends_with('/') {
|
||||
s.len() - 1
|
||||
} else {
|
||||
s.len()
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
if !check(res, user_data) {
|
||||
return false;
|
||||
}
|
||||
let path = res.resource_path();
|
||||
path.skip(min(path.path().len(), len) as u16);
|
||||
true
|
||||
}
|
||||
PatternType::Dynamic(ref re, ref names, len) => {
|
||||
let mut idx = 0;
|
||||
let mut pos = 0;
|
||||
let mut segments: [PathItem; MAX_DYNAMIC_SEGMENTS] =
|
||||
[PathItem::Static(""); MAX_DYNAMIC_SEGMENTS];
|
||||
|
||||
if let Some(captures) = re.captures(res.resource_path().path()) {
|
||||
for (no, name) in names.iter().enumerate() {
|
||||
if let Some(m) = captures.name(&name) {
|
||||
idx += 1;
|
||||
pos = m.end();
|
||||
segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16);
|
||||
} else {
|
||||
log::error!(
|
||||
"Dynamic path match but not all segments found: {}",
|
||||
name
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !check(res, user_data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let path = res.resource_path();
|
||||
for idx in 0..idx {
|
||||
path.add(names[idx], segments[idx]);
|
||||
}
|
||||
path.skip((pos + len) as u16);
|
||||
true
|
||||
}
|
||||
PatternType::DynamicSet(ref re, ref params) => {
|
||||
let path = res.resource_path().path();
|
||||
if let Some(idx) = re.matches(path).into_iter().next() {
|
||||
let (ref pattern, ref names, len) = params[idx];
|
||||
let mut idx = 0;
|
||||
let mut pos = 0;
|
||||
let mut segments: [PathItem; MAX_DYNAMIC_SEGMENTS] =
|
||||
[PathItem::Static(""); MAX_DYNAMIC_SEGMENTS];
|
||||
|
||||
if let Some(captures) = pattern.captures(path) {
|
||||
for (no, name) in names.iter().enumerate() {
|
||||
if let Some(m) = captures.name(&name) {
|
||||
idx += 1;
|
||||
pos = m.end();
|
||||
segments[no] =
|
||||
PathItem::Segment(m.start() as u16, m.end() as u16);
|
||||
} else {
|
||||
log::error!(
|
||||
"Dynamic path match but not all segments found: {}",
|
||||
name
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !check(res, user_data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let path = res.resource_path();
|
||||
for idx in 0..idx {
|
||||
path.add(names[idx], segments[idx]);
|
||||
}
|
||||
path.skip((pos + len) as u16);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Build resource path from elements. Returns `true` on success.
|
||||
pub fn resource_path<U, I>(&self, path: &mut String, elements: &mut U) -> bool
|
||||
where
|
||||
U: Iterator<Item = I>,
|
||||
I: AsRef<str>,
|
||||
{
|
||||
match self.tp {
|
||||
PatternType::Prefix(ref p) => path.push_str(p),
|
||||
PatternType::Static(ref p) => path.push_str(p),
|
||||
PatternType::Dynamic(..) => {
|
||||
for el in &self.elements {
|
||||
match *el {
|
||||
PatternElement::Str(ref s) => path.push_str(s),
|
||||
PatternElement::Var(_) => {
|
||||
if let Some(val) = elements.next() {
|
||||
path.push_str(val.as_ref())
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
PatternType::DynamicSet(..) => {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Build resource path from elements. Returns `true` on success.
|
||||
pub fn resource_path_named<K, V, S>(
|
||||
&self,
|
||||
path: &mut String,
|
||||
elements: &HashMap<K, V, S>,
|
||||
) -> bool
|
||||
where
|
||||
K: std::borrow::Borrow<str> + Eq + Hash,
|
||||
V: AsRef<str>,
|
||||
S: std::hash::BuildHasher,
|
||||
{
|
||||
match self.tp {
|
||||
PatternType::Prefix(ref p) => path.push_str(p),
|
||||
PatternType::Static(ref p) => path.push_str(p),
|
||||
PatternType::Dynamic(..) => {
|
||||
for el in &self.elements {
|
||||
match *el {
|
||||
PatternElement::Str(ref s) => path.push_str(s),
|
||||
PatternElement::Var(ref name) => {
|
||||
if let Some(val) = elements.get(name) {
|
||||
path.push_str(val.as_ref())
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
PatternType::DynamicSet(..) => {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn parse_param(pattern: &str) -> (PatternElement, String, &str, bool) {
|
||||
const DEFAULT_PATTERN: &str = "[^/]+";
|
||||
const DEFAULT_PATTERN_TAIL: &str = ".*";
|
||||
let mut params_nesting = 0usize;
|
||||
let close_idx = pattern
|
||||
.find(|c| match c {
|
||||
'{' => {
|
||||
params_nesting += 1;
|
||||
false
|
||||
}
|
||||
'}' => {
|
||||
params_nesting -= 1;
|
||||
params_nesting == 0
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
.expect("malformed dynamic segment");
|
||||
let (mut param, mut rem) = pattern.split_at(close_idx + 1);
|
||||
param = ¶m[1..param.len() - 1]; // Remove outer brackets
|
||||
let tail = rem == "*";
|
||||
|
||||
let (name, pattern) = match param.find(':') {
|
||||
Some(idx) => {
|
||||
if tail {
|
||||
panic!("Custom regex is not supported for remainder match");
|
||||
}
|
||||
let (name, pattern) = param.split_at(idx);
|
||||
(name, &pattern[1..])
|
||||
}
|
||||
None => (
|
||||
param,
|
||||
if tail {
|
||||
rem = &rem[1..];
|
||||
DEFAULT_PATTERN_TAIL
|
||||
} else {
|
||||
DEFAULT_PATTERN
|
||||
},
|
||||
),
|
||||
};
|
||||
(
|
||||
PatternElement::Var(name.to_string()),
|
||||
format!(r"(?P<{}>{})", &name, &pattern),
|
||||
rem,
|
||||
tail,
|
||||
)
|
||||
}
|
||||
|
||||
fn parse(
|
||||
mut pattern: &str,
|
||||
mut for_prefix: bool,
|
||||
) -> (String, Vec<PatternElement>, bool, usize) {
|
||||
if pattern.find('{').is_none() {
|
||||
return if let Some(path) = pattern.strip_suffix('*') {
|
||||
let re = String::from("^") + path + "(.*)";
|
||||
(re, vec![PatternElement::Str(String::from(path))], true, 0)
|
||||
} else {
|
||||
(
|
||||
String::from(pattern),
|
||||
vec![PatternElement::Str(String::from(pattern))],
|
||||
false,
|
||||
pattern.chars().count(),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
let mut elements = Vec::new();
|
||||
let mut re = String::from("^");
|
||||
let mut dyn_elements = 0;
|
||||
|
||||
while let Some(idx) = pattern.find('{') {
|
||||
let (prefix, rem) = pattern.split_at(idx);
|
||||
elements.push(PatternElement::Str(String::from(prefix)));
|
||||
re.push_str(&escape(prefix));
|
||||
let (param_pattern, re_part, rem, tail) = Self::parse_param(rem);
|
||||
if tail {
|
||||
for_prefix = true;
|
||||
}
|
||||
|
||||
elements.push(param_pattern);
|
||||
re.push_str(&re_part);
|
||||
pattern = rem;
|
||||
dyn_elements += 1;
|
||||
}
|
||||
|
||||
elements.push(PatternElement::Str(String::from(pattern)));
|
||||
re.push_str(&escape(pattern));
|
||||
|
||||
if dyn_elements > MAX_DYNAMIC_SEGMENTS {
|
||||
panic!(
|
||||
"Only {} dynamic segments are allowed, provided: {}",
|
||||
MAX_DYNAMIC_SEGMENTS, dyn_elements
|
||||
);
|
||||
}
|
||||
|
||||
if !for_prefix {
|
||||
re.push('$');
|
||||
}
|
||||
(re, elements, true, pattern.chars().count())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for ResourceDef {}
|
||||
|
||||
impl PartialEq for ResourceDef {
|
||||
fn eq(&self, other: &ResourceDef) -> bool {
|
||||
self.pattern == other.pattern
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for ResourceDef {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.pattern.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for ResourceDef {
|
||||
fn from(path: &'a str) -> ResourceDef {
|
||||
ResourceDef::new(path)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for ResourceDef {
|
||||
fn from(path: String) -> ResourceDef {
|
||||
ResourceDef::new(path)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn insert_slash(path: &str) -> String {
|
||||
let mut path = path.to_owned();
|
||||
if !path.is_empty() && !path.starts_with('/') {
|
||||
path.insert(0, '/');
|
||||
};
|
||||
path
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[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"));
|
||||
}
|
||||
|
||||
#[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,247 +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| {
|
||||
// SAFETY: we get data from http::Uri, which does UTF-8 checks already
|
||||
// this code only decodes valid pct encoded values
|
||||
unsafe { String::from_utf8_unchecked(data) }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn from_hex(v: u8) -> Option<u8> {
|
||||
if (b'0'..=b'9').contains(&v) {
|
||||
Some(v - 0x30) // ord('0') == 0x30
|
||||
} else if (b'A'..=b'F').contains(&v) {
|
||||
Some(v - 0x41 + 10) // ord('A') == 0x41
|
||||
} else if (b'a'..=b'f').contains(&v) {
|
||||
Some(v - 0x61 + 10) // ord('a') == 0x61
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn restore_ch(d1: u8, d2: u8) -> Option<u8> {
|
||||
from_hex(d1).and_then(|d1| from_hex(d2).map(move |d2| d1 << 4 | d2))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use http::Uri;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use super::*;
|
||||
use crate::{Path, ResourceDef};
|
||||
|
||||
#[test]
|
||||
fn test_parse_url() {
|
||||
let re = ResourceDef::new("/user/{id}/test");
|
||||
|
||||
let url = Uri::try_from("/user/2345/test").unwrap();
|
||||
let mut path = Path::new(Url::new(url));
|
||||
assert!(re.match_path(&mut path));
|
||||
assert_eq!(path.get("id").unwrap(), "2345");
|
||||
|
||||
let url = Uri::try_from("/user/qwe%25/test").unwrap();
|
||||
let mut path = Path::new(Url::new(url));
|
||||
assert!(re.match_path(&mut path));
|
||||
assert_eq!(path.get("id").unwrap(), "qwe%");
|
||||
|
||||
let url = Uri::try_from("/user/qwe%25rty/test").unwrap();
|
||||
let mut path = Path::new(Url::new(url));
|
||||
assert!(re.match_path(&mut path));
|
||||
assert_eq!(path.get("id").unwrap(), "qwe%rty");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_hex() {
|
||||
let hex = b"0123456789abcdefABCDEF";
|
||||
|
||||
for i in 0..256 {
|
||||
let c = i as u8;
|
||||
if hex.contains(&c) {
|
||||
assert!(from_hex(c).is_some())
|
||||
} else {
|
||||
assert!(from_hex(c).is_none())
|
||||
}
|
||||
}
|
||||
|
||||
let expected = [
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 10, 11, 12, 13, 14, 15,
|
||||
];
|
||||
for i in 0..hex.len() {
|
||||
assert_eq!(from_hex(hex[i]).unwrap(), expected[i]);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,11 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
* Add `io-uring` feature for enabling async file I/O on linux. [#374]
|
||||
* The `spawn` method can now resolve with non-unit outputs. [#369]
|
||||
|
||||
[#369]: https://github.com/actix/actix-net/pull/369
|
||||
[#374]: https://github.com/actix/actix-net/pull/374
|
||||
|
||||
|
||||
## 2.2.0 - 2021-03-29
|
||||
@@ -8,7 +13,7 @@
|
||||
`Ready` object in ok variant. [#293]
|
||||
* Breakage is acceptable since `ActixStream` was not intended to be public.
|
||||
|
||||
[#293] https://github.com/actix/actix-net/pull/293
|
||||
[#293]: https://github.com/actix/actix-net/pull/293
|
||||
|
||||
|
||||
## 2.1.0 - 2021-02-24
|
||||
|
@@ -21,6 +21,7 @@ path = "src/lib.rs"
|
||||
[features]
|
||||
default = ["macros"]
|
||||
macros = ["actix-macros"]
|
||||
io-uring = ["tokio-uring"]
|
||||
|
||||
[dependencies]
|
||||
actix-macros = { version = "0.2.0", optional = true }
|
||||
@@ -28,6 +29,9 @@ actix-macros = { version = "0.2.0", optional = true }
|
||||
futures-core = { version = "0.3", default-features = false }
|
||||
tokio = { version = "1.3", features = ["rt", "net", "parking_lot", "signal", "sync", "time"] }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
tokio-uring = { version = "0.1", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.2", features = ["full"] }
|
||||
hyper = { version = "0.14", default-features = false, features = ["server", "tcp", "http1"] }
|
||||
|
@@ -9,12 +9,9 @@ use std::{
|
||||
};
|
||||
|
||||
use futures_core::ready;
|
||||
use tokio::{sync::mpsc, task::LocalSet};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use crate::{
|
||||
runtime::{default_tokio_runtime, Runtime},
|
||||
system::{System, SystemCommand},
|
||||
};
|
||||
use crate::system::{System, SystemCommand};
|
||||
|
||||
pub(crate) static COUNT: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
@@ -98,16 +95,19 @@ impl Arbiter {
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if a [System] is not registered on the current thread.
|
||||
#[cfg(not(all(target_os = "linux", feature = "io-uring")))]
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub fn new() -> Arbiter {
|
||||
Self::with_tokio_rt(|| {
|
||||
default_tokio_runtime().expect("Cannot create new Arbiter's Runtime.")
|
||||
crate::runtime::default_tokio_runtime()
|
||||
.expect("Cannot create new Arbiter's Runtime.")
|
||||
})
|
||||
}
|
||||
|
||||
/// Spawn a new Arbiter using the [Tokio Runtime](tokio-runtime) returned from a closure.
|
||||
///
|
||||
/// [tokio-runtime]: tokio::runtime::Runtime
|
||||
#[cfg(not(all(target_os = "linux", feature = "io-uring")))]
|
||||
#[doc(hidden)]
|
||||
pub fn with_tokio_rt<F>(runtime_factory: F) -> Arbiter
|
||||
where
|
||||
@@ -127,7 +127,7 @@ impl Arbiter {
|
||||
.spawn({
|
||||
let tx = tx.clone();
|
||||
move || {
|
||||
let rt = Runtime::from(runtime_factory());
|
||||
let rt = crate::runtime::Runtime::from(runtime_factory());
|
||||
let hnd = ArbiterHandle::new(tx);
|
||||
|
||||
System::set_current(sys);
|
||||
@@ -159,15 +159,67 @@ impl Arbiter {
|
||||
Arbiter { tx, thread_handle }
|
||||
}
|
||||
|
||||
/// Sets up an Arbiter runner in a new System using the provided runtime local task set.
|
||||
pub(crate) fn in_new_system(local: &LocalSet) -> ArbiterHandle {
|
||||
/// Spawn a new Arbiter thread and start its event loop with `tokio-uring` runtime.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if a [System] is not registered on the current thread.
|
||||
#[cfg(all(target_os = "linux", feature = "io-uring"))]
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub fn new() -> Arbiter {
|
||||
let sys = System::current();
|
||||
let system_id = sys.id();
|
||||
let arb_id = COUNT.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
let name = format!("actix-rt|system:{}|arbiter:{}", system_id, arb_id);
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
|
||||
let (ready_tx, ready_rx) = std::sync::mpsc::channel::<()>();
|
||||
|
||||
let thread_handle = thread::Builder::new()
|
||||
.name(name.clone())
|
||||
.spawn({
|
||||
let tx = tx.clone();
|
||||
move || {
|
||||
let hnd = ArbiterHandle::new(tx);
|
||||
|
||||
System::set_current(sys);
|
||||
|
||||
HANDLE.with(|cell| *cell.borrow_mut() = Some(hnd.clone()));
|
||||
|
||||
// register arbiter
|
||||
let _ = System::current()
|
||||
.tx()
|
||||
.send(SystemCommand::RegisterArbiter(arb_id, hnd));
|
||||
|
||||
ready_tx.send(()).unwrap();
|
||||
|
||||
// run arbiter event processing loop
|
||||
tokio_uring::start(ArbiterRunner { rx });
|
||||
|
||||
// deregister arbiter
|
||||
let _ = System::current()
|
||||
.tx()
|
||||
.send(SystemCommand::DeregisterArbiter(arb_id));
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|err| {
|
||||
panic!("Cannot spawn Arbiter's thread: {:?}. {:?}", &name, err)
|
||||
});
|
||||
|
||||
ready_rx.recv().unwrap();
|
||||
|
||||
Arbiter { tx, thread_handle }
|
||||
}
|
||||
|
||||
/// Sets up an Arbiter runner in a new System using the environment's local set.
|
||||
pub(crate) fn in_new_system() -> ArbiterHandle {
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
|
||||
let hnd = ArbiterHandle::new(tx);
|
||||
|
||||
HANDLE.with(|cell| *cell.borrow_mut() = Some(hnd.clone()));
|
||||
|
||||
local.spawn_local(ArbiterRunner { rx });
|
||||
crate::spawn(ArbiterRunner { rx });
|
||||
|
||||
hnd
|
||||
}
|
||||
|
@@ -32,6 +32,10 @@
|
||||
//! arbiter.stop();
|
||||
//! arbiter.join().unwrap();
|
||||
//! ```
|
||||
//!
|
||||
//! # `io-uring` Support
|
||||
//! There is experimental support for using io-uring with this crate by enabling the
|
||||
//! `io-uring` feature. For now, it is semver exempt.
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![allow(clippy::type_complexity)]
|
||||
@@ -39,6 +43,9 @@
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![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 tokio::task::JoinHandle;
|
||||
@@ -46,7 +53,10 @@ use tokio::task::JoinHandle;
|
||||
// Cannot define a main macro when compiled into test harness.
|
||||
// Workaround for https://github.com/rust-lang/rust/issues/62127.
|
||||
#[cfg(all(feature = "macros", not(test)))]
|
||||
pub use actix_macros::{main, test};
|
||||
pub use actix_macros::main;
|
||||
|
||||
#[cfg(feature = "macros")]
|
||||
pub use actix_macros::test;
|
||||
|
||||
mod arbiter;
|
||||
mod runtime;
|
||||
@@ -155,14 +165,41 @@ pub mod task {
|
||||
pub use tokio::task::{spawn_blocking, yield_now, JoinError, JoinHandle};
|
||||
}
|
||||
|
||||
/// Spawns a future on the current thread.
|
||||
/// Spawns a future on the current thread as a new task.
|
||||
///
|
||||
/// If not immediately awaited, the task can be cancelled using [`JoinHandle::abort`].
|
||||
///
|
||||
/// The provided future is spawned as a new task; therefore, panics are caught.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if Actix system is not running.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use std::time::Duration;
|
||||
/// # actix_rt::Runtime::new().unwrap().block_on(async {
|
||||
/// // task resolves successfully
|
||||
/// assert_eq!(actix_rt::spawn(async { 1 }).await.unwrap(), 1);
|
||||
///
|
||||
/// // task panics
|
||||
/// assert!(actix_rt::spawn(async {
|
||||
/// panic!("panic is caught at task boundary");
|
||||
/// })
|
||||
/// .await
|
||||
/// .unwrap_err()
|
||||
/// .is_panic());
|
||||
///
|
||||
/// // task is cancelled before completion
|
||||
/// let handle = actix_rt::spawn(actix_rt::time::sleep(Duration::from_secs(100)));
|
||||
/// handle.abort();
|
||||
/// assert!(handle.await.unwrap_err().is_cancelled());
|
||||
/// # });
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn spawn<Fut>(f: Fut) -> JoinHandle<()>
|
||||
pub fn spawn<Fut>(f: Fut) -> JoinHandle<Fut::Output>
|
||||
where
|
||||
Fut: Future<Output = ()> + 'static,
|
||||
Fut: Future + 'static,
|
||||
Fut::Output: 'static,
|
||||
{
|
||||
tokio::task::spawn_local(f)
|
||||
}
|
||||
|
@@ -31,11 +31,6 @@ impl Runtime {
|
||||
})
|
||||
}
|
||||
|
||||
/// Reference to local task set.
|
||||
pub(crate) fn local_set(&self) -> &LocalSet {
|
||||
&self.local
|
||||
}
|
||||
|
||||
/// Offload a future onto the single-threaded runtime.
|
||||
///
|
||||
/// The returned join handle can be used to await the future's result.
|
||||
|
@@ -54,7 +54,7 @@ impl System {
|
||||
let (sys_tx, sys_rx) = mpsc::unbounded_channel();
|
||||
|
||||
let rt = Runtime::from(runtime_factory());
|
||||
let sys_arbiter = Arbiter::in_new_system(rt.local_set());
|
||||
let sys_arbiter = rt.block_on(async { Arbiter::in_new_system() });
|
||||
let system = System::construct(sys_tx, sys_arbiter.clone());
|
||||
|
||||
system
|
||||
|
@@ -1,14 +1,11 @@
|
||||
use std::{
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
mpsc::channel,
|
||||
Arc,
|
||||
},
|
||||
future::Future,
|
||||
sync::mpsc::channel,
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use actix_rt::{Arbiter, System};
|
||||
use actix_rt::{task::JoinError, Arbiter, System};
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
#[test]
|
||||
@@ -220,8 +217,8 @@ fn system_stop_stops_arbiters() {
|
||||
System::current().stop();
|
||||
sys.run().unwrap();
|
||||
|
||||
// account for slightly slow thread de-spawns (only observed on windows)
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
// account for slightly slow thread de-spawns
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
|
||||
// arbiter should be dead and return false
|
||||
assert!(!Arbiter::current().spawn_fn(|| {}));
|
||||
@@ -230,6 +227,7 @@ fn system_stop_stops_arbiters() {
|
||||
arb.join().unwrap();
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "io-uring"))]
|
||||
#[test]
|
||||
fn new_system_with_tokio() {
|
||||
let (tx, rx) = channel();
|
||||
@@ -262,8 +260,14 @@ fn new_system_with_tokio() {
|
||||
assert_eq!(rx.recv().unwrap(), 42);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "io-uring"))]
|
||||
#[test]
|
||||
fn new_arbiter_with_tokio() {
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
};
|
||||
|
||||
let _ = System::new();
|
||||
|
||||
let arb = Arbiter::with_tokio_rt(|| {
|
||||
@@ -286,7 +290,7 @@ fn new_arbiter_with_tokio() {
|
||||
|
||||
arb.join().unwrap();
|
||||
|
||||
assert_eq!(false, counter.load(Ordering::SeqCst));
|
||||
assert!(!counter.load(Ordering::SeqCst));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -298,3 +302,56 @@ fn try_current_no_system() {
|
||||
fn try_current_with_system() {
|
||||
System::new().block_on(async { assert!(System::try_current().is_some()) });
|
||||
}
|
||||
|
||||
#[allow(clippy::unit_cmp)]
|
||||
#[test]
|
||||
fn spawn_local() {
|
||||
System::new().block_on(async {
|
||||
// demonstrate that spawn -> R is strictly more capable than spawn -> ()
|
||||
|
||||
assert_eq!(actix_rt::spawn(async {}).await.unwrap(), ());
|
||||
assert_eq!(actix_rt::spawn(async { 1 }).await.unwrap(), 1);
|
||||
assert!(actix_rt::spawn(async { panic!("") }).await.is_err());
|
||||
|
||||
actix_rt::spawn(async { tokio::time::sleep(Duration::from_millis(50)).await })
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
fn g<F: Future<Output = Result<(), JoinError>>>(_f: F) {}
|
||||
g(actix_rt::spawn(async {}));
|
||||
// g(actix_rt::spawn(async { 1 })); // compile err
|
||||
|
||||
fn h<F: Future<Output = Result<R, JoinError>>, R>(_f: F) {}
|
||||
h(actix_rt::spawn(async {}));
|
||||
h(actix_rt::spawn(async { 1 }));
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(all(target_os = "linux", feature = "io-uring"))]
|
||||
#[test]
|
||||
fn tokio_uring_arbiter() {
|
||||
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,6 +1,20 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
* Remove `config` module. `ServiceConfig`, `ServiceRuntime` public types are removed due to this change. [#349]
|
||||
* Remove `ServerBuilder::configure` [#349]
|
||||
* Add `io-uring` feature for enabling async file I/O on linux. [#374]
|
||||
* Server no long listens to SIGHUP signal.
|
||||
It actually did not take any action when receiving SIGHUP, the only thing SIGHUP did was to stop
|
||||
the Server from receiving any future signal, because the `Signals` future stops on the first
|
||||
signal received [#389]
|
||||
|
||||
[#374]: https://github.com/actix/actix-net/pull/374
|
||||
[#349]: https://github.com/actix/actix-net/pull/349
|
||||
[#389]: https://github.com/actix/actix-net/pull/389
|
||||
|
||||
|
||||
## 2.0.0-beta.5 - 2021-04-20
|
||||
* Server shutdown would notify all workers to exit regardless if shutdown is graceful.
|
||||
This would make all worker shutdown immediately in force shutdown case. [#333]
|
||||
|
||||
|
@@ -1,14 +1,13 @@
|
||||
[package]
|
||||
name = "actix-server"
|
||||
version = "2.0.0-beta.4"
|
||||
version = "2.0.0-beta.5"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"fakeshadow <24548779@qq.com>",
|
||||
]
|
||||
description = "General purpose TCP server built for the Actix ecosystem"
|
||||
keywords = ["network", "framework", "async", "futures"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-net.git"
|
||||
repository = "https://github.com/actix/actix-net"
|
||||
categories = ["network-programming", "asynchronous"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
@@ -19,17 +18,17 @@ path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
io-uring = ["actix-rt/io-uring"]
|
||||
|
||||
[dependencies]
|
||||
actix-rt = { version = "2.0.0", default-features = false }
|
||||
actix-service = "2.0.0"
|
||||
actix-utils = "3.0.0-beta.4"
|
||||
actix-utils = "3.0.0"
|
||||
|
||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||
log = "0.4"
|
||||
mio = { version = "0.7.6", features = ["os-poll", "net"] }
|
||||
num_cpus = "1.13"
|
||||
slab = "0.4"
|
||||
tokio = { version = "1.2", features = ["sync"] }
|
||||
|
||||
[dev-dependencies]
|
||||
|
@@ -7,21 +7,14 @@ use actix_rt::{
|
||||
};
|
||||
use log::{error, info};
|
||||
use mio::{Interest, Poll, Token as MioToken};
|
||||
use slab::Slab;
|
||||
|
||||
use crate::server::Server;
|
||||
use crate::socket::{MioListener, SocketAddr};
|
||||
use crate::socket::MioListener;
|
||||
use crate::waker_queue::{WakerInterest, WakerQueue, WAKER_TOKEN};
|
||||
use crate::worker::{Conn, WorkerHandleAccept};
|
||||
use crate::Token;
|
||||
|
||||
struct ServerSocketInfo {
|
||||
/// Address of socket. Mainly used for logging.
|
||||
addr: SocketAddr,
|
||||
|
||||
/// Beware this is the crate token for identify socket and should not be confused
|
||||
/// with `mio::Token`.
|
||||
token: Token,
|
||||
token: usize,
|
||||
|
||||
lst: MioListener,
|
||||
|
||||
@@ -65,7 +58,7 @@ impl AcceptLoop {
|
||||
|
||||
pub(crate) fn start(
|
||||
&mut self,
|
||||
socks: Vec<(Token, MioListener)>,
|
||||
socks: Vec<(usize, MioListener)>,
|
||||
handles: Vec<WorkerHandleAccept>,
|
||||
) {
|
||||
let srv = self.srv.take().expect("Can not re-use AcceptInfo");
|
||||
@@ -84,7 +77,7 @@ struct Accept {
|
||||
srv: Server,
|
||||
next: usize,
|
||||
avail: Availability,
|
||||
backpressure: bool,
|
||||
paused: bool,
|
||||
}
|
||||
|
||||
/// Array of u128 with every bit as marker for a worker handle's availability.
|
||||
@@ -98,23 +91,22 @@ impl Default for Availability {
|
||||
|
||||
impl Availability {
|
||||
/// Check if any worker handle is available
|
||||
#[inline(always)]
|
||||
fn available(&self) -> bool {
|
||||
self.0.iter().any(|a| *a != 0)
|
||||
}
|
||||
|
||||
/// Check if worker handle is available by index
|
||||
#[inline(always)]
|
||||
fn get_available(&self, idx: usize) -> bool {
|
||||
let (offset, idx) = Self::offset(idx);
|
||||
|
||||
self.0[offset] & (1 << idx as u128) != 0
|
||||
}
|
||||
|
||||
/// Set worker handle available state by index.
|
||||
fn set_available(&mut self, idx: usize, avail: bool) {
|
||||
let (offset, idx) = if idx < 128 {
|
||||
(0, idx)
|
||||
} else if idx < 128 * 2 {
|
||||
(1, idx - 128)
|
||||
} else if idx < 128 * 3 {
|
||||
(2, idx - 128 * 2)
|
||||
} else if idx < 128 * 4 {
|
||||
(3, idx - 128 * 3)
|
||||
} else {
|
||||
panic!("Max WorkerHandle count is 512")
|
||||
};
|
||||
let (offset, idx) = Self::offset(idx);
|
||||
|
||||
let off = 1 << idx as u128;
|
||||
if avail {
|
||||
@@ -131,6 +123,21 @@ impl Availability {
|
||||
self.set_available(handle.idx(), true);
|
||||
})
|
||||
}
|
||||
|
||||
/// Get offset and adjusted index of given worker handle index.
|
||||
fn offset(idx: usize) -> (usize, usize) {
|
||||
if idx < 128 {
|
||||
(0, idx)
|
||||
} else if idx < 128 * 2 {
|
||||
(1, idx - 128)
|
||||
} else if idx < 128 * 3 {
|
||||
(2, idx - 128 * 2)
|
||||
} else if idx < 128 * 4 {
|
||||
(3, idx - 128 * 3)
|
||||
} else {
|
||||
panic!("Max WorkerHandle count is 512")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This function defines errors that are per-connection. Which basically
|
||||
@@ -150,7 +157,7 @@ impl Accept {
|
||||
pub(crate) fn start(
|
||||
poll: Poll,
|
||||
waker: WakerQueue,
|
||||
socks: Vec<(Token, MioListener)>,
|
||||
socks: Vec<(usize, MioListener)>,
|
||||
srv: Server,
|
||||
handles: Vec<WorkerHandleAccept>,
|
||||
) {
|
||||
@@ -161,10 +168,10 @@ impl Accept {
|
||||
.name("actix-server accept loop".to_owned())
|
||||
.spawn(move || {
|
||||
System::set_current(sys);
|
||||
let (mut accept, sockets) =
|
||||
let (mut accept, mut sockets) =
|
||||
Accept::new_with_sockets(poll, waker, socks, handles, srv);
|
||||
|
||||
accept.poll_with(sockets);
|
||||
accept.poll_with(&mut sockets);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
@@ -172,29 +179,25 @@ impl Accept {
|
||||
fn new_with_sockets(
|
||||
poll: Poll,
|
||||
waker: WakerQueue,
|
||||
socks: Vec<(Token, MioListener)>,
|
||||
socks: Vec<(usize, MioListener)>,
|
||||
handles: Vec<WorkerHandleAccept>,
|
||||
srv: Server,
|
||||
) -> (Accept, Slab<ServerSocketInfo>) {
|
||||
let mut sockets = Slab::new();
|
||||
for (hnd_token, mut lst) in socks.into_iter() {
|
||||
let addr = lst.local_addr();
|
||||
) -> (Accept, Vec<ServerSocketInfo>) {
|
||||
let sockets = socks
|
||||
.into_iter()
|
||||
.map(|(token, mut lst)| {
|
||||
// Start listening for incoming connections
|
||||
poll.registry()
|
||||
.register(&mut lst, MioToken(token), Interest::READABLE)
|
||||
.unwrap_or_else(|e| panic!("Can not register io: {}", e));
|
||||
|
||||
let entry = sockets.vacant_entry();
|
||||
let token = entry.key();
|
||||
|
||||
// Start listening for incoming connections
|
||||
poll.registry()
|
||||
.register(&mut lst, MioToken(token), Interest::READABLE)
|
||||
.unwrap_or_else(|e| panic!("Can not register io: {}", e));
|
||||
|
||||
entry.insert(ServerSocketInfo {
|
||||
addr,
|
||||
token: hnd_token,
|
||||
lst,
|
||||
timeout: None,
|
||||
});
|
||||
}
|
||||
ServerSocketInfo {
|
||||
token,
|
||||
lst,
|
||||
timeout: None,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut avail = Availability::default();
|
||||
|
||||
@@ -208,19 +211,19 @@ impl Accept {
|
||||
srv,
|
||||
next: 0,
|
||||
avail,
|
||||
backpressure: false,
|
||||
paused: false,
|
||||
};
|
||||
|
||||
(accept, sockets)
|
||||
}
|
||||
|
||||
fn poll_with(&mut self, mut sockets: Slab<ServerSocketInfo>) {
|
||||
fn poll_with(&mut self, sockets: &mut [ServerSocketInfo]) {
|
||||
let mut events = mio::Events::with_capacity(128);
|
||||
|
||||
loop {
|
||||
if let Err(e) = self.poll.poll(&mut events, None) {
|
||||
match e.kind() {
|
||||
std::io::ErrorKind::Interrupted => continue,
|
||||
io::ErrorKind::Interrupted => {}
|
||||
_ => panic!("Poll error: {}", e),
|
||||
}
|
||||
}
|
||||
@@ -228,130 +231,160 @@ impl Accept {
|
||||
for event in events.iter() {
|
||||
let token = event.token();
|
||||
match token {
|
||||
// This is a loop because interests for command from previous version was
|
||||
// a loop that would try to drain the command channel. It's yet unknown
|
||||
// if it's necessary/good practice to actively drain the waker queue.
|
||||
WAKER_TOKEN => 'waker: loop {
|
||||
// take guard with every iteration so no new interest can be added
|
||||
// until the current task is done.
|
||||
let mut guard = self.waker.guard();
|
||||
match guard.pop_front() {
|
||||
// worker notify it becomes available. we may want to recover
|
||||
// from backpressure.
|
||||
Some(WakerInterest::WorkerAvailable(idx)) => {
|
||||
drop(guard);
|
||||
self.maybe_backpressure(&mut sockets, false);
|
||||
self.avail.set_available(idx, true);
|
||||
}
|
||||
// a new worker thread is made and it's handle would be added to Accept
|
||||
Some(WakerInterest::Worker(handle)) => {
|
||||
drop(guard);
|
||||
// maybe we want to recover from a backpressure.
|
||||
self.maybe_backpressure(&mut sockets, false);
|
||||
self.avail.set_available(handle.idx(), true);
|
||||
self.handles.push(handle);
|
||||
}
|
||||
// got timer interest and it's time to try register socket(s) again
|
||||
Some(WakerInterest::Timer) => {
|
||||
drop(guard);
|
||||
self.process_timer(&mut sockets)
|
||||
}
|
||||
Some(WakerInterest::Pause) => {
|
||||
drop(guard);
|
||||
self.deregister_all(&mut sockets);
|
||||
}
|
||||
Some(WakerInterest::Resume) => {
|
||||
drop(guard);
|
||||
sockets.iter_mut().for_each(|(token, info)| {
|
||||
self.register_logged(token, info);
|
||||
});
|
||||
}
|
||||
Some(WakerInterest::Stop) => {
|
||||
return self.deregister_all(&mut sockets);
|
||||
}
|
||||
// waker queue is drained
|
||||
None => {
|
||||
// Reset the WakerQueue before break so it does not grow infinitely
|
||||
WakerQueue::reset(&mut guard);
|
||||
break 'waker;
|
||||
}
|
||||
WAKER_TOKEN => {
|
||||
let exit = self.handle_waker(sockets);
|
||||
if exit {
|
||||
info!("Accept is stopped.");
|
||||
return;
|
||||
}
|
||||
},
|
||||
}
|
||||
_ => {
|
||||
let token = usize::from(token);
|
||||
self.accept(&mut sockets, token);
|
||||
self.accept(sockets, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_timer(&self, sockets: &mut Slab<ServerSocketInfo>) {
|
||||
fn handle_waker(&mut self, sockets: &mut [ServerSocketInfo]) -> bool {
|
||||
// This is a loop because interests for command from previous version was
|
||||
// a loop that would try to drain the command channel. It's yet unknown
|
||||
// if it's necessary/good practice to actively drain the waker queue.
|
||||
loop {
|
||||
// take guard with every iteration so no new interest can be added
|
||||
// until the current task is done.
|
||||
let mut guard = self.waker.guard();
|
||||
match guard.pop_front() {
|
||||
// worker notify it becomes available.
|
||||
Some(WakerInterest::WorkerAvailable(idx)) => {
|
||||
drop(guard);
|
||||
|
||||
self.avail.set_available(idx, true);
|
||||
|
||||
if !self.paused {
|
||||
self.accept_all(sockets);
|
||||
}
|
||||
}
|
||||
// a new worker thread is made and it's handle would be added to Accept
|
||||
Some(WakerInterest::Worker(handle)) => {
|
||||
drop(guard);
|
||||
|
||||
self.avail.set_available(handle.idx(), true);
|
||||
self.handles.push(handle);
|
||||
|
||||
if !self.paused {
|
||||
self.accept_all(sockets);
|
||||
}
|
||||
}
|
||||
// got timer interest and it's time to try register socket(s) again
|
||||
Some(WakerInterest::Timer) => {
|
||||
drop(guard);
|
||||
|
||||
self.process_timer(sockets)
|
||||
}
|
||||
Some(WakerInterest::Pause) => {
|
||||
drop(guard);
|
||||
|
||||
if !self.paused {
|
||||
self.paused = true;
|
||||
|
||||
self.deregister_all(sockets);
|
||||
}
|
||||
}
|
||||
Some(WakerInterest::Resume) => {
|
||||
drop(guard);
|
||||
|
||||
if self.paused {
|
||||
self.paused = false;
|
||||
|
||||
sockets.iter_mut().for_each(|info| {
|
||||
self.register_logged(info);
|
||||
});
|
||||
|
||||
self.accept_all(sockets);
|
||||
}
|
||||
}
|
||||
Some(WakerInterest::Stop) => {
|
||||
if !self.paused {
|
||||
self.deregister_all(sockets);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
// waker queue is drained
|
||||
None => {
|
||||
// Reset the WakerQueue before break so it does not grow infinitely
|
||||
WakerQueue::reset(&mut guard);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_timer(&self, sockets: &mut [ServerSocketInfo]) {
|
||||
let now = Instant::now();
|
||||
sockets
|
||||
.iter_mut()
|
||||
// Only sockets that had an associated timeout were deregistered.
|
||||
.filter(|(_, info)| info.timeout.is_some())
|
||||
.for_each(|(token, info)| {
|
||||
.filter(|info| info.timeout.is_some())
|
||||
.for_each(|info| {
|
||||
let inst = info.timeout.take().unwrap();
|
||||
|
||||
if now < inst {
|
||||
info.timeout = Some(inst);
|
||||
} else if !self.backpressure {
|
||||
self.register_logged(token, info);
|
||||
} else if !self.paused {
|
||||
self.register_logged(info);
|
||||
}
|
||||
|
||||
// Drop the timeout if server is in backpressure and socket timeout is expired.
|
||||
// When server recovers from backpressure it will register all sockets without
|
||||
// Drop the timeout if server is paused and socket timeout is expired.
|
||||
// When server recovers from pause it will register all sockets without
|
||||
// a timeout value so this socket register will be delayed till then.
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn register(&self, token: usize, info: &mut ServerSocketInfo) -> io::Result<()> {
|
||||
fn register(&self, info: &mut ServerSocketInfo) -> io::Result<()> {
|
||||
let token = MioToken(info.token);
|
||||
self.poll
|
||||
.registry()
|
||||
.register(&mut info.lst, MioToken(token), Interest::READABLE)
|
||||
.register(&mut info.lst, token, Interest::READABLE)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn register(&self, token: usize, info: &mut ServerSocketInfo) -> io::Result<()> {
|
||||
fn register(&self, info: &mut ServerSocketInfo) -> io::Result<()> {
|
||||
// On windows, calling register without deregister cause an error.
|
||||
// See https://github.com/actix/actix-web/issues/905
|
||||
// Calling reregister seems to fix the issue.
|
||||
let token = MioToken(info.token);
|
||||
self.poll
|
||||
.registry()
|
||||
.register(&mut info.lst, mio::Token(token), Interest::READABLE)
|
||||
.register(&mut info.lst, token, Interest::READABLE)
|
||||
.or_else(|_| {
|
||||
self.poll.registry().reregister(
|
||||
&mut info.lst,
|
||||
mio::Token(token),
|
||||
Interest::READABLE,
|
||||
)
|
||||
self.poll
|
||||
.registry()
|
||||
.reregister(&mut info.lst, token, Interest::READABLE)
|
||||
})
|
||||
}
|
||||
|
||||
fn register_logged(&self, token: usize, info: &mut ServerSocketInfo) {
|
||||
match self.register(token, info) {
|
||||
Ok(_) => info!("Resume accepting connections on {}", info.addr),
|
||||
fn register_logged(&self, info: &mut ServerSocketInfo) {
|
||||
match self.register(info) {
|
||||
Ok(_) => info!("Resume accepting connections on {}", info.lst.local_addr()),
|
||||
Err(e) => error!("Can not register server socket {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
fn deregister(&self, info: &mut ServerSocketInfo) -> io::Result<()> {
|
||||
self.poll.registry().deregister(&mut info.lst)
|
||||
}
|
||||
|
||||
fn deregister_logged(&self, info: &mut ServerSocketInfo) {
|
||||
match self.deregister(info) {
|
||||
Ok(_) => info!("Paused accepting connections on {}", info.addr),
|
||||
match self.poll.registry().deregister(&mut info.lst) {
|
||||
Ok(_) => info!("Paused accepting connections on {}", info.lst.local_addr()),
|
||||
Err(e) => {
|
||||
error!("Can not deregister server socket {}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn deregister_all(&self, sockets: &mut Slab<ServerSocketInfo>) {
|
||||
fn deregister_all(&self, sockets: &mut [ServerSocketInfo]) {
|
||||
// This is a best effort implementation with following limitation:
|
||||
//
|
||||
// Every ServerSocketInfo with associate timeout will be skipped and it's timeout
|
||||
@@ -364,70 +397,23 @@ impl Accept {
|
||||
.iter_mut()
|
||||
// Take all timeout.
|
||||
// This is to prevent Accept::process_timer method re-register a socket afterwards.
|
||||
.map(|(_, info)| (info.timeout.take(), info))
|
||||
.map(|info| (info.timeout.take(), info))
|
||||
// Socket info with a timeout is already deregistered so skip them.
|
||||
.filter(|(timeout, _)| timeout.is_none())
|
||||
.for_each(|(_, info)| self.deregister_logged(info));
|
||||
}
|
||||
|
||||
fn maybe_backpressure(&mut self, sockets: &mut Slab<ServerSocketInfo>, on: bool) {
|
||||
// Only operate when server is in a different backpressure than the given flag.
|
||||
if self.backpressure != on {
|
||||
self.backpressure = on;
|
||||
sockets
|
||||
.iter_mut()
|
||||
// Only operate on sockets without associated timeout.
|
||||
// Sockets with it should be handled by `accept` and `process_timer` methods.
|
||||
// They are already deregistered or need to be reregister in the future.
|
||||
.filter(|(_, info)| info.timeout.is_none())
|
||||
.for_each(|(token, info)| {
|
||||
if on {
|
||||
self.deregister_logged(info);
|
||||
} else {
|
||||
self.register_logged(token, info);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn accept_one(&mut self, sockets: &mut Slab<ServerSocketInfo>, mut conn: Conn) {
|
||||
if self.backpressure {
|
||||
// send_connection would remove fault worker from handles.
|
||||
// worst case here is conn get dropped after all handles are gone.
|
||||
while let Err(c) = self.send_connection(sockets, conn) {
|
||||
conn = c
|
||||
}
|
||||
} else {
|
||||
while self.avail.available() {
|
||||
let next = self.next();
|
||||
let idx = next.idx();
|
||||
if next.available() {
|
||||
self.avail.set_available(idx, true);
|
||||
match self.send_connection(sockets, conn) {
|
||||
Ok(_) => return,
|
||||
Err(c) => conn = c,
|
||||
}
|
||||
} else {
|
||||
self.avail.set_available(idx, false);
|
||||
self.set_next();
|
||||
}
|
||||
}
|
||||
|
||||
// Sending Conn failed due to either all workers are in error or not available.
|
||||
// Enter backpressure state and try again.
|
||||
self.maybe_backpressure(sockets, true);
|
||||
self.accept_one(sockets, conn);
|
||||
}
|
||||
}
|
||||
|
||||
// Send connection to worker and handle error.
|
||||
fn send_connection(
|
||||
&mut self,
|
||||
sockets: &mut Slab<ServerSocketInfo>,
|
||||
conn: Conn,
|
||||
) -> Result<(), Conn> {
|
||||
match self.next().send(conn) {
|
||||
fn send_connection(&mut self, conn: Conn) -> Result<(), Conn> {
|
||||
let next = self.next();
|
||||
match next.send(conn) {
|
||||
Ok(_) => {
|
||||
// Increment counter of WorkerHandle.
|
||||
// Set worker to unavailable with it hit max (Return false).
|
||||
if !next.inc_counter() {
|
||||
let idx = next.idx();
|
||||
self.avail.set_available(idx, false);
|
||||
}
|
||||
self.set_next();
|
||||
Ok(())
|
||||
}
|
||||
@@ -438,7 +424,6 @@ impl Accept {
|
||||
|
||||
if self.handles.is_empty() {
|
||||
error!("No workers");
|
||||
self.maybe_backpressure(sockets, true);
|
||||
// All workers are gone and Conn is nowhere to be sent.
|
||||
// Treat this situation as Ok and drop Conn.
|
||||
return Ok(());
|
||||
@@ -451,19 +436,38 @@ impl Accept {
|
||||
}
|
||||
}
|
||||
|
||||
fn accept(&mut self, sockets: &mut Slab<ServerSocketInfo>, token: usize) {
|
||||
fn accept_one(&mut self, mut conn: Conn) {
|
||||
loop {
|
||||
let info = sockets
|
||||
.get_mut(token)
|
||||
.expect("ServerSocketInfo is removed from Slab");
|
||||
let next = self.next();
|
||||
let idx = next.idx();
|
||||
|
||||
if self.avail.get_available(idx) {
|
||||
match self.send_connection(conn) {
|
||||
Ok(_) => return,
|
||||
Err(c) => conn = c,
|
||||
}
|
||||
} else {
|
||||
self.avail.set_available(idx, false);
|
||||
self.set_next();
|
||||
|
||||
if !self.avail.available() {
|
||||
while let Err(c) = self.send_connection(conn) {
|
||||
conn = c;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn accept(&mut self, sockets: &mut [ServerSocketInfo], token: usize) {
|
||||
while self.avail.available() {
|
||||
let info = &mut sockets[token];
|
||||
|
||||
match info.lst.accept() {
|
||||
Ok(io) => {
|
||||
let msg = Conn {
|
||||
io,
|
||||
token: info.token,
|
||||
};
|
||||
self.accept_one(sockets, msg);
|
||||
let conn = Conn { io, token };
|
||||
self.accept_one(conn);
|
||||
}
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => return,
|
||||
Err(ref e) if connection_error(e) => continue,
|
||||
@@ -491,11 +495,22 @@ impl Accept {
|
||||
}
|
||||
}
|
||||
|
||||
fn accept_all(&mut self, sockets: &mut [ServerSocketInfo]) {
|
||||
sockets
|
||||
.iter_mut()
|
||||
.map(|info| info.token)
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
.for_each(|idx| self.accept(sockets, idx))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn next(&self) -> &WorkerHandleAccept {
|
||||
&self.handles[self.next]
|
||||
}
|
||||
|
||||
/// Set next worker handle that would accept connection.
|
||||
#[inline(always)]
|
||||
fn set_next(&mut self) {
|
||||
self.next = (self.next + 1) % self.handles.len();
|
||||
}
|
||||
|
@@ -8,31 +8,29 @@ use std::{
|
||||
|
||||
use actix_rt::{self as rt, net::TcpStream, time::sleep, System};
|
||||
use log::{error, info};
|
||||
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver};
|
||||
use tokio::sync::oneshot;
|
||||
use tokio::sync::{
|
||||
mpsc::{unbounded_channel, UnboundedReceiver},
|
||||
oneshot,
|
||||
};
|
||||
|
||||
use crate::accept::AcceptLoop;
|
||||
use crate::config::{ConfiguredService, ServiceConfig};
|
||||
use crate::join_all;
|
||||
use crate::server::{Server, ServerCommand};
|
||||
use crate::service::{InternalServiceFactory, ServiceFactory, StreamNewService};
|
||||
use crate::signals::{Signal, Signals};
|
||||
use crate::socket::{MioListener, StdSocketAddr, StdTcpListener, ToSocketAddrs};
|
||||
use crate::socket::{MioTcpListener, MioTcpSocket};
|
||||
use crate::waker_queue::{WakerInterest, WakerQueue};
|
||||
use crate::worker::{
|
||||
ServerWorker, ServerWorkerConfig, WorkerAvailability, WorkerHandleAccept,
|
||||
WorkerHandleServer,
|
||||
};
|
||||
use crate::{join_all, Token};
|
||||
use crate::worker::{ServerWorker, ServerWorkerConfig, WorkerHandleAccept, WorkerHandleServer};
|
||||
|
||||
/// Server builder
|
||||
pub struct ServerBuilder {
|
||||
threads: usize,
|
||||
token: Token,
|
||||
token: usize,
|
||||
backlog: u32,
|
||||
handles: Vec<(usize, WorkerHandleServer)>,
|
||||
services: Vec<Box<dyn InternalServiceFactory>>,
|
||||
sockets: Vec<(Token, String, MioListener)>,
|
||||
sockets: Vec<(usize, String, MioListener)>,
|
||||
accept: AcceptLoop,
|
||||
exit: bool,
|
||||
no_signals: bool,
|
||||
@@ -56,7 +54,7 @@ impl ServerBuilder {
|
||||
|
||||
ServerBuilder {
|
||||
threads: num_cpus::get(),
|
||||
token: Token::default(),
|
||||
token: 0,
|
||||
handles: Vec::new(),
|
||||
services: Vec::new(),
|
||||
sockets: Vec::new(),
|
||||
@@ -149,32 +147,6 @@ impl ServerBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Execute external configuration as part of the server building process.
|
||||
///
|
||||
/// This function is useful for moving parts of configuration to a different module or
|
||||
/// even library.
|
||||
pub fn configure<F>(mut self, f: F) -> io::Result<ServerBuilder>
|
||||
where
|
||||
F: Fn(&mut ServiceConfig) -> io::Result<()>,
|
||||
{
|
||||
let mut cfg = ServiceConfig::new(self.threads, self.backlog);
|
||||
|
||||
f(&mut cfg)?;
|
||||
|
||||
if let Some(apply) = cfg.apply {
|
||||
let mut srv = ConfiguredService::new(apply);
|
||||
for (name, lst) in cfg.services {
|
||||
let token = self.token.next();
|
||||
srv.stream(token, name.clone(), lst.local_addr()?);
|
||||
self.sockets.push((token, name, MioListener::Tcp(lst)));
|
||||
}
|
||||
self.services.push(Box::new(srv));
|
||||
}
|
||||
self.threads = cfg.threads;
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Add new service to the server.
|
||||
pub fn bind<F, U, N: AsRef<str>>(mut self, name: N, addr: U, factory: F) -> io::Result<Self>
|
||||
where
|
||||
@@ -184,7 +156,7 @@ impl ServerBuilder {
|
||||
let sockets = bind_addr(addr, self.backlog)?;
|
||||
|
||||
for lst in sockets {
|
||||
let token = self.token.next();
|
||||
let token = self.next_token();
|
||||
self.services.push(StreamNewService::create(
|
||||
name.as_ref().to_string(),
|
||||
token,
|
||||
@@ -233,7 +205,7 @@ impl ServerBuilder {
|
||||
{
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
lst.set_nonblocking(true)?;
|
||||
let token = self.token.next();
|
||||
let token = self.next_token();
|
||||
let addr = StdSocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
|
||||
self.services.push(StreamNewService::create(
|
||||
name.as_ref().to_string(),
|
||||
@@ -259,7 +231,7 @@ impl ServerBuilder {
|
||||
lst.set_nonblocking(true)?;
|
||||
let addr = lst.local_addr()?;
|
||||
|
||||
let token = self.token.next();
|
||||
let token = self.next_token();
|
||||
self.services.push(StreamNewService::create(
|
||||
name.as_ref().to_string(),
|
||||
token,
|
||||
@@ -318,12 +290,11 @@ impl ServerBuilder {
|
||||
fn start_worker(
|
||||
&self,
|
||||
idx: usize,
|
||||
waker: WakerQueue,
|
||||
waker_queue: WakerQueue,
|
||||
) -> (WorkerHandleAccept, WorkerHandleServer) {
|
||||
let avail = WorkerAvailability::new(idx, waker);
|
||||
let services = self.services.iter().map(|v| v.clone_factory()).collect();
|
||||
|
||||
ServerWorker::start(idx, services, avail, self.worker_config)
|
||||
ServerWorker::start(idx, services, waker_queue, self.worker_config)
|
||||
}
|
||||
|
||||
fn handle_cmd(&mut self, item: ServerCommand) {
|
||||
@@ -364,7 +335,6 @@ impl ServerBuilder {
|
||||
completion: None,
|
||||
})
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
ServerCommand::Notify(tx) => {
|
||||
@@ -437,6 +407,12 @@ impl ServerBuilder {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn next_token(&mut self) -> usize {
|
||||
let token = self.token;
|
||||
self.token += 1;
|
||||
token
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for ServerBuilder {
|
||||
|
@@ -1,287 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
use std::future::Future;
|
||||
use std::{fmt, io};
|
||||
|
||||
use actix_rt::net::TcpStream;
|
||||
use actix_service::{
|
||||
fn_service, IntoServiceFactory as IntoBaseServiceFactory,
|
||||
ServiceFactory as BaseServiceFactory,
|
||||
};
|
||||
use actix_utils::{counter::CounterGuard, future::ready};
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use log::error;
|
||||
|
||||
use crate::builder::bind_addr;
|
||||
use crate::service::{BoxedServerService, InternalServiceFactory, StreamService};
|
||||
use crate::socket::{MioStream, MioTcpListener, StdSocketAddr, StdTcpListener, ToSocketAddrs};
|
||||
use crate::Token;
|
||||
|
||||
pub struct ServiceConfig {
|
||||
pub(crate) services: Vec<(String, MioTcpListener)>,
|
||||
pub(crate) apply: Option<Box<dyn ServiceRuntimeConfiguration>>,
|
||||
pub(crate) threads: usize,
|
||||
pub(crate) backlog: u32,
|
||||
}
|
||||
|
||||
impl ServiceConfig {
|
||||
pub(super) fn new(threads: usize, backlog: u32) -> ServiceConfig {
|
||||
ServiceConfig {
|
||||
threads,
|
||||
backlog,
|
||||
services: Vec::new(),
|
||||
apply: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set number of workers to start.
|
||||
///
|
||||
/// By default server uses number of available logical cpu as workers
|
||||
/// count.
|
||||
pub fn workers(&mut self, num: usize) {
|
||||
self.threads = num;
|
||||
}
|
||||
|
||||
/// Add new service to server
|
||||
pub fn bind<U, N: AsRef<str>>(&mut self, name: N, addr: U) -> io::Result<&mut Self>
|
||||
where
|
||||
U: ToSocketAddrs,
|
||||
{
|
||||
let sockets = bind_addr(addr, self.backlog)?;
|
||||
|
||||
for lst in sockets {
|
||||
self._listen(name.as_ref(), lst);
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Add new service to server
|
||||
pub fn listen<N: AsRef<str>>(&mut self, name: N, lst: StdTcpListener) -> &mut Self {
|
||||
self._listen(name, MioTcpListener::from_std(lst))
|
||||
}
|
||||
|
||||
/// Register service configuration function. This function get called
|
||||
/// during worker runtime configuration. It get executed in worker thread.
|
||||
pub fn apply<F>(&mut self, f: F) -> io::Result<()>
|
||||
where
|
||||
F: Fn(&mut ServiceRuntime) + Send + Clone + 'static,
|
||||
{
|
||||
self.apply = Some(Box::new(f));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn _listen<N: AsRef<str>>(&mut self, name: N, lst: MioTcpListener) -> &mut Self {
|
||||
if self.apply.is_none() {
|
||||
self.apply = Some(Box::new(not_configured));
|
||||
}
|
||||
self.services.push((name.as_ref().to_string(), lst));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct ConfiguredService {
|
||||
rt: Box<dyn ServiceRuntimeConfiguration>,
|
||||
names: HashMap<Token, (String, StdSocketAddr)>,
|
||||
topics: HashMap<String, Token>,
|
||||
services: Vec<Token>,
|
||||
}
|
||||
|
||||
impl ConfiguredService {
|
||||
pub(super) fn new(rt: Box<dyn ServiceRuntimeConfiguration>) -> Self {
|
||||
ConfiguredService {
|
||||
rt,
|
||||
names: HashMap::new(),
|
||||
topics: HashMap::new(),
|
||||
services: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn stream(&mut self, token: Token, name: String, addr: StdSocketAddr) {
|
||||
self.names.insert(token, (name.clone(), addr));
|
||||
self.topics.insert(name, token);
|
||||
self.services.push(token);
|
||||
}
|
||||
}
|
||||
|
||||
impl InternalServiceFactory for ConfiguredService {
|
||||
fn name(&self, token: Token) -> &str {
|
||||
&self.names[&token].0
|
||||
}
|
||||
|
||||
fn clone_factory(&self) -> Box<dyn InternalServiceFactory> {
|
||||
Box::new(Self {
|
||||
rt: self.rt.clone(),
|
||||
names: self.names.clone(),
|
||||
topics: self.topics.clone(),
|
||||
services: self.services.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn create(&self) -> LocalBoxFuture<'static, Result<Vec<(Token, BoxedServerService)>, ()>> {
|
||||
// configure services
|
||||
let mut rt = ServiceRuntime::new(self.topics.clone());
|
||||
self.rt.configure(&mut rt);
|
||||
rt.validate();
|
||||
let mut names = self.names.clone();
|
||||
let tokens = self.services.clone();
|
||||
|
||||
// construct services
|
||||
Box::pin(async move {
|
||||
let mut services = rt.services;
|
||||
// TODO: Proper error handling here
|
||||
for f in rt.onstart.into_iter() {
|
||||
f.await;
|
||||
}
|
||||
let mut res = vec![];
|
||||
for token in tokens {
|
||||
if let Some(srv) = services.remove(&token) {
|
||||
let newserv = srv.new_service(());
|
||||
match newserv.await {
|
||||
Ok(serv) => {
|
||||
res.push((token, serv));
|
||||
}
|
||||
Err(_) => {
|
||||
error!("Can not construct service");
|
||||
return Err(());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let name = names.remove(&token).unwrap().0;
|
||||
res.push((
|
||||
token,
|
||||
Box::new(StreamService::new(fn_service(move |_: TcpStream| {
|
||||
error!("Service {:?} is not configured", name);
|
||||
ready::<Result<_, ()>>(Ok(()))
|
||||
}))),
|
||||
));
|
||||
};
|
||||
}
|
||||
Ok(res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) trait ServiceRuntimeConfiguration: Send {
|
||||
fn clone(&self) -> Box<dyn ServiceRuntimeConfiguration>;
|
||||
|
||||
fn configure(&self, rt: &mut ServiceRuntime);
|
||||
}
|
||||
|
||||
impl<F> ServiceRuntimeConfiguration for F
|
||||
where
|
||||
F: Fn(&mut ServiceRuntime) + Send + Clone + 'static,
|
||||
{
|
||||
fn clone(&self) -> Box<dyn ServiceRuntimeConfiguration> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn configure(&self, rt: &mut ServiceRuntime) {
|
||||
(self)(rt)
|
||||
}
|
||||
}
|
||||
|
||||
fn not_configured(_: &mut ServiceRuntime) {
|
||||
error!("Service is not configured");
|
||||
}
|
||||
|
||||
pub struct ServiceRuntime {
|
||||
names: HashMap<String, Token>,
|
||||
services: HashMap<Token, BoxedNewService>,
|
||||
onstart: Vec<LocalBoxFuture<'static, ()>>,
|
||||
}
|
||||
|
||||
impl ServiceRuntime {
|
||||
fn new(names: HashMap<String, Token>) -> Self {
|
||||
ServiceRuntime {
|
||||
names,
|
||||
services: HashMap::new(),
|
||||
onstart: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn validate(&self) {
|
||||
for (name, token) in &self.names {
|
||||
if !self.services.contains_key(&token) {
|
||||
error!("Service {:?} is not configured", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Register service.
|
||||
///
|
||||
/// Name of the service must be registered during configuration stage with
|
||||
/// *ServiceConfig::bind()* or *ServiceConfig::listen()* methods.
|
||||
pub fn service<T, F>(&mut self, name: &str, service: F)
|
||||
where
|
||||
F: IntoBaseServiceFactory<T, TcpStream>,
|
||||
T: BaseServiceFactory<TcpStream, Config = ()> + 'static,
|
||||
T::Future: 'static,
|
||||
T::Service: 'static,
|
||||
T::InitError: fmt::Debug,
|
||||
{
|
||||
// let name = name.to_owned();
|
||||
if let Some(token) = self.names.get(name) {
|
||||
self.services.insert(
|
||||
*token,
|
||||
Box::new(ServiceFactory {
|
||||
inner: service.into_factory(),
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
panic!("Unknown service: {:?}", name);
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute future before services initialization.
|
||||
pub fn on_start<F>(&mut self, fut: F)
|
||||
where
|
||||
F: Future<Output = ()> + 'static,
|
||||
{
|
||||
self.onstart.push(Box::pin(fut))
|
||||
}
|
||||
}
|
||||
|
||||
type BoxedNewService = Box<
|
||||
dyn BaseServiceFactory<
|
||||
(CounterGuard, MioStream),
|
||||
Response = (),
|
||||
Error = (),
|
||||
InitError = (),
|
||||
Config = (),
|
||||
Service = BoxedServerService,
|
||||
Future = LocalBoxFuture<'static, Result<BoxedServerService, ()>>,
|
||||
>,
|
||||
>;
|
||||
|
||||
struct ServiceFactory<T> {
|
||||
inner: T,
|
||||
}
|
||||
|
||||
impl<T> BaseServiceFactory<(CounterGuard, MioStream)> for ServiceFactory<T>
|
||||
where
|
||||
T: BaseServiceFactory<TcpStream, Config = ()>,
|
||||
T::Future: 'static,
|
||||
T::Service: 'static,
|
||||
T::Error: 'static,
|
||||
T::InitError: fmt::Debug + 'static,
|
||||
{
|
||||
type Response = ();
|
||||
type Error = ();
|
||||
type Config = ();
|
||||
type Service = BoxedServerService;
|
||||
type InitError = ();
|
||||
type Future = LocalBoxFuture<'static, Result<BoxedServerService, ()>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
let fut = self.inner.new_service(());
|
||||
Box::pin(async move {
|
||||
match fut.await {
|
||||
Ok(s) => Ok(Box::new(StreamService::new(s)) as BoxedServerService),
|
||||
Err(e) => {
|
||||
error!("Can not construct service: {:?}", e);
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -6,7 +6,6 @@
|
||||
|
||||
mod accept;
|
||||
mod builder;
|
||||
mod config;
|
||||
mod server;
|
||||
mod service;
|
||||
mod signals;
|
||||
@@ -16,7 +15,6 @@ mod waker_queue;
|
||||
mod worker;
|
||||
|
||||
pub use self::builder::ServerBuilder;
|
||||
pub use self::config::{ServiceConfig, ServiceRuntime};
|
||||
pub use self::server::Server;
|
||||
pub use self::service::ServiceFactory;
|
||||
pub use self::test_server::TestServer;
|
||||
@@ -28,28 +26,6 @@ use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
/// Socket ID token
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct Token(usize);
|
||||
|
||||
impl Default for Token {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Token {
|
||||
fn new() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
|
||||
pub(crate) fn next(&mut self) -> Token {
|
||||
let token = Token(self.0);
|
||||
self.0 += 1;
|
||||
token
|
||||
}
|
||||
}
|
||||
|
||||
/// Start server building process
|
||||
pub fn new() -> ServerBuilder {
|
||||
ServerBuilder::default()
|
||||
|
@@ -3,15 +3,12 @@ use std::net::SocketAddr;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use actix_service::{Service, ServiceFactory as BaseServiceFactory};
|
||||
use actix_utils::{
|
||||
counter::CounterGuard,
|
||||
future::{ready, Ready},
|
||||
};
|
||||
use actix_utils::future::{ready, Ready};
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use log::error;
|
||||
|
||||
use crate::socket::{FromStream, MioStream};
|
||||
use crate::Token;
|
||||
use crate::worker::WorkerCounterGuard;
|
||||
|
||||
pub trait ServiceFactory<Stream: FromStream>: Send + Clone + 'static {
|
||||
type Factory: BaseServiceFactory<Stream, Config = ()>;
|
||||
@@ -20,16 +17,16 @@ pub trait ServiceFactory<Stream: FromStream>: Send + Clone + 'static {
|
||||
}
|
||||
|
||||
pub(crate) trait InternalServiceFactory: Send {
|
||||
fn name(&self, token: Token) -> &str;
|
||||
fn name(&self, token: usize) -> &str;
|
||||
|
||||
fn clone_factory(&self) -> Box<dyn InternalServiceFactory>;
|
||||
|
||||
fn create(&self) -> LocalBoxFuture<'static, Result<Vec<(Token, BoxedServerService)>, ()>>;
|
||||
fn create(&self) -> LocalBoxFuture<'static, Result<(usize, BoxedServerService), ()>>;
|
||||
}
|
||||
|
||||
pub(crate) type BoxedServerService = Box<
|
||||
dyn Service<
|
||||
(CounterGuard, MioStream),
|
||||
(WorkerCounterGuard, MioStream),
|
||||
Response = (),
|
||||
Error = (),
|
||||
Future = Ready<Result<(), ()>>,
|
||||
@@ -50,7 +47,7 @@ impl<S, I> StreamService<S, I> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, I> Service<(CounterGuard, MioStream)> for StreamService<S, I>
|
||||
impl<S, I> Service<(WorkerCounterGuard, MioStream)> for StreamService<S, I>
|
||||
where
|
||||
S: Service<I>,
|
||||
S::Future: 'static,
|
||||
@@ -65,7 +62,7 @@ where
|
||||
self.service.poll_ready(ctx).map_err(|_| ())
|
||||
}
|
||||
|
||||
fn call(&self, (guard, req): (CounterGuard, MioStream)) -> Self::Future {
|
||||
fn call(&self, (guard, req): (WorkerCounterGuard, MioStream)) -> Self::Future {
|
||||
ready(match FromStream::from_mio(req) {
|
||||
Ok(stream) => {
|
||||
let f = self.service.call(stream);
|
||||
@@ -86,7 +83,7 @@ where
|
||||
pub(crate) struct StreamNewService<F: ServiceFactory<Io>, Io: FromStream> {
|
||||
name: String,
|
||||
inner: F,
|
||||
token: Token,
|
||||
token: usize,
|
||||
addr: SocketAddr,
|
||||
_t: PhantomData<Io>,
|
||||
}
|
||||
@@ -98,7 +95,7 @@ where
|
||||
{
|
||||
pub(crate) fn create(
|
||||
name: String,
|
||||
token: Token,
|
||||
token: usize,
|
||||
inner: F,
|
||||
addr: SocketAddr,
|
||||
) -> Box<dyn InternalServiceFactory> {
|
||||
@@ -117,7 +114,7 @@ where
|
||||
F: ServiceFactory<Io>,
|
||||
Io: FromStream + Send + 'static,
|
||||
{
|
||||
fn name(&self, _: Token) -> &str {
|
||||
fn name(&self, _: usize) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
@@ -131,14 +128,14 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
fn create(&self) -> LocalBoxFuture<'static, Result<Vec<(Token, BoxedServerService)>, ()>> {
|
||||
fn create(&self) -> LocalBoxFuture<'static, Result<(usize, BoxedServerService), ()>> {
|
||||
let token = self.token;
|
||||
let fut = self.inner.create().new_service(());
|
||||
Box::pin(async move {
|
||||
match fut.await {
|
||||
Ok(inner) => {
|
||||
let service = Box::new(StreamService::new(inner)) as _;
|
||||
Ok(vec![(token, service)])
|
||||
Ok((token, service))
|
||||
}
|
||||
Err(_) => Err(()),
|
||||
}
|
||||
|
@@ -8,8 +8,6 @@ use crate::server::Server;
|
||||
#[allow(dead_code)]
|
||||
#[derive(PartialEq, Clone, Copy, Debug)]
|
||||
pub(crate) enum Signal {
|
||||
/// SIGHUP
|
||||
Hup,
|
||||
/// SIGINT
|
||||
Int,
|
||||
/// SIGTERM
|
||||
@@ -41,7 +39,6 @@ impl Signals {
|
||||
|
||||
let sig_map = [
|
||||
(unix::SignalKind::interrupt(), Signal::Int),
|
||||
(unix::SignalKind::hangup(), Signal::Hup),
|
||||
(unix::SignalKind::terminate(), Signal::Term),
|
||||
(unix::SignalKind::quit(), Signal::Quit),
|
||||
];
|
||||
|
@@ -23,9 +23,15 @@ pub(crate) enum MioListener {
|
||||
impl MioListener {
|
||||
pub(crate) fn local_addr(&self) -> SocketAddr {
|
||||
match *self {
|
||||
MioListener::Tcp(ref lst) => SocketAddr::Tcp(lst.local_addr().unwrap()),
|
||||
MioListener::Tcp(ref lst) => lst
|
||||
.local_addr()
|
||||
.map(SocketAddr::Tcp)
|
||||
.unwrap_or(SocketAddr::Unknown),
|
||||
#[cfg(unix)]
|
||||
MioListener::Uds(ref lst) => SocketAddr::Uds(lst.local_addr().unwrap()),
|
||||
MioListener::Uds(ref lst) => lst
|
||||
.local_addr()
|
||||
.map(SocketAddr::Uds)
|
||||
.unwrap_or(SocketAddr::Unknown),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,14 +116,15 @@ impl fmt::Debug for MioListener {
|
||||
impl fmt::Display for MioListener {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
MioListener::Tcp(ref lst) => write!(f, "{}", lst.local_addr().ok().unwrap()),
|
||||
MioListener::Tcp(ref lst) => write!(f, "{:?}", lst),
|
||||
#[cfg(unix)]
|
||||
MioListener::Uds(ref lst) => write!(f, "{:?}", lst.local_addr().ok().unwrap()),
|
||||
MioListener::Uds(ref lst) => write!(f, "{:?}", lst),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum SocketAddr {
|
||||
Unknown,
|
||||
Tcp(StdSocketAddr),
|
||||
#[cfg(unix)]
|
||||
Uds(mio::net::SocketAddr),
|
||||
@@ -126,9 +133,10 @@ pub(crate) enum SocketAddr {
|
||||
impl fmt::Display for SocketAddr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
SocketAddr::Tcp(ref addr) => write!(f, "{}", addr),
|
||||
Self::Unknown => write!(f, "Unknown SocketAddr"),
|
||||
Self::Tcp(ref addr) => write!(f, "{}", addr),
|
||||
#[cfg(unix)]
|
||||
SocketAddr::Uds(ref addr) => write!(f, "{:?}", addr),
|
||||
Self::Uds(ref addr) => write!(f, "{:?}", addr),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,9 +144,10 @@ impl fmt::Display for SocketAddr {
|
||||
impl fmt::Debug for SocketAddr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
SocketAddr::Tcp(ref addr) => write!(f, "{:?}", addr),
|
||||
Self::Unknown => write!(f, "Unknown SocketAddr"),
|
||||
Self::Tcp(ref addr) => write!(f, "{:?}", addr),
|
||||
#[cfg(unix)]
|
||||
SocketAddr::Uds(ref addr) => write!(f, "{:?}", addr),
|
||||
Self::Uds(ref addr) => write!(f, "{:?}", addr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -2,8 +2,9 @@ use std::{
|
||||
future::Future,
|
||||
mem,
|
||||
pin::Pin,
|
||||
rc::Rc,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc,
|
||||
},
|
||||
task::{Context, Poll},
|
||||
@@ -15,7 +16,6 @@ use actix_rt::{
|
||||
time::{sleep, Instant, Sleep},
|
||||
Arbiter,
|
||||
};
|
||||
use actix_utils::counter::Counter;
|
||||
use futures_core::{future::LocalBoxFuture, ready};
|
||||
use log::{error, info, trace};
|
||||
use tokio::sync::{
|
||||
@@ -23,10 +23,10 @@ use tokio::sync::{
|
||||
oneshot,
|
||||
};
|
||||
|
||||
use crate::join_all;
|
||||
use crate::service::{BoxedServerService, InternalServiceFactory};
|
||||
use crate::socket::MioStream;
|
||||
use crate::waker_queue::{WakerInterest, WakerQueue};
|
||||
use crate::{join_all, Token};
|
||||
|
||||
/// Stop worker message. Returns `true` on successful graceful shutdown.
|
||||
/// and `false` if some connections still alive when shutdown execute.
|
||||
@@ -38,35 +38,131 @@ pub(crate) struct Stop {
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Conn {
|
||||
pub io: MioStream,
|
||||
pub token: Token,
|
||||
pub token: usize,
|
||||
}
|
||||
|
||||
fn handle_pair(
|
||||
idx: usize,
|
||||
tx1: UnboundedSender<Conn>,
|
||||
tx2: UnboundedSender<Stop>,
|
||||
avail: WorkerAvailability,
|
||||
counter: Counter,
|
||||
) -> (WorkerHandleAccept, WorkerHandleServer) {
|
||||
let accept = WorkerHandleAccept { tx: tx1, avail };
|
||||
let accept = WorkerHandleAccept {
|
||||
idx,
|
||||
tx: tx1,
|
||||
counter,
|
||||
};
|
||||
|
||||
let server = WorkerHandleServer { idx, tx: tx2 };
|
||||
|
||||
(accept, server)
|
||||
}
|
||||
|
||||
/// counter: Arc<AtomicUsize> field is owned by `Accept` thread and `ServerWorker` thread.
|
||||
///
|
||||
/// `Accept` would increment the counter and `ServerWorker` would decrement it.
|
||||
///
|
||||
/// # Atomic Ordering:
|
||||
///
|
||||
/// `Accept` always look into it's cached `Availability` field for `ServerWorker` state.
|
||||
/// It lazily increment counter after successful dispatching new work to `ServerWorker`.
|
||||
/// On reaching counter limit `Accept` update it's cached `Availability` and mark worker as
|
||||
/// unable to accept any work.
|
||||
///
|
||||
/// `ServerWorker` always decrement the counter when every work received from `Accept` is done.
|
||||
/// 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.
|
||||
///
|
||||
/// Hence, a wake up would only happen after `Accept` increment it to limit.
|
||||
/// And a decrement to limit always wake up `Accept`.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Counter {
|
||||
counter: Arc<AtomicUsize>,
|
||||
limit: usize,
|
||||
}
|
||||
|
||||
impl Counter {
|
||||
pub(crate) fn new(limit: usize) -> Self {
|
||||
Self {
|
||||
counter: Arc::new(AtomicUsize::new(1)),
|
||||
limit,
|
||||
}
|
||||
}
|
||||
|
||||
/// Increment counter by 1 and return true when hitting limit
|
||||
#[inline(always)]
|
||||
pub(crate) fn inc(&self) -> bool {
|
||||
self.counter.fetch_add(1, Ordering::Relaxed) != self.limit
|
||||
}
|
||||
|
||||
/// Decrement counter by 1 and return true if crossing limit.
|
||||
#[inline(always)]
|
||||
pub(crate) fn dec(&self) -> bool {
|
||||
self.counter.fetch_sub(1, Ordering::Relaxed) == self.limit
|
||||
}
|
||||
|
||||
pub(crate) fn total(&self) -> usize {
|
||||
self.counter.load(Ordering::SeqCst) - 1
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct WorkerCounter {
|
||||
idx: usize,
|
||||
inner: Rc<(WakerQueue, Counter)>,
|
||||
}
|
||||
|
||||
impl Clone for WorkerCounter {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
idx: self.idx,
|
||||
inner: self.inner.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WorkerCounter {
|
||||
pub(crate) fn new(idx: usize, waker_queue: WakerQueue, counter: Counter) -> Self {
|
||||
Self {
|
||||
idx,
|
||||
inner: Rc::new((waker_queue, counter)),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn guard(&self) -> WorkerCounterGuard {
|
||||
WorkerCounterGuard(self.clone())
|
||||
}
|
||||
|
||||
fn total(&self) -> usize {
|
||||
self.inner.1.total()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct WorkerCounterGuard(WorkerCounter);
|
||||
|
||||
impl Drop for WorkerCounterGuard {
|
||||
fn drop(&mut self) {
|
||||
let (waker_queue, counter) = &*self.0.inner;
|
||||
if counter.dec() {
|
||||
waker_queue.wake(WakerInterest::WorkerAvailable(self.0.idx));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle to worker that can send connection message to worker and share the
|
||||
/// availability of worker to other thread.
|
||||
///
|
||||
/// Held by [Accept](crate::accept::Accept).
|
||||
pub(crate) struct WorkerHandleAccept {
|
||||
idx: usize,
|
||||
tx: UnboundedSender<Conn>,
|
||||
avail: WorkerAvailability,
|
||||
counter: Counter,
|
||||
}
|
||||
|
||||
impl WorkerHandleAccept {
|
||||
#[inline(always)]
|
||||
pub(crate) fn idx(&self) -> usize {
|
||||
self.avail.idx
|
||||
self.idx
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@@ -75,16 +171,17 @@ impl WorkerHandleAccept {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn available(&self) -> bool {
|
||||
self.avail.available()
|
||||
pub(crate) fn inc_counter(&self) -> bool {
|
||||
self.counter.inc()
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle to worker than can send stop message to worker.
|
||||
///
|
||||
/// Held by [ServerBuilder](crate::builder::ServerBuilder).
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct WorkerHandleServer {
|
||||
pub idx: usize,
|
||||
idx: usize,
|
||||
tx: UnboundedSender<Stop>,
|
||||
}
|
||||
|
||||
@@ -96,40 +193,6 @@ impl WorkerHandleServer {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct WorkerAvailability {
|
||||
idx: usize,
|
||||
waker: WakerQueue,
|
||||
available: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl WorkerAvailability {
|
||||
pub fn new(idx: usize, waker: WakerQueue) -> Self {
|
||||
WorkerAvailability {
|
||||
idx,
|
||||
waker,
|
||||
available: Arc::new(AtomicBool::new(false)),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn available(&self) -> bool {
|
||||
self.available.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
pub fn set(&self, val: bool) {
|
||||
// Ordering:
|
||||
//
|
||||
// There could be multiple set calls happen in one <ServerWorker as Future>::poll.
|
||||
// Order is important between them.
|
||||
let old = self.available.swap(val, Ordering::AcqRel);
|
||||
// Notify the accept on switched to available.
|
||||
if !old && val {
|
||||
self.waker.wake(WakerInterest::WorkerAvailable(self.idx));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Service worker.
|
||||
///
|
||||
/// Worker accepts Socket objects via unbounded channel and starts stream processing.
|
||||
@@ -138,9 +201,8 @@ pub(crate) struct ServerWorker {
|
||||
// It must be dropped as soon as ServerWorker dropping.
|
||||
rx: UnboundedReceiver<Conn>,
|
||||
rx2: UnboundedReceiver<Stop>,
|
||||
counter: WorkerCounter,
|
||||
services: Box<[WorkerService]>,
|
||||
availability: WorkerAvailability,
|
||||
conns: Counter,
|
||||
factories: Box<[Box<dyn InternalServiceFactory>]>,
|
||||
state: WorkerState,
|
||||
shutdown_timeout: Duration,
|
||||
@@ -207,35 +269,41 @@ impl ServerWorker {
|
||||
pub(crate) fn start(
|
||||
idx: usize,
|
||||
factories: Vec<Box<dyn InternalServiceFactory>>,
|
||||
availability: WorkerAvailability,
|
||||
waker_queue: WakerQueue,
|
||||
config: ServerWorkerConfig,
|
||||
) -> (WorkerHandleAccept, WorkerHandleServer) {
|
||||
assert!(!availability.available());
|
||||
|
||||
let (tx1, rx) = unbounded_channel();
|
||||
let (tx2, rx2) = unbounded_channel();
|
||||
let avail = availability.clone();
|
||||
|
||||
let counter = Counter::new(config.max_concurrent_connections);
|
||||
|
||||
let counter_clone = counter.clone();
|
||||
// every worker runs in it's own arbiter.
|
||||
// use a custom tokio runtime builder to change the settings of runtime.
|
||||
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()
|
||||
.enable_all()
|
||||
.max_blocking_threads(config.max_blocking_threads)
|
||||
.build()
|
||||
.unwrap()
|
||||
})
|
||||
.spawn(async move {
|
||||
});
|
||||
|
||||
arbiter.spawn(async move {
|
||||
let fut = factories
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, factory)| {
|
||||
let fut = factory.create();
|
||||
async move {
|
||||
fut.await.map(|r| {
|
||||
r.into_iter().map(|(t, s)| (idx, t, s)).collect::<Vec<_>>()
|
||||
})
|
||||
}
|
||||
async move { fut.await.map(|(t, s)| (idx, t, s)) }
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -248,9 +316,8 @@ impl ServerWorker {
|
||||
let services = match res {
|
||||
Ok(res) => res
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.fold(Vec::new(), |mut services, (factory, token, service)| {
|
||||
assert_eq!(token.0, services.len());
|
||||
assert_eq!(token, services.len());
|
||||
services.push(WorkerService {
|
||||
factory,
|
||||
service,
|
||||
@@ -271,8 +338,7 @@ impl ServerWorker {
|
||||
rx,
|
||||
rx2,
|
||||
services,
|
||||
availability,
|
||||
conns: Counter::new(config.max_concurrent_connections),
|
||||
counter: WorkerCounter::new(idx, waker_queue, counter_clone),
|
||||
factories: factories.into_boxed_slice(),
|
||||
state: Default::default(),
|
||||
shutdown_timeout: config.shutdown_timeout,
|
||||
@@ -280,16 +346,16 @@ impl ServerWorker {
|
||||
});
|
||||
});
|
||||
|
||||
handle_pair(idx, tx1, tx2, avail)
|
||||
handle_pair(idx, tx1, tx2, counter)
|
||||
}
|
||||
|
||||
fn restart_service(&mut self, token: Token, factory_id: usize) {
|
||||
fn restart_service(&mut self, idx: usize, factory_id: usize) {
|
||||
let factory = &self.factories[factory_id];
|
||||
trace!("Service {:?} failed, restarting", factory.name(token));
|
||||
self.services[token.0].status = WorkerServiceStatus::Restarting;
|
||||
trace!("Service {:?} failed, restarting", factory.name(idx));
|
||||
self.services[idx].status = WorkerServiceStatus::Restarting;
|
||||
self.state = WorkerState::Restarting(Restart {
|
||||
factory_id,
|
||||
token,
|
||||
token: idx,
|
||||
fut: factory.create(),
|
||||
});
|
||||
}
|
||||
@@ -307,8 +373,8 @@ impl ServerWorker {
|
||||
});
|
||||
}
|
||||
|
||||
fn check_readiness(&mut self, cx: &mut Context<'_>) -> Result<bool, (Token, usize)> {
|
||||
let mut ready = self.conns.available(cx);
|
||||
fn check_readiness(&mut self, cx: &mut Context<'_>) -> Result<bool, (usize, usize)> {
|
||||
let mut ready = true;
|
||||
for (idx, srv) in self.services.iter_mut().enumerate() {
|
||||
if srv.status == WorkerServiceStatus::Available
|
||||
|| srv.status == WorkerServiceStatus::Unavailable
|
||||
@@ -318,7 +384,7 @@ impl ServerWorker {
|
||||
if srv.status == WorkerServiceStatus::Unavailable {
|
||||
trace!(
|
||||
"Service {:?} is available",
|
||||
self.factories[srv.factory].name(Token(idx))
|
||||
self.factories[srv.factory].name(idx)
|
||||
);
|
||||
srv.status = WorkerServiceStatus::Available;
|
||||
}
|
||||
@@ -329,7 +395,7 @@ impl ServerWorker {
|
||||
if srv.status == WorkerServiceStatus::Available {
|
||||
trace!(
|
||||
"Service {:?} is unavailable",
|
||||
self.factories[srv.factory].name(Token(idx))
|
||||
self.factories[srv.factory].name(idx)
|
||||
);
|
||||
srv.status = WorkerServiceStatus::Unavailable;
|
||||
}
|
||||
@@ -337,10 +403,10 @@ impl ServerWorker {
|
||||
Poll::Ready(Err(_)) => {
|
||||
error!(
|
||||
"Service {:?} readiness check returned error, restarting",
|
||||
self.factories[srv.factory].name(Token(idx))
|
||||
self.factories[srv.factory].name(idx)
|
||||
);
|
||||
srv.status = WorkerServiceStatus::Failed;
|
||||
return Err((Token(idx), srv.factory));
|
||||
return Err((idx, srv.factory));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -359,8 +425,8 @@ enum WorkerState {
|
||||
|
||||
struct Restart {
|
||||
factory_id: usize,
|
||||
token: Token,
|
||||
fut: LocalBoxFuture<'static, Result<Vec<(Token, BoxedServerService)>, ()>>,
|
||||
token: usize,
|
||||
fut: LocalBoxFuture<'static, Result<(usize, BoxedServerService), ()>>,
|
||||
}
|
||||
|
||||
// Shutdown keep states necessary for server shutdown:
|
||||
@@ -381,10 +447,6 @@ impl Default for WorkerState {
|
||||
|
||||
impl Drop for ServerWorker {
|
||||
fn drop(&mut self) {
|
||||
// Set availability to true so if accept try to send connection to this worker
|
||||
// it would find worker is gone and remove it.
|
||||
// This is helpful when worker is dropped unexpected.
|
||||
self.availability.set(true);
|
||||
// Stop the Arbiter ServerWorker runs on on drop.
|
||||
Arbiter::current().stop();
|
||||
}
|
||||
@@ -399,8 +461,7 @@ impl Future for ServerWorker {
|
||||
// `StopWorker` message handler
|
||||
if let Poll::Ready(Some(Stop { graceful, tx })) = Pin::new(&mut this.rx2).poll_recv(cx)
|
||||
{
|
||||
this.availability.set(false);
|
||||
let num = this.conns.total();
|
||||
let num = this.counter.total();
|
||||
if num == 0 {
|
||||
info!("Shutting down worker, 0 connections");
|
||||
let _ = tx.send(true);
|
||||
@@ -427,7 +488,6 @@ impl Future for ServerWorker {
|
||||
WorkerState::Unavailable => match this.check_readiness(cx) {
|
||||
Ok(true) => {
|
||||
this.state = WorkerState::Available;
|
||||
this.availability.set(true);
|
||||
self.poll(cx)
|
||||
}
|
||||
Ok(false) => Poll::Pending,
|
||||
@@ -440,26 +500,22 @@ impl Future for ServerWorker {
|
||||
let factory_id = restart.factory_id;
|
||||
let token = restart.token;
|
||||
|
||||
let service = ready!(restart.fut.as_mut().poll(cx))
|
||||
let (token_new, service) = ready!(restart.fut.as_mut().poll(cx))
|
||||
.unwrap_or_else(|_| {
|
||||
panic!(
|
||||
"Can not restart {:?} service",
|
||||
this.factories[factory_id].name(token)
|
||||
)
|
||||
})
|
||||
.into_iter()
|
||||
// Find the same token from vector. There should be only one
|
||||
// So the first match would be enough.
|
||||
.find(|(t, _)| *t == token)
|
||||
.map(|(_, service)| service)
|
||||
.expect("No BoxedServerService found");
|
||||
});
|
||||
|
||||
assert_eq!(token, token_new);
|
||||
|
||||
trace!(
|
||||
"Service {:?} has been restarted",
|
||||
this.factories[factory_id].name(token)
|
||||
);
|
||||
|
||||
this.services[token.0].created(service);
|
||||
this.services[token].created(service);
|
||||
this.state = WorkerState::Unavailable;
|
||||
|
||||
self.poll(cx)
|
||||
@@ -468,7 +524,7 @@ impl Future for ServerWorker {
|
||||
// Wait for 1 second.
|
||||
ready!(shutdown.timer.as_mut().poll(cx));
|
||||
|
||||
if this.conns.total() == 0 {
|
||||
if this.counter.total() == 0 {
|
||||
// Graceful shutdown.
|
||||
if let WorkerState::Shutdown(shutdown) = mem::take(&mut this.state) {
|
||||
let _ = shutdown.tx.send(true);
|
||||
@@ -493,22 +549,20 @@ impl Future for ServerWorker {
|
||||
Ok(true) => {}
|
||||
Ok(false) => {
|
||||
trace!("Worker is unavailable");
|
||||
this.availability.set(false);
|
||||
this.state = WorkerState::Unavailable;
|
||||
return self.poll(cx);
|
||||
}
|
||||
Err((token, idx)) => {
|
||||
this.restart_service(token, idx);
|
||||
this.availability.set(false);
|
||||
return self.poll(cx);
|
||||
}
|
||||
}
|
||||
|
||||
// handle incoming io stream
|
||||
match ready!(Pin::new(&mut this.rx).poll_recv(cx)) {
|
||||
// handle incoming io stream
|
||||
Some(msg) => {
|
||||
let guard = this.conns.get();
|
||||
let _ = this.services[msg.token.0].service.call((guard, msg.io));
|
||||
let guard = this.counter.guard();
|
||||
let _ = this.services[msg.token].service.call((guard, msg.io));
|
||||
}
|
||||
None => return Poll::Ready(()),
|
||||
};
|
||||
|
@@ -142,57 +142,6 @@ fn test_start() {
|
||||
let _ = h.join();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_configure() {
|
||||
let addr1 = unused_addr();
|
||||
let addr2 = unused_addr();
|
||||
let addr3 = unused_addr();
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let num = Arc::new(AtomicUsize::new(0));
|
||||
let num2 = num.clone();
|
||||
|
||||
let h = thread::spawn(move || {
|
||||
let num = num2.clone();
|
||||
let sys = actix_rt::System::new();
|
||||
let srv = sys.block_on(lazy(|_| {
|
||||
Server::build()
|
||||
.disable_signals()
|
||||
.configure(move |cfg| {
|
||||
let num = num.clone();
|
||||
let lst = net::TcpListener::bind(addr3).unwrap();
|
||||
cfg.bind("addr1", addr1)
|
||||
.unwrap()
|
||||
.bind("addr2", addr2)
|
||||
.unwrap()
|
||||
.listen("addr3", lst)
|
||||
.apply(move |rt| {
|
||||
let num = num.clone();
|
||||
rt.service("addr1", fn_service(|_| ok::<_, ()>(())));
|
||||
rt.service("addr3", fn_service(|_| ok::<_, ()>(())));
|
||||
rt.on_start(lazy(move |_| {
|
||||
let _ = num.fetch_add(1, Ordering::Relaxed);
|
||||
}))
|
||||
})
|
||||
})
|
||||
.unwrap()
|
||||
.workers(1)
|
||||
.run()
|
||||
}));
|
||||
|
||||
let _ = tx.send((srv, actix_rt::System::current()));
|
||||
let _ = sys.run();
|
||||
});
|
||||
let (_, sys) = rx.recv().unwrap();
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
|
||||
assert!(net::TcpStream::connect(addr1).is_ok());
|
||||
assert!(net::TcpStream::connect(addr2).is_ok());
|
||||
assert!(net::TcpStream::connect(addr3).is_ok());
|
||||
assert_eq!(num.load(Ordering::Relaxed), 1);
|
||||
sys.stop();
|
||||
let _ = h.join();
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_max_concurrent_connections() {
|
||||
// Note:
|
||||
@@ -305,81 +254,6 @@ async fn test_service_restart() {
|
||||
let num_clone = num.clone();
|
||||
let num2_clone = num2.clone();
|
||||
|
||||
let h = thread::spawn(move || {
|
||||
actix_rt::System::new().block_on(async {
|
||||
let server = Server::build()
|
||||
.backlog(1)
|
||||
.disable_signals()
|
||||
.configure(move |cfg| {
|
||||
let num = num.clone();
|
||||
let num2 = num2.clone();
|
||||
cfg.bind("addr1", addr1)
|
||||
.unwrap()
|
||||
.bind("addr2", addr2)
|
||||
.unwrap()
|
||||
.apply(move |rt| {
|
||||
let num = num.clone();
|
||||
let num2 = num2.clone();
|
||||
rt.service(
|
||||
"addr1",
|
||||
fn_factory(move || {
|
||||
let num = num.clone();
|
||||
async move { Ok::<_, ()>(TestService(num)) }
|
||||
}),
|
||||
);
|
||||
rt.service(
|
||||
"addr2",
|
||||
fn_factory(move || {
|
||||
let num2 = num2.clone();
|
||||
async move { Ok::<_, ()>(TestService(num2)) }
|
||||
}),
|
||||
);
|
||||
})
|
||||
})
|
||||
.unwrap()
|
||||
.workers(1)
|
||||
.run();
|
||||
|
||||
let _ = tx.send((server.clone(), actix_rt::System::current()));
|
||||
server.await
|
||||
})
|
||||
});
|
||||
|
||||
let (server, sys) = rx.recv().unwrap();
|
||||
|
||||
for _ in 0..5 {
|
||||
TcpStream::connect(addr1)
|
||||
.await
|
||||
.unwrap()
|
||||
.shutdown()
|
||||
.await
|
||||
.unwrap();
|
||||
TcpStream::connect(addr2)
|
||||
.await
|
||||
.unwrap()
|
||||
.shutdown()
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
sleep(Duration::from_secs(3)).await;
|
||||
|
||||
assert!(num_clone.load(Ordering::SeqCst) > 5);
|
||||
assert!(num2_clone.load(Ordering::SeqCst) > 5);
|
||||
|
||||
sys.stop();
|
||||
let _ = server.stop(false);
|
||||
let _ = h.join().unwrap();
|
||||
|
||||
let addr1 = unused_addr();
|
||||
let addr2 = unused_addr();
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let num = Arc::new(AtomicUsize::new(0));
|
||||
let num2 = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let num_clone = num.clone();
|
||||
let num2_clone = num2.clone();
|
||||
|
||||
let h = thread::spawn(move || {
|
||||
let num = num.clone();
|
||||
actix_rt::System::new().block_on(async {
|
||||
|
@@ -3,6 +3,10 @@
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 2.0.1 - 2021-10-11
|
||||
* Documentation fix.
|
||||
|
||||
|
||||
## 2.0.0 - 2021-04-16
|
||||
* Removed pipeline and related structs/functions. [#335]
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-service"
|
||||
version = "2.0.0"
|
||||
version = "2.0.1"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
@@ -8,8 +8,8 @@ authors = [
|
||||
]
|
||||
description = "Service trait and combinators for representing asynchronous request/response operations."
|
||||
keywords = ["network", "framework", "async", "futures", "service"]
|
||||
categories = ["network-programming", "asynchronous", "no-std"]
|
||||
repository = "https://github.com/actix/actix-net"
|
||||
categories = ["network-programming", "asynchronous"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
@@ -24,5 +24,5 @@ pin-project-lite = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "2.0.0"
|
||||
actix-utils = "3.0.0-beta.4"
|
||||
actix-utils = "3.0.0"
|
||||
futures-util = { version = "0.3.7", default-features = false }
|
||||
|
@@ -3,10 +3,10 @@
|
||||
> Service trait and combinators for representing asynchronous request/response operations.
|
||||
|
||||
[](https://crates.io/crates/actix-service)
|
||||
[](https://docs.rs/actix-service/2.0.0)
|
||||
[](https://docs.rs/actix-service/2.0.1)
|
||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
||||

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

|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
|
@@ -77,7 +77,7 @@ use self::ready::{err, ok, ready, Ready};
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// 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
|
||||
///
|
||||
/// # Examples
|
||||
|
@@ -41,7 +41,7 @@ where
|
||||
///
|
||||
/// actix_service::forward_ready!(service);
|
||||
///
|
||||
/// fn call(&self, req: S::Request) -> Self::Future {
|
||||
/// fn call(&self, req: Req) -> Self::Future {
|
||||
/// TimeoutServiceResponse {
|
||||
/// fut: self.service.call(req),
|
||||
/// sleep: Sleep::new(clock::now() + self.timeout),
|
||||
|
@@ -43,7 +43,7 @@ uri = ["http"]
|
||||
actix-codec = "0.4.0-beta.1"
|
||||
actix-rt = { version = "2.2.0", default-features = false }
|
||||
actix-service = "2.0.0"
|
||||
actix-utils = "3.0.0-beta.4"
|
||||
actix-utils = "3.0.0"
|
||||
|
||||
derive_more = "0.99.5"
|
||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||
@@ -64,7 +64,7 @@ tokio-native-tls = { version = "0.3", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "2.2.0"
|
||||
actix-server = "2.0.0-beta.3"
|
||||
actix-server = "2.0.0-beta.5"
|
||||
bytes = "1"
|
||||
env_logger = "0.8"
|
||||
futures-util = { version = "0.3.7", default-features = false, features = ["sink"] }
|
||||
|
@@ -19,7 +19,7 @@ pub trait Address: Unpin + 'static {
|
||||
|
||||
impl Address for String {
|
||||
fn hostname(&self) -> &str {
|
||||
&self
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -164,7 +164,7 @@ impl<T: Address> Service<Connect<T>> for Resolver {
|
||||
}
|
||||
|
||||
Self::Custom(resolver) => {
|
||||
let resolver = Rc::clone(&resolver);
|
||||
let resolver = Rc::clone(resolver);
|
||||
ResolverFuture::LookupCustom(Box::pin(async move {
|
||||
let addrs = resolver
|
||||
.lookup(req.hostname(), req.port())
|
||||
|
@@ -5,6 +5,7 @@
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
#[allow(unused_extern_crates)]
|
||||
extern crate tls_openssl as openssl;
|
||||
|
||||
#[cfg(feature = "accept")]
|
||||
|
@@ -17,7 +17,7 @@ path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
actix-service = "2.0.0"
|
||||
actix-utils = "3.0.0-beta.4"
|
||||
actix-utils = "3.0.0"
|
||||
|
||||
tracing = "0.1"
|
||||
tracing-futures = "0.2"
|
||||
|
@@ -3,6 +3,10 @@
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 3.0.0 - 2021-04-16
|
||||
* No significant changes from `3.0.0-beta.4`.
|
||||
|
||||
|
||||
## 3.0.0-beta.4 - 2021-04-01
|
||||
* Add `future::Either` type. [#305]
|
||||
|
||||
|
@@ -1,14 +1,14 @@
|
||||
[package]
|
||||
name = "actix-utils"
|
||||
version = "3.0.0-beta.4"
|
||||
version = "3.0.0"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
]
|
||||
description = "Utilities for the Actix ecosystem"
|
||||
description = "Various utilities used in the Actix ecosystem"
|
||||
keywords = ["network", "framework", "async", "futures"]
|
||||
repository = "https://github.com/actix/actix-net.git"
|
||||
categories = ["network-programming", "asynchronous"]
|
||||
repository = "https://github.com/actix/actix-net"
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//! Various utilities for the Actix ecosystem.
|
||||
//! Various utilities used in the Actix ecosystem.
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(missing_docs)]
|
||||
|
Reference in New Issue
Block a user