1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-04 09:56:22 +02:00

Compare commits

...

29 Commits

Author SHA1 Message Date
a5c78483f9 chore(actix-web): prepare release 4.7.0 2024-06-09 00:22:03 +01:00
12a0521ef8 chore(actix-multipart): prepare release 0.6.2 2024-06-09 00:20:36 +01:00
b4faf8820c chore(actix-web-codegen): prepare release 4.3.0 2024-06-09 00:19:09 +01:00
d6f885127d chore(actix-test): prepare release 0.1.4 2024-06-09 00:16:36 +01:00
ebc43dcf1b feat: forwards-compatibility for handler visibility inheritance fix (#3391) 2024-06-09 00:10:15 +01:00
7c4c26d2df feat: expose Identity middleware (#3390) 2024-06-08 05:26:26 +01:00
3db7891303 Scope macro (#3136)
* add scope proc macro

* Update scope macro code to work with current HttpServiceFactory

* started some test code

* add some unit tests

* code formatting cleanup

* add another test for combining and calling 2 scopes

* format code with formatter

* Update actix-web-codegen/src/lib.rs with comment documentation fix

Co-authored-by: oliver <151407407+kwfn@users.noreply.github.com>

* work in progress. revised procedural macro to change othe macro call

* add tests again. refactor nested code.

* clean up code. fix bugs with route and method attributes with parameters

* clean up for rust fmt

* clean up for rust fmt

* fix out of date comment for scope macro

* sync to master branch by adding test_wrap

* needed to format code

* test: split out scope tests

* test: add negative tests

* chore: move imports back inside (?)

* docs: tweak scope docs

* fix: prevent trailing slashes in scope prefixes

* chore: address clippy lints

---------

Co-authored-by: oliver <151407407+kwfn@users.noreply.github.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-06-07 22:10:48 +00:00
c366649516 docs: example of CPU core pinning 2024-06-07 16:57:13 +01:00
534cfe1fda feat: add .customize().add_cookie() (#3215)
* feat: add .customize().add_cookie()

* docs: added cookie hint

* fix: added unwrap to test of add_cookie()

* docs: added changelog entry for .customize().add_cookie()

* chore: make append_header infallible

* docs: update changelog

---------

Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-06-07 15:22:48 +00:00
cff958e518 chore: address clippy lint 2024-06-07 16:10:25 +01:00
b9305ff59d chore: fmt 2024-06-07 16:05:42 +01:00
5221c1b194 ci: pin msrv lookup job 2024-06-07 16:05:41 +01:00
4493aa35d0 actix-http::ws: Remove redundant + 4 byte reservation when masked (#3371)
* actix-http::ws: Remove redundant + 4 byte reservation when masked

* actix-http: Update CHANGES wrt byte fix

* docs: remove changelog entry

---------

Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-06-07 14:41:32 +00:00
8b4d23a69a Allow disabling redirect following in actix-test (#3356)
If you're testing that redirects are being properly generated, then it's
useful to not have the client go off on a wild goose chase of its own.

Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-06-07 14:40:55 +00:00
8fdf358954 Add app_data method to GuardContext (#3341)
* changes: guard

* fix(guard): docs link to app_data

* docs: fix changelog

---------

Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-06-07 14:31:53 +00:00
b2d0196f34 Do not require actix-router default features from actix-web-codegen (#3372)
* fix: Do not require actix-router default features from actix-web-codegen

* docs: update changelog

* test: update trybuild stderr

---------

Co-authored-by: Dylan Anthony <dbanty@users.noreply.github.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-06-07 14:08:13 +00:00
85655f731d From Boxed ResponseError impl added (#3388)
* From Boxed ResponseError impl added

* docs: update changelog

---------

Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-06-07 13:55:29 +00:00
ebd8bb266d ci: fix cargo-public-api 2024-06-07 14:31:20 +01:00
5c18569b78 docs: align App:app_data arg name 2024-06-07 14:31:20 +01:00
dd84bcb609 build(deps): bump taiki-e/install-action from 2.33.34 to 2.34.0 (#3386)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.33.34 to 2.34.0.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.33.34...v2.34.0)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-03 01:24:09 +00:00
3ce97effa2 ci: delete upload doc workflow 2024-05-28 01:21:23 +01:00
26efa64278 build(deps): bump taiki-e/install-action from 2.33.26 to 2.33.34 (#3380)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.33.26 to 2.33.34.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.33.26...v2.33.34)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-27 00:58:18 +00:00
cc06fd6a5e build(deps): bump codecov/codecov-action from 4.4.0 to 4.4.1 (#3381)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.4.0 to 4.4.1.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v4.4.0...v4.4.1)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-27 00:57:59 +00:00
1b214bc5f5 build(deps): bump codecov/codecov-action from 4.3.1 to 4.4.0 (#3374)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.3.1 to 4.4.0.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v4.3.1...v4.4.0)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-20 09:30:48 +00:00
d4bcdf28f2 build(deps): bump JamesIves/github-pages-deploy-action from 4.6.0 to 4.6.1 (#3375)
build(deps): bump JamesIves/github-pages-deploy-action

Bumps [JamesIves/github-pages-deploy-action](https://github.com/jamesives/github-pages-deploy-action) from 4.6.0 to 4.6.1.
- [Release notes](https://github.com/jamesives/github-pages-deploy-action/releases)
- [Commits](https://github.com/jamesives/github-pages-deploy-action/compare/v4.6.0...v4.6.1)

---
updated-dependencies:
- dependency-name: JamesIves/github-pages-deploy-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-20 09:30:27 +00:00
4f7b334d80 build(deps): bump taiki-e/install-action from 2.33.22 to 2.33.26 (#3376)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.33.22 to 2.33.26.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.33.22...v2.33.26)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-20 09:27:44 +00:00
fdff3775a8 ci: use mold linker (#3370) 2024-05-19 20:24:33 +01:00
b342b8fc82 chore(actix-router): prepare release 0.5.3 2024-05-19 12:09:46 +01:00
804a344565 ci: limit cargo hack concurrency 2024-05-19 12:06:20 +01:00
50 changed files with 690 additions and 140 deletions

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);
}; };

View File

@ -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.

View File

@ -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>",

View File

@ -5,17 +5,16 @@
<!-- prettier-ignore-start --> <!-- prettier-ignore-start -->
[![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart) [![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart)
[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.6.1)](https://docs.rs/actix-multipart/0.6.1) [![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.6.2)](https://docs.rs/actix-multipart/0.6.2)
![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg) ![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-multipart/0.6.1/status.svg)](https://deps.rs/crate/actix-multipart/0.6.1) [![dependency status](https://deps.rs/crate/actix-multipart/0.6.2/status.svg)](https://deps.rs/crate/actix-multipart/0.6.2)
[![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart) [![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](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

View File

@ -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.

View File

@ -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>",

View File

@ -3,11 +3,11 @@
<!-- prettier-ignore-start --> <!-- prettier-ignore-start -->
[![crates.io](https://img.shields.io/crates/v/actix-router?label=latest)](https://crates.io/crates/actix-router) [![crates.io](https://img.shields.io/crates/v/actix-router?label=latest)](https://crates.io/crates/actix-router)
[![Documentation](https://docs.rs/actix-router/badge.svg?version=0.5.2)](https://docs.rs/actix-router/0.5.2) [![Documentation](https://docs.rs/actix-router/badge.svg?version=0.5.3)](https://docs.rs/actix-router/0.5.3)
![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg) ![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-router.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-router.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-router/0.5.2/status.svg)](https://deps.rs/crate/actix-router/0.5.2) [![dependency status](https://deps.rs/crate/actix-router/0.5.3/status.svg)](https://deps.rs/crate/actix-router/0.5.3)
[![Download](https://img.shields.io/crates/d/actix-router.svg)](https://crates.io/crates/actix-router) [![Download](https://img.shields.io/crates/d/actix-router.svg)](https://crates.io/crates/actix-router)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)

View File

@ -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

View File

@ -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>",

View File

@ -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

View File

@ -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

View File

@ -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"] }

View File

@ -5,11 +5,11 @@
<!-- prettier-ignore-start --> <!-- prettier-ignore-start -->
[![crates.io](https://img.shields.io/crates/v/actix-web-codegen?label=latest)](https://crates.io/crates/actix-web-codegen) [![crates.io](https://img.shields.io/crates/v/actix-web-codegen?label=latest)](https://crates.io/crates/actix-web-codegen)
[![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=4.2.2)](https://docs.rs/actix-web-codegen/4.2.2) [![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=4.3.0)](https://docs.rs/actix-web-codegen/4.3.0)
![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg) ![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg)
![License](https://img.shields.io/crates/l/actix-web-codegen.svg) ![License](https://img.shields.io/crates/l/actix-web-codegen.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-web-codegen/4.2.2/status.svg)](https://deps.rs/crate/actix-web-codegen/4.2.2) [![dependency status](https://deps.rs/crate/actix-web-codegen/4.3.0/status.svg)](https://deps.rs/crate/actix-web-codegen/4.3.0)
[![Download](https://img.shields.io/crates/d/actix-web-codegen.svg)](https://crates.io/crates/actix-web-codegen) [![Download](https://img.shields.io/crates/d/actix-web-codegen.svg)](https://crates.io/crates/actix-web-codegen)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)

View File

@ -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
}

View File

@ -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
}

View 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")
}

View 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");
}

View File

@ -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");

View File

@ -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

View 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() {}

View 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)]
| ^^^

View File

@ -0,0 +1,6 @@
use actix_web_codegen::scope;
#[scope]
mod api {}
fn main() {}

View File

@ -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)

View File

@ -0,0 +1,8 @@
use actix_web_codegen::scope;
#[scope("/api")]
async fn index() -> &'static str {
"Hello World!"
}
fn main() {}

View 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 {
| ^^^^^

View File

@ -0,0 +1,6 @@
use actix_web_codegen::scope;
#[scope("/api/")]
mod api {}
fn main() {}

View File

@ -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/")]
| ^^^^^^^

View File

@ -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

View File

@ -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"

View File

@ -8,10 +8,10 @@
<!-- prettier-ignore-start --> <!-- prettier-ignore-start -->
[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) [![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web)
[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.6.0)](https://docs.rs/actix-web/4.6.0) [![Documentation](https://docs.rs/actix-web/badge.svg?version=4.7.0)](https://docs.rs/actix-web/4.7.0)
![MSRV](https://img.shields.io/badge/rustc-1.72+-ab6000.svg) ![MSRV](https://img.shields.io/badge/rustc-1.72+-ab6000.svg)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg)
[![Dependency Status](https://deps.rs/crate/actix-web/4.6.0/status.svg)](https://deps.rs/crate/actix-web/4.6.0) [![Dependency Status](https://deps.rs/crate/actix-web/4.7.0/status.svg)](https://deps.rs/crate/actix-web/4.7.0)
<br /> <br />
[![CI](https://github.com/actix/actix-web/actions/workflows/ci.yml/badge.svg)](https://github.com/actix/actix-web/actions/workflows/ci.yml) [![CI](https://github.com/actix/actix-web/actions/workflows/ci.yml/badge.svg)](https://github.com/actix/actix-web/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web)

View 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
}

View File

@ -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
} }

View File

@ -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()

View File

@ -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()));
}
} }

View File

@ -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>;

View File

@ -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();

View File

@ -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());

View File

@ -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)
} }

View File

@ -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::*;

View File

@ -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]

View File

@ -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"]

View File

@ -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.

View File

@ -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)