mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-04 09:56:22 +02:00
Compare commits
29 Commits
web-v4.6.0
...
web-v4.7.0
Author | SHA1 | Date | |
---|---|---|---|
a5c78483f9 | |||
12a0521ef8 | |||
b4faf8820c | |||
d6f885127d | |||
ebc43dcf1b | |||
7c4c26d2df | |||
3db7891303 | |||
c366649516 | |||
534cfe1fda | |||
cff958e518 | |||
b9305ff59d | |||
5221c1b194 | |||
4493aa35d0 | |||
8b4d23a69a | |||
8fdf358954 | |||
b2d0196f34 | |||
85655f731d | |||
ebd8bb266d | |||
5c18569b78 | |||
dd84bcb609 | |||
3ce97effa2 | |||
26efa64278 | |||
cc06fd6a5e | |||
1b214bc5f5 | |||
d4bcdf28f2 | |||
4f7b334d80 | |||
fdff3775a8 | |||
b342b8fc82 | |||
804a344565 |
@ -6,5 +6,5 @@ lint-all = "clippy --workspace --all-features --all-targets -- -Dclippy::todo"
|
|||||||
ci-check-min = "hack --workspace check --no-default-features"
|
ci-check-min = "hack --workspace check --no-default-features"
|
||||||
ci-check-default = "hack --workspace check"
|
ci-check-default = "hack --workspace check"
|
||||||
ci-check-default-tests = "check --workspace --tests"
|
ci-check-default-tests = "check --workspace --tests"
|
||||||
ci-check-all-feature-powerset="hack --workspace --feature-powerset --skip=__compress,experimental-io-uring check"
|
ci-check-all-feature-powerset="hack --workspace --feature-powerset --depth=4 --skip=__compress,experimental-io-uring check"
|
||||||
ci-check-all-feature-powerset-linux="hack --workspace --feature-powerset --skip=__compress check"
|
ci-check-all-feature-powerset-linux="hack --workspace --feature-powerset --depth=4 --skip=__compress check"
|
||||||
|
4
.github/workflows/ci-post-merge.yml
vendored
4
.github/workflows/ci-post-merge.yml
vendored
@ -49,7 +49,7 @@ jobs:
|
|||||||
toolchain: ${{ matrix.version.version }}
|
toolchain: ${{ matrix.version.version }}
|
||||||
|
|
||||||
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
|
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
|
||||||
uses: taiki-e/install-action@v2.33.22
|
uses: taiki-e/install-action@v2.34.0
|
||||||
with:
|
with:
|
||||||
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
|
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ jobs:
|
|||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||||
|
|
||||||
- name: Install cargo-hack
|
- name: Install cargo-hack
|
||||||
uses: taiki-e/install-action@v2.33.22
|
uses: taiki-e/install-action@v2.34.0
|
||||||
with:
|
with:
|
||||||
tool: cargo-hack
|
tool: cargo-hack
|
||||||
|
|
||||||
|
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
@ -18,7 +18,7 @@ concurrency:
|
|||||||
jobs:
|
jobs:
|
||||||
read_msrv:
|
read_msrv:
|
||||||
name: Read MSRV
|
name: Read MSRV
|
||||||
uses: actions-rust-lang/msrv/.github/workflows/msrv.yml@main
|
uses: actions-rust-lang/msrv/.github/workflows/msrv.yml@v0.1.0
|
||||||
|
|
||||||
build_and_test:
|
build_and_test:
|
||||||
needs: read_msrv
|
needs: read_msrv
|
||||||
@ -54,13 +54,17 @@ jobs:
|
|||||||
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' >> $GITHUB_ENV
|
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' >> $GITHUB_ENV
|
||||||
echo "RUSTFLAGS=-C target-feature=+crt-static" >> $GITHUB_ENV
|
echo "RUSTFLAGS=-C target-feature=+crt-static" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Setup mold linker
|
||||||
|
if: matrix.target.os == 'ubuntu-latest'
|
||||||
|
uses: rui314/setup-mold@v1
|
||||||
|
|
||||||
- name: Install Rust (${{ matrix.version.name }})
|
- name: Install Rust (${{ matrix.version.name }})
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||||
with:
|
with:
|
||||||
toolchain: ${{ matrix.version.version }}
|
toolchain: ${{ matrix.version.version }}
|
||||||
|
|
||||||
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
|
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
|
||||||
uses: taiki-e/install-action@v2.33.22
|
uses: taiki-e/install-action@v2.34.0
|
||||||
with:
|
with:
|
||||||
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
|
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
|
||||||
|
|
||||||
@ -109,7 +113,7 @@ jobs:
|
|||||||
toolchain: nightly
|
toolchain: nightly
|
||||||
|
|
||||||
- name: Install just
|
- name: Install just
|
||||||
uses: taiki-e/install-action@v2.33.22
|
uses: taiki-e/install-action@v2.34.0
|
||||||
with:
|
with:
|
||||||
tool: just
|
tool: just
|
||||||
|
|
||||||
|
8
.github/workflows/coverage.yml
vendored
8
.github/workflows/coverage.yml
vendored
@ -22,16 +22,16 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
components: llvm-tools-preview
|
components: llvm-tools-preview
|
||||||
|
|
||||||
- name: Install cargo-llvm-cov
|
- name: Install just,cargo-llvm-cov
|
||||||
uses: taiki-e/install-action@v2.33.22
|
uses: taiki-e/install-action@v2.34.0
|
||||||
with:
|
with:
|
||||||
tool: cargo-llvm-cov
|
tool: just,cargo-llvm-cov
|
||||||
|
|
||||||
- name: Generate code coverage
|
- name: Generate code coverage
|
||||||
run: cargo llvm-cov --workspace --all-features --codecov --output-path codecov.json
|
run: cargo llvm-cov --workspace --all-features --codecov --output-path codecov.json
|
||||||
|
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
uses: codecov/codecov-action@v4.3.1
|
uses: codecov/codecov-action@v4.4.1
|
||||||
with:
|
with:
|
||||||
files: codecov.json
|
files: codecov.json
|
||||||
fail_ci_if_error: true
|
fail_ci_if_error: true
|
||||||
|
6
.github/workflows/lint.yml
vendored
6
.github/workflows/lint.yml
vendored
@ -79,15 +79,15 @@ jobs:
|
|||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||||
with:
|
with:
|
||||||
toolchain: nightly-2024-04-26
|
toolchain: nightly-2024-06-07
|
||||||
|
|
||||||
- name: Install cargo-public-api
|
- name: Install cargo-public-api
|
||||||
uses: taiki-e/install-action@v2.33.22
|
uses: taiki-e/install-action@v2.34.0
|
||||||
with:
|
with:
|
||||||
tool: cargo-public-api
|
tool: cargo-public-api
|
||||||
|
|
||||||
- name: Generate API diff
|
- name: Generate API diff
|
||||||
run: |
|
run: |
|
||||||
for f in $(find -mindepth 2 -maxdepth 2 -name Cargo.toml); do
|
for f in $(find -mindepth 2 -maxdepth 2 -name Cargo.toml); do
|
||||||
cargo public-api --manifest-path "$f" diff ${{ github.event.pull_request.base.sha }}..${{ github.sha }}
|
cargo public-api --manifest-path "$f" --simplified diff ${{ github.event.pull_request.base.sha }}..${{ github.sha }}
|
||||||
done
|
done
|
||||||
|
41
.github/workflows/upload-doc.yml
vendored
41
.github/workflows/upload-doc.yml
vendored
@ -1,41 +0,0 @@
|
|||||||
name: Upload Documentation
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [master]
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install Rust
|
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
|
||||||
with:
|
|
||||||
toolchain: nightly
|
|
||||||
|
|
||||||
- name: Build Docs
|
|
||||||
run: cargo +nightly doc --no-deps --workspace --all-features
|
|
||||||
env:
|
|
||||||
RUSTDOCFLAGS: --cfg=docsrs
|
|
||||||
|
|
||||||
- name: Tweak HTML
|
|
||||||
run: echo '<meta http-equiv="refresh" content="0;url=actix_web/index.html">' > target/doc/index.html
|
|
||||||
|
|
||||||
- name: Deploy to GitHub Pages
|
|
||||||
uses: JamesIves/github-pages-deploy-action@v4.6.0
|
|
||||||
with:
|
|
||||||
folder: target/doc
|
|
||||||
single-commit: true
|
|
@ -178,14 +178,14 @@ impl Parser {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if payload_len < 126 {
|
if payload_len < 126 {
|
||||||
dst.reserve(p_len + 2 + if mask { 4 } else { 0 });
|
dst.reserve(p_len + 2);
|
||||||
dst.put_slice(&[one, two | payload_len as u8]);
|
dst.put_slice(&[one, two | payload_len as u8]);
|
||||||
} else if payload_len <= 65_535 {
|
} else if payload_len <= 65_535 {
|
||||||
dst.reserve(p_len + 4 + if mask { 4 } else { 0 });
|
dst.reserve(p_len + 4);
|
||||||
dst.put_slice(&[one, two | 126]);
|
dst.put_slice(&[one, two | 126]);
|
||||||
dst.put_u16(payload_len as u16);
|
dst.put_u16(payload_len as u16);
|
||||||
} else {
|
} else {
|
||||||
dst.reserve(p_len + 10 + if mask { 4 } else { 0 });
|
dst.reserve(p_len + 10);
|
||||||
dst.put_slice(&[one, two | 127]);
|
dst.put_slice(&[one, two | 127]);
|
||||||
dst.put_u64(payload_len as u64);
|
dst.put_u64(payload_len as u64);
|
||||||
};
|
};
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
## 0.6.2
|
||||||
|
|
||||||
- Add testing utilities under new module `test`.
|
- Add testing utilities under new module `test`.
|
||||||
- Minimum supported Rust version (MSRV) is now 1.72.
|
- Minimum supported Rust version (MSRV) is now 1.72.
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-multipart"
|
name = "actix-multipart"
|
||||||
version = "0.6.1"
|
version = "0.6.2"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"Jacob Halsey <jacob@jhalsey.com>",
|
"Jacob Halsey <jacob@jhalsey.com>",
|
||||||
|
@ -5,17 +5,16 @@
|
|||||||
<!-- prettier-ignore-start -->
|
<!-- prettier-ignore-start -->
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-multipart)
|
[](https://crates.io/crates/actix-multipart)
|
||||||
[](https://docs.rs/actix-multipart/0.6.1)
|
[](https://docs.rs/actix-multipart/0.6.2)
|
||||||

|

|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-multipart/0.6.1)
|
[](https://deps.rs/crate/actix-multipart/0.6.2)
|
||||||
[](https://crates.io/crates/actix-multipart)
|
[](https://crates.io/crates/actix-multipart)
|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
<!-- prettier-ignore-end -->
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
Dependencies:
|
Dependencies:
|
||||||
@ -65,6 +64,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
```
|
```
|
||||||
|
|
||||||
Curl request :
|
Curl request :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -v --request POST \
|
curl -v --request POST \
|
||||||
--url http://localhost:8080/videos \
|
--url http://localhost:8080/videos \
|
||||||
@ -72,7 +72,6 @@ curl -v --request POST \
|
|||||||
-F file=@./Cargo.lock
|
-F file=@./Cargo.lock
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
https://github.com/actix/examples/tree/master/forms/multipart
|
https://github.com/actix/examples/tree/master/forms/multipart
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
## 0.5.3
|
||||||
|
|
||||||
- Add `unicode` crate feature (on-by-default) to switch between `regex` and `regex-lite` as a trade-off between full unicode support and binary size.
|
- Add `unicode` crate feature (on-by-default) to switch between `regex` and `regex-lite` as a trade-off between full unicode support and binary size.
|
||||||
- Minimum supported Rust version (MSRV) is now 1.72.
|
- Minimum supported Rust version (MSRV) is now 1.72.
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-router"
|
name = "actix-router"
|
||||||
version = "0.5.2"
|
version = "0.5.3"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"Ali MJ Al-Nasrawy <alimjalnasrawy@gmail.com>",
|
"Ali MJ Al-Nasrawy <alimjalnasrawy@gmail.com>",
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
<!-- prettier-ignore-start -->
|
<!-- prettier-ignore-start -->
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-router)
|
[](https://crates.io/crates/actix-router)
|
||||||
[](https://docs.rs/actix-router/0.5.2)
|
[](https://docs.rs/actix-router/0.5.3)
|
||||||

|

|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-router/0.5.2)
|
[](https://deps.rs/crate/actix-router/0.5.3)
|
||||||
[](https://crates.io/crates/actix-router)
|
[](https://crates.io/crates/actix-router)
|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
|
@ -2,9 +2,12 @@
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
## 0.1.4
|
||||||
|
|
||||||
- Add `TestServerConfig::rustls_0_23()` method for Rustls v0.23 support behind new `rustls-0_23` crate feature.
|
- Add `TestServerConfig::rustls_0_23()` method for Rustls v0.23 support behind new `rustls-0_23` crate feature.
|
||||||
- Minimum supported Rust version (MSRV) is now 1.72.
|
- Add `TestServerConfig::disable_redirects()` method.
|
||||||
- Various types from `awc`, such as `ClientRequest` and `ClientResponse`, are now re-exported.
|
- Various types from `awc`, such as `ClientRequest` and `ClientResponse`, are now re-exported.
|
||||||
|
- Minimum supported Rust version (MSRV) is now 1.72.
|
||||||
|
|
||||||
## 0.1.3
|
## 0.1.3
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-test"
|
name = "actix-test"
|
||||||
version = "0.1.3"
|
version = "0.1.4"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"Rob Ede <robjtede@icloud.com>",
|
"Rob Ede <robjtede@icloud.com>",
|
||||||
|
@ -149,6 +149,8 @@ where
|
|||||||
StreamType::Rustls023(_) => true,
|
StreamType::Rustls023(_) => true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let client_cfg = cfg.clone();
|
||||||
|
|
||||||
// run server in separate orphaned thread
|
// run server in separate orphaned thread
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
rt::System::new().block_on(async move {
|
rt::System::new().block_on(async move {
|
||||||
@ -460,7 +462,13 @@ where
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Client::builder().connector(connector).finish()
|
let mut client_builder = Client::builder().connector(connector);
|
||||||
|
|
||||||
|
if client_cfg.disable_redirects {
|
||||||
|
client_builder = client_builder.disable_redirects();
|
||||||
|
}
|
||||||
|
|
||||||
|
client_builder.finish()
|
||||||
};
|
};
|
||||||
|
|
||||||
TestServer {
|
TestServer {
|
||||||
@ -480,6 +488,7 @@ enum HttpVer {
|
|||||||
Both,
|
Both,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
enum StreamType {
|
enum StreamType {
|
||||||
Tcp,
|
Tcp,
|
||||||
@ -507,6 +516,7 @@ pub struct TestServerConfig {
|
|||||||
client_request_timeout: Duration,
|
client_request_timeout: Duration,
|
||||||
port: u16,
|
port: u16,
|
||||||
workers: usize,
|
workers: usize,
|
||||||
|
disable_redirects: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TestServerConfig {
|
impl Default for TestServerConfig {
|
||||||
@ -524,6 +534,7 @@ impl TestServerConfig {
|
|||||||
client_request_timeout: Duration::from_secs(5),
|
client_request_timeout: Duration::from_secs(5),
|
||||||
port: 0,
|
port: 0,
|
||||||
workers: 1,
|
workers: 1,
|
||||||
|
disable_redirects: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -611,6 +622,15 @@ impl TestServerConfig {
|
|||||||
self.workers = workers;
|
self.workers = workers;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Instruct the client to not follow redirects.
|
||||||
|
///
|
||||||
|
/// By default, the client will follow up to 10 consecutive redirects
|
||||||
|
/// before giving up.
|
||||||
|
pub fn disable_redirects(mut self) -> Self {
|
||||||
|
self.disable_redirects = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A basic HTTP server controller that simplifies the process of writing integration tests for
|
/// A basic HTTP server controller that simplifies the process of writing integration tests for
|
||||||
|
@ -2,6 +2,11 @@
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
## 4.3.0
|
||||||
|
|
||||||
|
- Add `#[scope]` macro.
|
||||||
|
- Add `compat-routing-macros-force-pub` crate feature which, on-by-default, which when disabled causes handlers to inherit their attached function's visibility.
|
||||||
|
- Prevent inclusion of default `actix-router` features.
|
||||||
- Minimum supported Rust version (MSRV) is now 1.72.
|
- Minimum supported Rust version (MSRV) is now 1.72.
|
||||||
|
|
||||||
## 4.2.2
|
## 4.2.2
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-web-codegen"
|
name = "actix-web-codegen"
|
||||||
version = "4.2.2"
|
version = "4.3.0"
|
||||||
description = "Routing and runtime macros for Actix Web"
|
description = "Routing and runtime macros for Actix Web"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
@ -15,8 +15,12 @@ rust-version.workspace = true
|
|||||||
[lib]
|
[lib]
|
||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["compat-routing-macros-force-pub"]
|
||||||
|
compat-routing-macros-force-pub = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-router = "0.5"
|
actix-router = { version = "0.5", default-features = false }
|
||||||
proc-macro2 = "1"
|
proc-macro2 = "1"
|
||||||
quote = "1"
|
quote = "1"
|
||||||
syn = { version = "2", features = ["full", "extra-traits"] }
|
syn = { version = "2", features = ["full", "extra-traits"] }
|
||||||
|
@ -5,11 +5,11 @@
|
|||||||
<!-- prettier-ignore-start -->
|
<!-- prettier-ignore-start -->
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-web-codegen)
|
[](https://crates.io/crates/actix-web-codegen)
|
||||||
[](https://docs.rs/actix-web-codegen/4.2.2)
|
[](https://docs.rs/actix-web-codegen/4.3.0)
|
||||||

|

|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-web-codegen/4.2.2)
|
[](https://deps.rs/crate/actix-web-codegen/4.3.0)
|
||||||
[](https://crates.io/crates/actix-web-codegen)
|
[](https://crates.io/crates/actix-web-codegen)
|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
|
@ -83,6 +83,7 @@ use proc_macro::TokenStream;
|
|||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
|
||||||
mod route;
|
mod route;
|
||||||
|
mod scope;
|
||||||
|
|
||||||
/// Creates resource handler, allowing multiple HTTP method guards.
|
/// Creates resource handler, allowing multiple HTTP method guards.
|
||||||
///
|
///
|
||||||
@ -197,6 +198,43 @@ method_macro!(Options, options);
|
|||||||
method_macro!(Trace, trace);
|
method_macro!(Trace, trace);
|
||||||
method_macro!(Patch, patch);
|
method_macro!(Patch, patch);
|
||||||
|
|
||||||
|
/// Prepends a path prefix to all handlers using routing macros inside the attached module.
|
||||||
|
///
|
||||||
|
/// # Syntax
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use actix_web_codegen::scope;
|
||||||
|
/// #[scope("/prefix")]
|
||||||
|
/// mod api {
|
||||||
|
/// // ...
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// - `"/prefix"` - Raw literal string to be prefixed onto contained handlers' paths.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use actix_web_codegen::{scope, get};
|
||||||
|
/// # use actix_web::Responder;
|
||||||
|
/// #[scope("/api")]
|
||||||
|
/// mod api {
|
||||||
|
/// # use super::*;
|
||||||
|
/// #[get("/hello")]
|
||||||
|
/// pub async fn hello() -> impl Responder {
|
||||||
|
/// // this has path /api/hello
|
||||||
|
/// "Hello, world!"
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// # fn main() {}
|
||||||
|
/// ```
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn scope(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
|
scope::with_scope(args, input)
|
||||||
|
}
|
||||||
|
|
||||||
/// Marks async main function as the Actix Web system entry-point.
|
/// Marks async main function as the Actix Web system entry-point.
|
||||||
///
|
///
|
||||||
/// Note that Actix Web also works under `#[tokio::main]` since version 4.0. However, this macro is
|
/// Note that Actix Web also works under `#[tokio::main]` since version 4.0. However, this macro is
|
||||||
@ -240,3 +278,15 @@ pub fn test(_: TokenStream, item: TokenStream) -> TokenStream {
|
|||||||
output.extend(item);
|
output.extend(item);
|
||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts the error to a token stream and appends it to the original input.
|
||||||
|
///
|
||||||
|
/// Returning the original input in addition to the error is good for IDEs which can gracefully
|
||||||
|
/// recover and show more precise errors within the macro body.
|
||||||
|
///
|
||||||
|
/// See <https://github.com/rust-analyzer/rust-analyzer/issues/10468> for more info.
|
||||||
|
fn input_and_compile_error(mut item: TokenStream, err: syn::Error) -> TokenStream {
|
||||||
|
let compile_err = TokenStream::from(err.to_compile_error());
|
||||||
|
item.extend(compile_err);
|
||||||
|
item
|
||||||
|
}
|
||||||
|
@ -6,10 +6,12 @@ use proc_macro2::{Span, TokenStream as TokenStream2};
|
|||||||
use quote::{quote, ToTokens, TokenStreamExt};
|
use quote::{quote, ToTokens, TokenStreamExt};
|
||||||
use syn::{punctuated::Punctuated, Ident, LitStr, Path, Token};
|
use syn::{punctuated::Punctuated, Ident, LitStr, Path, Token};
|
||||||
|
|
||||||
|
use crate::input_and_compile_error;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RouteArgs {
|
pub struct RouteArgs {
|
||||||
path: syn::LitStr,
|
pub(crate) path: syn::LitStr,
|
||||||
options: Punctuated<syn::MetaNameValue, Token![,]>,
|
pub(crate) options: Punctuated<syn::MetaNameValue, Token![,]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl syn::parse::Parse for RouteArgs {
|
impl syn::parse::Parse for RouteArgs {
|
||||||
@ -78,7 +80,7 @@ macro_rules! standard_method_type {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_path(method: &Path) -> Result<Self, ()> {
|
pub(crate) fn from_path(method: &Path) -> Result<Self, ()> {
|
||||||
match () {
|
match () {
|
||||||
$(_ if method.is_ident(stringify!($lower)) => Ok(Self::$variant),)+
|
$(_ if method.is_ident(stringify!($lower)) => Ok(Self::$variant),)+
|
||||||
_ => Err(()),
|
_ => Err(()),
|
||||||
@ -411,6 +413,13 @@ impl ToTokens for Route {
|
|||||||
doc_attributes,
|
doc_attributes,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
|
#[allow(unused_variables)] // used when force-pub feature is disabled
|
||||||
|
let vis = &ast.vis;
|
||||||
|
|
||||||
|
// TODO(breaking): remove this force-pub forwards-compatibility feature
|
||||||
|
#[cfg(feature = "compat-routing-macros-force-pub")]
|
||||||
|
let vis = syn::Visibility::Public(<Token![pub]>::default());
|
||||||
|
|
||||||
let registrations: TokenStream2 = args
|
let registrations: TokenStream2 = args
|
||||||
.iter()
|
.iter()
|
||||||
.map(|args| {
|
.map(|args| {
|
||||||
@ -458,7 +467,7 @@ impl ToTokens for Route {
|
|||||||
let stream = quote! {
|
let stream = quote! {
|
||||||
#(#doc_attributes)*
|
#(#doc_attributes)*
|
||||||
#[allow(non_camel_case_types, missing_docs)]
|
#[allow(non_camel_case_types, missing_docs)]
|
||||||
pub struct #name;
|
#vis struct #name;
|
||||||
|
|
||||||
impl ::actix_web::dev::HttpServiceFactory for #name {
|
impl ::actix_web::dev::HttpServiceFactory for #name {
|
||||||
fn register(self, __config: &mut actix_web::dev::AppService) {
|
fn register(self, __config: &mut actix_web::dev::AppService) {
|
||||||
@ -542,15 +551,3 @@ pub(crate) fn with_methods(input: TokenStream) -> TokenStream {
|
|||||||
Err(err) => input_and_compile_error(input, err),
|
Err(err) => input_and_compile_error(input, err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts the error to a token stream and appends it to the original input.
|
|
||||||
///
|
|
||||||
/// Returning the original input in addition to the error is good for IDEs which can gracefully
|
|
||||||
/// recover and show more precise errors within the macro body.
|
|
||||||
///
|
|
||||||
/// See <https://github.com/rust-analyzer/rust-analyzer/issues/10468> for more info.
|
|
||||||
fn input_and_compile_error(mut item: TokenStream, err: syn::Error) -> TokenStream {
|
|
||||||
let compile_err = TokenStream::from(err.to_compile_error());
|
|
||||||
item.extend(compile_err);
|
|
||||||
item
|
|
||||||
}
|
|
||||||
|
103
actix-web-codegen/src/scope.rs
Normal file
103
actix-web-codegen/src/scope.rs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
use proc_macro::TokenStream;
|
||||||
|
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||||
|
use quote::{quote, ToTokens as _};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
input_and_compile_error,
|
||||||
|
route::{MethodType, RouteArgs},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn with_scope(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
|
match with_scope_inner(args, input.clone()) {
|
||||||
|
Ok(stream) => stream,
|
||||||
|
Err(err) => input_and_compile_error(input, err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_scope_inner(args: TokenStream, input: TokenStream) -> syn::Result<TokenStream> {
|
||||||
|
if args.is_empty() {
|
||||||
|
return Err(syn::Error::new(
|
||||||
|
Span::call_site(),
|
||||||
|
"missing arguments for scope macro, expected: #[scope(\"/prefix\")]",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let scope_prefix = syn::parse::<syn::LitStr>(args.clone()).map_err(|err| {
|
||||||
|
syn::Error::new(
|
||||||
|
err.span(),
|
||||||
|
"argument to scope macro is not a string literal, expected: #[scope(\"/prefix\")]",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let scope_prefix_value = scope_prefix.value();
|
||||||
|
|
||||||
|
if scope_prefix_value.ends_with('/') {
|
||||||
|
// trailing slashes cause non-obvious problems
|
||||||
|
// it's better to point them out to developers rather than
|
||||||
|
|
||||||
|
return Err(syn::Error::new(
|
||||||
|
scope_prefix.span(),
|
||||||
|
"scopes should not have trailing slashes; see https://docs.rs/actix-web/4/actix_web/struct.Scope.html#avoid-trailing-slashes",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut module = syn::parse::<syn::ItemMod>(input).map_err(|err| {
|
||||||
|
syn::Error::new(err.span(), "#[scope] macro must be attached to a module")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// modify any routing macros (method or route[s]) attached to
|
||||||
|
// functions by prefixing them with this scope macro's argument
|
||||||
|
if let Some((_, items)) = &mut module.content {
|
||||||
|
for item in items {
|
||||||
|
if let syn::Item::Fn(fun) = item {
|
||||||
|
fun.attrs = fun
|
||||||
|
.attrs
|
||||||
|
.iter()
|
||||||
|
.map(|attr| modify_attribute_with_scope(attr, &scope_prefix_value))
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(module.to_token_stream().into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the attribute is a method type and has a route path, then modifies it.
|
||||||
|
fn modify_attribute_with_scope(attr: &syn::Attribute, scope_path: &str) -> syn::Attribute {
|
||||||
|
match (attr.parse_args::<RouteArgs>(), attr.clone().meta) {
|
||||||
|
(Ok(route_args), syn::Meta::List(meta_list)) if has_allowed_methods_in_scope(attr) => {
|
||||||
|
let modified_path = format!("{}{}", scope_path, route_args.path.value());
|
||||||
|
|
||||||
|
let options_tokens: Vec<TokenStream2> = route_args
|
||||||
|
.options
|
||||||
|
.iter()
|
||||||
|
.map(|option| {
|
||||||
|
quote! { ,#option }
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let combined_options_tokens: TokenStream2 =
|
||||||
|
options_tokens
|
||||||
|
.into_iter()
|
||||||
|
.fold(TokenStream2::new(), |mut acc, ts| {
|
||||||
|
acc.extend(std::iter::once(ts));
|
||||||
|
acc
|
||||||
|
});
|
||||||
|
|
||||||
|
syn::Attribute {
|
||||||
|
meta: syn::Meta::List(syn::MetaList {
|
||||||
|
tokens: quote! { #modified_path #combined_options_tokens },
|
||||||
|
..meta_list.clone()
|
||||||
|
}),
|
||||||
|
..attr.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => attr.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_allowed_methods_in_scope(attr: &syn::Attribute) -> bool {
|
||||||
|
MethodType::from_path(attr.path()).is_ok()
|
||||||
|
|| attr.path().is_ident("route")
|
||||||
|
|| attr.path().is_ident("ROUTE")
|
||||||
|
}
|
200
actix-web-codegen/tests/scopes.rs
Normal file
200
actix-web-codegen/tests/scopes.rs
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
use actix_web::{guard::GuardContext, http, http::header, web, App, HttpResponse, Responder};
|
||||||
|
use actix_web_codegen::{delete, get, post, route, routes, scope};
|
||||||
|
|
||||||
|
pub fn image_guard(ctx: &GuardContext) -> bool {
|
||||||
|
ctx.header::<header::Accept>()
|
||||||
|
.map(|h| h.preference() == "image/*")
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[scope("/test")]
|
||||||
|
mod scope_module {
|
||||||
|
// ensure that imports can be brought into the scope
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[get("/test/guard", guard = "image_guard")]
|
||||||
|
pub async fn guard() -> impl Responder {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/test")]
|
||||||
|
pub async fn test() -> impl Responder {
|
||||||
|
HttpResponse::Ok().finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/twice-test/{value}")]
|
||||||
|
pub async fn twice(value: web::Path<String>) -> impl actix_web::Responder {
|
||||||
|
let int_value: i32 = value.parse().unwrap_or(0);
|
||||||
|
let doubled = int_value * 2;
|
||||||
|
HttpResponse::Ok().body(format!("Twice value: {}", doubled))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/test")]
|
||||||
|
pub async fn post() -> impl Responder {
|
||||||
|
HttpResponse::Ok().body("post works")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[delete("/test")]
|
||||||
|
pub async fn delete() -> impl Responder {
|
||||||
|
"delete works"
|
||||||
|
}
|
||||||
|
|
||||||
|
#[route("/test", method = "PUT", method = "PATCH", method = "CUSTOM")]
|
||||||
|
pub async fn multiple_shared_path() -> impl Responder {
|
||||||
|
HttpResponse::Ok().finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[routes]
|
||||||
|
#[head("/test1")]
|
||||||
|
#[connect("/test2")]
|
||||||
|
#[options("/test3")]
|
||||||
|
#[trace("/test4")]
|
||||||
|
pub async fn multiple_separate_paths() -> impl Responder {
|
||||||
|
HttpResponse::Ok().finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
// test calling this from other mod scope with scope attribute...
|
||||||
|
pub fn mod_common(message: String) -> impl actix_web::Responder {
|
||||||
|
HttpResponse::Ok().body(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scope doc string to check in cargo expand.
|
||||||
|
#[scope("/v1")]
|
||||||
|
mod mod_scope_v1 {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Route doc string to check in cargo expand.
|
||||||
|
#[get("/test")]
|
||||||
|
pub async fn test() -> impl Responder {
|
||||||
|
scope_module::mod_common("version1 works".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[scope("/v2")]
|
||||||
|
mod mod_scope_v2 {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
// check to make sure non-function tokens in the scope block are preserved...
|
||||||
|
enum TestEnum {
|
||||||
|
Works,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/test")]
|
||||||
|
pub async fn test() -> impl Responder {
|
||||||
|
// make sure this type still exists...
|
||||||
|
let test_enum = TestEnum::Works;
|
||||||
|
|
||||||
|
match test_enum {
|
||||||
|
TestEnum::Works => scope_module::mod_common("version2 works".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn scope_get_async() {
|
||||||
|
let srv = actix_test::start(|| App::new().service(scope_module::test));
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::GET, srv.url("/test/test"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn scope_get_param_async() {
|
||||||
|
let srv = actix_test::start(|| App::new().service(scope_module::twice));
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::GET, srv.url("/test/twice-test/4"));
|
||||||
|
let mut response = request.send().await.unwrap();
|
||||||
|
let body = response.body().await.unwrap();
|
||||||
|
let body_str = String::from_utf8(body.to_vec()).unwrap();
|
||||||
|
assert_eq!(body_str, "Twice value: 8");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn scope_post_async() {
|
||||||
|
let srv = actix_test::start(|| App::new().service(scope_module::post));
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::POST, srv.url("/test/test"));
|
||||||
|
let mut response = request.send().await.unwrap();
|
||||||
|
let body = response.body().await.unwrap();
|
||||||
|
let body_str = String::from_utf8(body.to_vec()).unwrap();
|
||||||
|
assert_eq!(body_str, "post works");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn multiple_shared_path_async() {
|
||||||
|
let srv = actix_test::start(|| App::new().service(scope_module::multiple_shared_path));
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::PUT, srv.url("/test/test"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::PATCH, srv.url("/test/test"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn multiple_multi_path_async() {
|
||||||
|
let srv = actix_test::start(|| App::new().service(scope_module::multiple_separate_paths));
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::HEAD, srv.url("/test/test1"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::CONNECT, srv.url("/test/test2"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::OPTIONS, srv.url("/test/test3"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::TRACE, srv.url("/test/test4"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn scope_delete_async() {
|
||||||
|
let srv = actix_test::start(|| App::new().service(scope_module::delete));
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::DELETE, srv.url("/test/test"));
|
||||||
|
let mut response = request.send().await.unwrap();
|
||||||
|
let body = response.body().await.unwrap();
|
||||||
|
let body_str = String::from_utf8(body.to_vec()).unwrap();
|
||||||
|
assert_eq!(body_str, "delete works");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn scope_get_with_guard_async() {
|
||||||
|
let srv = actix_test::start(|| App::new().service(scope_module::guard));
|
||||||
|
|
||||||
|
let request = srv
|
||||||
|
.request(http::Method::GET, srv.url("/test/test/guard"))
|
||||||
|
.insert_header(("Accept", "image/*"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn scope_v1_v2_async() {
|
||||||
|
let srv = actix_test::start(|| {
|
||||||
|
App::new()
|
||||||
|
.service(mod_scope_v1::test)
|
||||||
|
.service(mod_scope_v2::test)
|
||||||
|
});
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::GET, srv.url("/v1/test"));
|
||||||
|
let mut response = request.send().await.unwrap();
|
||||||
|
let body = response.body().await.unwrap();
|
||||||
|
let body_str = String::from_utf8(body.to_vec()).unwrap();
|
||||||
|
assert_eq!(body_str, "version1 works");
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::GET, srv.url("/v2/test"));
|
||||||
|
let mut response = request.send().await.unwrap();
|
||||||
|
let body = response.body().await.unwrap();
|
||||||
|
let body_str = String::from_utf8(body.to_vec()).unwrap();
|
||||||
|
assert_eq!(body_str, "version2 works");
|
||||||
|
}
|
@ -18,6 +18,11 @@ fn compile_macros() {
|
|||||||
t.compile_fail("tests/trybuild/routes-missing-method-fail.rs");
|
t.compile_fail("tests/trybuild/routes-missing-method-fail.rs");
|
||||||
t.compile_fail("tests/trybuild/routes-missing-args-fail.rs");
|
t.compile_fail("tests/trybuild/routes-missing-args-fail.rs");
|
||||||
|
|
||||||
|
t.compile_fail("tests/trybuild/scope-on-handler.rs");
|
||||||
|
t.compile_fail("tests/trybuild/scope-missing-args.rs");
|
||||||
|
t.compile_fail("tests/trybuild/scope-invalid-args.rs");
|
||||||
|
t.compile_fail("tests/trybuild/scope-trailing-slash.rs");
|
||||||
|
|
||||||
t.pass("tests/trybuild/docstring-ok.rs");
|
t.pass("tests/trybuild/docstring-ok.rs");
|
||||||
|
|
||||||
t.pass("tests/trybuild/test-runtime.rs");
|
t.pass("tests/trybuild/test-runtime.rs");
|
||||||
|
@ -20,10 +20,7 @@ error: custom attribute panicked
|
|||||||
13 | #[get("/{}")]
|
13 | #[get("/{}")]
|
||||||
| ^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^
|
||||||
|
|
|
|
||||||
= help: message: Wrong path pattern: "/{}" regex parse error:
|
= help: message: Wrong path pattern: "/{}" empty capture group names are not allowed
|
||||||
((?s-m)^/(?P<>[^/]+))$
|
|
||||||
^
|
|
||||||
error: empty capture group name
|
|
||||||
|
|
||||||
error: custom attribute panicked
|
error: custom attribute panicked
|
||||||
--> $DIR/route-malformed-path-fail.rs:23:1
|
--> $DIR/route-malformed-path-fail.rs:23:1
|
||||||
|
14
actix-web-codegen/tests/trybuild/scope-invalid-args.rs
Normal file
14
actix-web-codegen/tests/trybuild/scope-invalid-args.rs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
use actix_web_codegen::scope;
|
||||||
|
|
||||||
|
const PATH: &str = "/api";
|
||||||
|
|
||||||
|
#[scope(PATH)]
|
||||||
|
mod api_const {}
|
||||||
|
|
||||||
|
#[scope(true)]
|
||||||
|
mod api_bool {}
|
||||||
|
|
||||||
|
#[scope(123)]
|
||||||
|
mod api_num {}
|
||||||
|
|
||||||
|
fn main() {}
|
17
actix-web-codegen/tests/trybuild/scope-invalid-args.stderr
Normal file
17
actix-web-codegen/tests/trybuild/scope-invalid-args.stderr
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
error: argument to scope macro is not a string literal, expected: #[scope("/prefix")]
|
||||||
|
--> tests/trybuild/scope-invalid-args.rs:5:9
|
||||||
|
|
|
||||||
|
5 | #[scope(PATH)]
|
||||||
|
| ^^^^
|
||||||
|
|
||||||
|
error: argument to scope macro is not a string literal, expected: #[scope("/prefix")]
|
||||||
|
--> tests/trybuild/scope-invalid-args.rs:8:9
|
||||||
|
|
|
||||||
|
8 | #[scope(true)]
|
||||||
|
| ^^^^
|
||||||
|
|
||||||
|
error: argument to scope macro is not a string literal, expected: #[scope("/prefix")]
|
||||||
|
--> tests/trybuild/scope-invalid-args.rs:11:9
|
||||||
|
|
|
||||||
|
11 | #[scope(123)]
|
||||||
|
| ^^^
|
6
actix-web-codegen/tests/trybuild/scope-missing-args.rs
Normal file
6
actix-web-codegen/tests/trybuild/scope-missing-args.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
use actix_web_codegen::scope;
|
||||||
|
|
||||||
|
#[scope]
|
||||||
|
mod api {}
|
||||||
|
|
||||||
|
fn main() {}
|
@ -0,0 +1,7 @@
|
|||||||
|
error: missing arguments for scope macro, expected: #[scope("/prefix")]
|
||||||
|
--> tests/trybuild/scope-missing-args.rs:3:1
|
||||||
|
|
|
||||||
|
3 | #[scope]
|
||||||
|
| ^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: this error originates in the attribute macro `scope` (in Nightly builds, run with -Z macro-backtrace for more info)
|
8
actix-web-codegen/tests/trybuild/scope-on-handler.rs
Normal file
8
actix-web-codegen/tests/trybuild/scope-on-handler.rs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
use actix_web_codegen::scope;
|
||||||
|
|
||||||
|
#[scope("/api")]
|
||||||
|
async fn index() -> &'static str {
|
||||||
|
"Hello World!"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
5
actix-web-codegen/tests/trybuild/scope-on-handler.stderr
Normal file
5
actix-web-codegen/tests/trybuild/scope-on-handler.stderr
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
error: #[scope] macro must be attached to a module
|
||||||
|
--> tests/trybuild/scope-on-handler.rs:4:1
|
||||||
|
|
|
||||||
|
4 | async fn index() -> &'static str {
|
||||||
|
| ^^^^^
|
6
actix-web-codegen/tests/trybuild/scope-trailing-slash.rs
Normal file
6
actix-web-codegen/tests/trybuild/scope-trailing-slash.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
use actix_web_codegen::scope;
|
||||||
|
|
||||||
|
#[scope("/api/")]
|
||||||
|
mod api {}
|
||||||
|
|
||||||
|
fn main() {}
|
@ -0,0 +1,5 @@
|
|||||||
|
error: scopes should not have trailing slashes; see https://docs.rs/actix-web/4/actix_web/struct.Scope.html#avoid-trailing-slashes
|
||||||
|
--> tests/trybuild/scope-trailing-slash.rs:3:9
|
||||||
|
|
|
||||||
|
3 | #[scope("/api/")]
|
||||||
|
| ^^^^^^^
|
@ -2,6 +2,17 @@
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
## 4.7.0
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add `middleware::Identity` type.
|
||||||
|
- Add `CustomizeResponder::add_cookie()` method.
|
||||||
|
- Add `guard::GuardContext::app_data()` method.
|
||||||
|
- Add `compat-routing-macros-force-pub` crate feature which (on-by-default) which, when disabled, causes handlers to inherit their attached function's visibility.
|
||||||
|
- Add `compat` crate feature group (on-by-default) which, when disabled, helps with transitioning to some planned v5.0 breaking changes, starting only with `compat-routing-macros-force-pub`.
|
||||||
|
- Implement `From<Box<dyn ResponseError>>` for `Error`.
|
||||||
|
|
||||||
## 4.6.0
|
## 4.6.0
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-web"
|
name = "actix-web"
|
||||||
version = "4.6.0"
|
version = "4.7.0"
|
||||||
description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust"
|
description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
@ -35,13 +35,21 @@ features = [
|
|||||||
"secure-cookies",
|
"secure-cookies",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "actix_web"
|
name = "actix_web"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["macros", "compress-brotli", "compress-gzip", "compress-zstd", "cookies", "http2", "unicode"]
|
default = [
|
||||||
|
"macros",
|
||||||
|
"compress-brotli",
|
||||||
|
"compress-gzip",
|
||||||
|
"compress-zstd",
|
||||||
|
"cookies",
|
||||||
|
"http2",
|
||||||
|
"unicode",
|
||||||
|
"compat",
|
||||||
|
]
|
||||||
|
|
||||||
# Brotli algorithm content-encoding support
|
# Brotli algorithm content-encoding support
|
||||||
compress-brotli = ["actix-http/compress-brotli", "__compress"]
|
compress-brotli = ["actix-http/compress-brotli", "__compress"]
|
||||||
@ -51,14 +59,15 @@ compress-gzip = ["actix-http/compress-gzip", "__compress"]
|
|||||||
compress-zstd = ["actix-http/compress-zstd", "__compress"]
|
compress-zstd = ["actix-http/compress-zstd", "__compress"]
|
||||||
|
|
||||||
# Routing and runtime proc macros
|
# Routing and runtime proc macros
|
||||||
macros = ["actix-macros", "actix-web-codegen"]
|
macros = ["dep:actix-macros", "dep:actix-web-codegen"]
|
||||||
|
|
||||||
# Cookies support
|
# Cookies support
|
||||||
cookies = ["cookie"]
|
cookies = ["dep:cookie"]
|
||||||
|
|
||||||
# Secure & signed cookies
|
# Secure & signed cookies
|
||||||
secure-cookies = ["cookies", "cookie/secure"]
|
secure-cookies = ["cookies", "cookie/secure"]
|
||||||
|
|
||||||
|
# HTTP/2 support (including h2c).
|
||||||
http2 = ["actix-http/http2"]
|
http2 = ["actix-http/http2"]
|
||||||
|
|
||||||
# TLS via OpenSSL
|
# TLS via OpenSSL
|
||||||
@ -85,6 +94,14 @@ __compress = []
|
|||||||
# io-uring feature only available for Linux OSes.
|
# io-uring feature only available for Linux OSes.
|
||||||
experimental-io-uring = ["actix-server/io-uring"]
|
experimental-io-uring = ["actix-server/io-uring"]
|
||||||
|
|
||||||
|
# Feature group which, when disabled, helps migrate code to v5.0.
|
||||||
|
compat = [
|
||||||
|
"compat-routing-macros-force-pub",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Opt-out forwards-compatibility for handler visibility inheritance fix.
|
||||||
|
compat-routing-macros-force-pub = ["actix-web-codegen?/compat-routing-macros-force-pub"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-codec = "0.5"
|
actix-codec = "0.5"
|
||||||
actix-macros = { version = "0.2.3", optional = true }
|
actix-macros = { version = "0.2.3", optional = true }
|
||||||
@ -95,8 +112,8 @@ actix-utils = "3"
|
|||||||
actix-tls = { version = "3.4", default-features = false, optional = true }
|
actix-tls = { version = "3.4", default-features = false, optional = true }
|
||||||
|
|
||||||
actix-http = { version = "3.7", features = ["ws"] }
|
actix-http = { version = "3.7", features = ["ws"] }
|
||||||
actix-router = { version = "0.5", default-features = false, features = ["http"] }
|
actix-router = { version = "0.5.3", default-features = false, features = ["http"] }
|
||||||
actix-web-codegen = { version = "4.2", optional = true }
|
actix-web-codegen = { version = "4.3", optional = true, default-features = false }
|
||||||
|
|
||||||
ahash = "0.8"
|
ahash = "0.8"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
@ -130,6 +147,7 @@ awc = { version = "3", features = ["openssl"] }
|
|||||||
|
|
||||||
brotli = "6"
|
brotli = "6"
|
||||||
const-str = "0.5"
|
const-str = "0.5"
|
||||||
|
core_affinity = "0.8"
|
||||||
criterion = { version = "0.5", features = ["html_reports"] }
|
criterion = { version = "0.5", features = ["html_reports"] }
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
flate2 = "1.0.13"
|
flate2 = "1.0.13"
|
||||||
|
@ -8,10 +8,10 @@
|
|||||||
<!-- prettier-ignore-start -->
|
<!-- prettier-ignore-start -->
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-web)
|
[](https://crates.io/crates/actix-web)
|
||||||
[](https://docs.rs/actix-web/4.6.0)
|
[](https://docs.rs/actix-web/4.7.0)
|
||||||

|

|
||||||

|

|
||||||
[](https://deps.rs/crate/actix-web/4.6.0)
|
[](https://deps.rs/crate/actix-web/4.7.0)
|
||||||
<br />
|
<br />
|
||||||
[](https://github.com/actix/actix-web/actions/workflows/ci.yml)
|
[](https://github.com/actix/actix-web/actions/workflows/ci.yml)
|
||||||
[](https://codecov.io/gh/actix/actix-web)
|
[](https://codecov.io/gh/actix/actix-web)
|
||||||
|
41
actix-web/examples/worker-cpu-pin.rs
Normal file
41
actix-web/examples/worker-cpu-pin.rs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
use std::{
|
||||||
|
io,
|
||||||
|
sync::{
|
||||||
|
atomic::{AtomicUsize, Ordering},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
|
thread,
|
||||||
|
};
|
||||||
|
|
||||||
|
use actix_web::{middleware, web, App, HttpServer};
|
||||||
|
|
||||||
|
async fn hello() -> &'static str {
|
||||||
|
"Hello world!"
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::main]
|
||||||
|
async fn main() -> io::Result<()> {
|
||||||
|
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
||||||
|
|
||||||
|
let core_ids = core_affinity::get_core_ids().unwrap();
|
||||||
|
let n_core_ids = core_ids.len();
|
||||||
|
let next_core_id = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
|
HttpServer::new(move || {
|
||||||
|
let pin = Arc::clone(&next_core_id).fetch_add(1, Ordering::AcqRel);
|
||||||
|
log::info!(
|
||||||
|
"setting CPU affinity for worker {}: pinning to core {}",
|
||||||
|
thread::current().name().unwrap(),
|
||||||
|
pin,
|
||||||
|
);
|
||||||
|
core_affinity::set_for_current(core_ids[pin]);
|
||||||
|
|
||||||
|
App::new()
|
||||||
|
.wrap(middleware::Logger::default())
|
||||||
|
.service(web::resource("/").get(hello))
|
||||||
|
})
|
||||||
|
.bind(("127.0.0.1", 8080))?
|
||||||
|
.workers(n_core_ids)
|
||||||
|
.run()
|
||||||
|
.await
|
||||||
|
}
|
@ -112,8 +112,8 @@ where
|
|||||||
/// })
|
/// })
|
||||||
/// ```
|
/// ```
|
||||||
#[doc(alias = "manage")]
|
#[doc(alias = "manage")]
|
||||||
pub fn app_data<U: 'static>(mut self, ext: U) -> Self {
|
pub fn app_data<U: 'static>(mut self, data: U) -> Self {
|
||||||
self.extensions.insert(ext);
|
self.extensions.insert(data);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,6 +60,12 @@ impl<T: ResponseError + 'static> From<T> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Box<dyn ResponseError>> for Error {
|
||||||
|
fn from(value: Box<dyn ResponseError>) -> Self {
|
||||||
|
Error { cause: value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<Error> for Response<BoxBody> {
|
impl From<Error> for Response<BoxBody> {
|
||||||
fn from(err: Error) -> Response<BoxBody> {
|
fn from(err: Error) -> Response<BoxBody> {
|
||||||
err.error_response().into()
|
err.error_response().into()
|
||||||
|
@ -110,6 +110,12 @@ impl<'a> GuardContext<'a> {
|
|||||||
pub fn header<H: Header>(&self) -> Option<H> {
|
pub fn header<H: Header>(&self) -> Option<H> {
|
||||||
H::parse(self.req).ok()
|
H::parse(self.req).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Counterpart to [HttpRequest::app_data](crate::HttpRequest::app_data).
|
||||||
|
#[inline]
|
||||||
|
pub fn app_data<T: 'static>(&self) -> Option<&T> {
|
||||||
|
self.req.app_data()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Interface for routing guards.
|
/// Interface for routing guards.
|
||||||
@ -512,4 +518,18 @@ mod tests {
|
|||||||
.to_srv_request();
|
.to_srv_request();
|
||||||
assert!(guard.check(&req.guard_ctx()));
|
assert!(guard.check(&req.guard_ctx()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn app_data() {
|
||||||
|
const TEST_VALUE: u32 = 42;
|
||||||
|
let guard = fn_guard(|ctx| dbg!(ctx.app_data::<u32>()) == Some(&TEST_VALUE));
|
||||||
|
|
||||||
|
let req = TestRequest::default().app_data(TEST_VALUE).to_srv_request();
|
||||||
|
assert!(guard.check(&req.guard_ctx()));
|
||||||
|
|
||||||
|
let req = TestRequest::default()
|
||||||
|
.app_data(TEST_VALUE * 2)
|
||||||
|
.to_srv_request();
|
||||||
|
assert!(!guard.check(&req.guard_ctx()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -145,5 +145,6 @@ codegen_reexport!(delete);
|
|||||||
codegen_reexport!(trace);
|
codegen_reexport!(trace);
|
||||||
codegen_reexport!(connect);
|
codegen_reexport!(connect);
|
||||||
codegen_reexport!(options);
|
codegen_reexport!(options);
|
||||||
|
codegen_reexport!(scope);
|
||||||
|
|
||||||
pub(crate) type BoxError = Box<dyn std::error::Error>;
|
pub(crate) type BoxError = Box<dyn std::error::Error>;
|
||||||
|
@ -38,15 +38,6 @@ pub struct Compat<T> {
|
|||||||
transform: T,
|
transform: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
impl Compat<super::Noop> {
|
|
||||||
pub(crate) fn noop() -> Self {
|
|
||||||
Self {
|
|
||||||
transform: super::Noop,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Compat<T> {
|
impl<T> Compat<T> {
|
||||||
/// Wrap a middleware to give it broader compatibility.
|
/// Wrap a middleware to give it broader compatibility.
|
||||||
pub fn new(middleware: T) -> Self {
|
pub fn new(middleware: T) -> Self {
|
||||||
@ -152,7 +143,7 @@ mod tests {
|
|||||||
use crate::{
|
use crate::{
|
||||||
dev::ServiceRequest,
|
dev::ServiceRequest,
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
middleware::{self, Condition, Logger},
|
middleware::{self, Condition, Identity, Logger},
|
||||||
test::{self, call_service, init_service, TestRequest},
|
test::{self, call_service, init_service, TestRequest},
|
||||||
web, App, HttpResponse,
|
web, App, HttpResponse,
|
||||||
};
|
};
|
||||||
@ -225,7 +216,7 @@ mod tests {
|
|||||||
async fn compat_noop_is_noop() {
|
async fn compat_noop_is_noop() {
|
||||||
let srv = test::ok_service();
|
let srv = test::ok_service();
|
||||||
|
|
||||||
let mw = Compat::noop()
|
let mw = Compat::new(Identity)
|
||||||
.new_transform(srv.into_service())
|
.new_transform(srv.into_service())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -141,7 +141,7 @@ mod tests {
|
|||||||
header::{HeaderValue, CONTENT_TYPE},
|
header::{HeaderValue, CONTENT_TYPE},
|
||||||
StatusCode,
|
StatusCode,
|
||||||
},
|
},
|
||||||
middleware::{self, ErrorHandlerResponse, ErrorHandlers},
|
middleware::{self, ErrorHandlerResponse, ErrorHandlers, Identity},
|
||||||
test::{self, TestRequest},
|
test::{self, TestRequest},
|
||||||
web::Bytes,
|
web::Bytes,
|
||||||
HttpResponse,
|
HttpResponse,
|
||||||
@ -158,7 +158,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn compat_with_builtin_middleware() {
|
fn compat_with_builtin_middleware() {
|
||||||
let _ = Condition::new(true, middleware::Compat::noop());
|
let _ = Condition::new(true, middleware::Compat::new(Identity));
|
||||||
let _ = Condition::new(true, middleware::Logger::default());
|
let _ = Condition::new(true, middleware::Logger::default());
|
||||||
let _ = Condition::new(true, middleware::Compress::default());
|
let _ = Condition::new(true, middleware::Compress::default());
|
||||||
let _ = Condition::new(true, middleware::NormalizePath::trim());
|
let _ = Condition::new(true, middleware::NormalizePath::trim());
|
||||||
|
@ -2,35 +2,39 @@
|
|||||||
|
|
||||||
use actix_utils::future::{ready, Ready};
|
use actix_utils::future::{ready, Ready};
|
||||||
|
|
||||||
use crate::dev::{Service, Transform};
|
use crate::dev::{forward_ready, Service, Transform};
|
||||||
|
|
||||||
/// A no-op middleware that passes through request and response untouched.
|
/// A no-op middleware that passes through request and response untouched.
|
||||||
pub(crate) struct Noop;
|
#[derive(Debug, Clone, Default)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct Identity;
|
||||||
|
|
||||||
impl<S: Service<Req>, Req> Transform<S, Req> for Noop {
|
impl<S: Service<Req>, Req> Transform<S, Req> for Identity {
|
||||||
type Response = S::Response;
|
type Response = S::Response;
|
||||||
type Error = S::Error;
|
type Error = S::Error;
|
||||||
type Transform = NoopService<S>;
|
type Transform = IdentityMiddleware<S>;
|
||||||
type InitError = ();
|
type InitError = ();
|
||||||
type Future = Ready<Result<Self::Transform, Self::InitError>>;
|
type Future = Ready<Result<Self::Transform, Self::InitError>>;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn new_transform(&self, service: S) -> Self::Future {
|
fn new_transform(&self, service: S) -> Self::Future {
|
||||||
ready(Ok(NoopService { service }))
|
ready(Ok(IdentityMiddleware { service }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub(crate) struct NoopService<S> {
|
pub struct IdentityMiddleware<S> {
|
||||||
service: S,
|
service: S,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: Service<Req>, Req> Service<Req> for NoopService<S> {
|
impl<S: Service<Req>, Req> Service<Req> for IdentityMiddleware<S> {
|
||||||
type Response = S::Response;
|
type Response = S::Response;
|
||||||
type Error = S::Error;
|
type Error = S::Error;
|
||||||
type Future = S::Future;
|
type Future = S::Future;
|
||||||
|
|
||||||
crate::dev::forward_ready!(service);
|
forward_ready!(service);
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn call(&self, req: Req) -> Self::Future {
|
fn call(&self, req: Req) -> Self::Future {
|
||||||
self.service.call(req)
|
self.service.call(req)
|
||||||
}
|
}
|
@ -218,31 +218,27 @@
|
|||||||
//! [lab_from_fn]: https://docs.rs/actix-web-lab/latest/actix_web_lab/middleware/fn.from_fn.html
|
//! [lab_from_fn]: https://docs.rs/actix-web-lab/latest/actix_web_lab/middleware/fn.from_fn.html
|
||||||
|
|
||||||
mod compat;
|
mod compat;
|
||||||
|
#[cfg(feature = "__compress")]
|
||||||
|
mod compress;
|
||||||
mod condition;
|
mod condition;
|
||||||
mod default_headers;
|
mod default_headers;
|
||||||
mod err_handlers;
|
mod err_handlers;
|
||||||
|
mod identity;
|
||||||
mod logger;
|
mod logger;
|
||||||
#[cfg(test)]
|
|
||||||
mod noop;
|
|
||||||
mod normalize;
|
mod normalize;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(feature = "__compress")]
|
||||||
pub(crate) use self::noop::Noop;
|
pub use self::compress::Compress;
|
||||||
pub use self::{
|
pub use self::{
|
||||||
compat::Compat,
|
compat::Compat,
|
||||||
condition::Condition,
|
condition::Condition,
|
||||||
default_headers::DefaultHeaders,
|
default_headers::DefaultHeaders,
|
||||||
err_handlers::{ErrorHandlerResponse, ErrorHandlers},
|
err_handlers::{ErrorHandlerResponse, ErrorHandlers},
|
||||||
|
identity::Identity,
|
||||||
logger::Logger,
|
logger::Logger,
|
||||||
normalize::{NormalizePath, TrailingSlash},
|
normalize::{NormalizePath, TrailingSlash},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "__compress")]
|
|
||||||
mod compress;
|
|
||||||
|
|
||||||
#[cfg(feature = "__compress")]
|
|
||||||
pub use self::compress::Compress;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -7,7 +7,7 @@ use actix_http::{
|
|||||||
|
|
||||||
use crate::{HttpRequest, HttpResponse, Responder};
|
use crate::{HttpRequest, HttpResponse, Responder};
|
||||||
|
|
||||||
/// Allows overriding status code and headers for a [`Responder`].
|
/// Allows overriding status code and headers (including cookies) for a [`Responder`].
|
||||||
///
|
///
|
||||||
/// Created by calling the [`customize`](Responder::customize) method on a [`Responder`] type.
|
/// Created by calling the [`customize`](Responder::customize) method on a [`Responder`] type.
|
||||||
pub struct CustomizeResponder<R> {
|
pub struct CustomizeResponder<R> {
|
||||||
@ -137,6 +137,29 @@ impl<R: Responder> CustomizeResponder<R> {
|
|||||||
Some(&mut self.inner)
|
Some(&mut self.inner)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Appends a `cookie` to the final response.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Final response will be an error if `cookie` cannot be converted into a valid header value.
|
||||||
|
#[cfg(feature = "cookies")]
|
||||||
|
pub fn add_cookie(mut self, cookie: &crate::cookie::Cookie<'_>) -> Self {
|
||||||
|
use actix_http::header::{TryIntoHeaderValue as _, SET_COOKIE};
|
||||||
|
|
||||||
|
if let Some(inner) = self.inner() {
|
||||||
|
match cookie.to_string().try_into_value() {
|
||||||
|
Ok(val) => {
|
||||||
|
inner.append_headers.append(SET_COOKIE, val);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
self.error = Some(err.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Responder for CustomizeResponder<T>
|
impl<T> Responder for CustomizeResponder<T>
|
||||||
@ -175,6 +198,7 @@ mod tests {
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
|
cookie::Cookie,
|
||||||
http::header::{HeaderValue, CONTENT_TYPE},
|
http::header::{HeaderValue, CONTENT_TYPE},
|
||||||
test::TestRequest,
|
test::TestRequest,
|
||||||
};
|
};
|
||||||
@ -209,6 +233,22 @@ mod tests {
|
|||||||
to_bytes(res.into_body()).await.unwrap(),
|
to_bytes(res.into_body()).await.unwrap(),
|
||||||
Bytes::from_static(b"test"),
|
Bytes::from_static(b"test"),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let res = "test"
|
||||||
|
.to_string()
|
||||||
|
.customize()
|
||||||
|
.add_cookie(&Cookie::new("name", "value"))
|
||||||
|
.respond_to(&req);
|
||||||
|
|
||||||
|
assert!(res.status().is_success());
|
||||||
|
assert_eq!(
|
||||||
|
res.cookies().collect::<Vec<Cookie<'_>>>(),
|
||||||
|
vec![Cookie::new("name", "value")],
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
to_bytes(res.into_body()).await.unwrap(),
|
||||||
|
Bytes::from_static(b"test"),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
|
@ -64,7 +64,7 @@ compress-gzip = ["actix-http/compress-gzip", "__compress"]
|
|||||||
compress-zstd = ["actix-http/compress-zstd", "__compress"]
|
compress-zstd = ["actix-http/compress-zstd", "__compress"]
|
||||||
|
|
||||||
# Cookie parsing and cookie jar
|
# Cookie parsing and cookie jar
|
||||||
cookies = ["cookie"]
|
cookies = ["dep:cookie"]
|
||||||
|
|
||||||
# Use `trust-dns-resolver` crate as DNS resolver
|
# Use `trust-dns-resolver` crate as DNS resolver
|
||||||
trust-dns = ["trust-dns-resolver"]
|
trust-dns = ["trust-dns-resolver"]
|
||||||
|
@ -1080,7 +1080,7 @@ mod resolver {
|
|||||||
|
|
||||||
// resolver struct is cached in thread local so new clients can reuse the existing instance
|
// resolver struct is cached in thread local so new clients can reuse the existing instance
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static TRUST_DNS_RESOLVER: RefCell<Option<Resolver>> = RefCell::new(None);
|
static TRUST_DNS_RESOLVER: RefCell<Option<Resolver>> = const { RefCell::new(None) };
|
||||||
}
|
}
|
||||||
|
|
||||||
// get from thread local or construct a new trust-dns resolver.
|
// get from thread local or construct a new trust-dns resolver.
|
||||||
|
6
justfile
6
justfile
@ -4,7 +4,7 @@ _list:
|
|||||||
# Format workspace.
|
# Format workspace.
|
||||||
fmt:
|
fmt:
|
||||||
cargo +nightly fmt
|
cargo +nightly fmt
|
||||||
npx -y prettier --write $(fd --type=file --hidden --extension=md --extension=yml)
|
fd --hidden --type=file --extension=md --extension=yml --exec-batch npx -y prettier --write
|
||||||
|
|
||||||
# Downgrade dev-dependencies necessary to run MSRV checks/tests.
|
# Downgrade dev-dependencies necessary to run MSRV checks/tests.
|
||||||
[private]
|
[private]
|
||||||
@ -32,6 +32,10 @@ all_crate_features := if os() == "linux" {
|
|||||||
"--features='" + non_linux_all_features_list + "'"
|
"--features='" + non_linux_all_features_list + "'"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Run Clippy over workspace.
|
||||||
|
clippy toolchain="":
|
||||||
|
cargo {{ toolchain }} clippy --workspace --all-targets {{ all_crate_features }}
|
||||||
|
|
||||||
# Test workspace using MSRV.
|
# Test workspace using MSRV.
|
||||||
test-msrv: downgrade-for-msrv (test msrv_rustup)
|
test-msrv: downgrade-for-msrv (test msrv_rustup)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user