Compare commits

...

61 Commits

Author SHA1 Message Date
Guillermo Céspedes Tabárez b9a9f87559
Merge 895969923c into 024addfc40 2025-12-31 12:39:16 +00:00
Guillermo Céspedes Tabárez 895969923c
Merge branch 'main' into introspection 2025-12-31 09:39:12 -03:00
dependabot[bot] 024addfc40
build(deps): bump serde_json from 1.0.145 to 1.0.148 (#3865)
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.145 to 1.0.148.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.145...v1.0.148)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-version: 1.0.148
  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>
2025-12-30 00:51:22 +00:00
dependabot[bot] 06ad9309b8
build(deps): bump itoa from 1.0.15 to 1.0.17 (#3863)
Bumps [itoa](https://github.com/dtolnay/itoa) from 1.0.15 to 1.0.17.
- [Release notes](https://github.com/dtolnay/itoa/releases)
- [Commits](https://github.com/dtolnay/itoa/compare/1.0.15...1.0.17)

---
updated-dependencies:
- dependency-name: itoa
  dependency-version: 1.0.17
  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>
2025-12-30 00:51:03 +00:00
dependabot[bot] 9f9855d1a2
build(deps): bump tracing from 0.1.43 to 0.1.44 (#3864)
Bumps [tracing](https://github.com/tokio-rs/tracing) from 0.1.43 to 0.1.44.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-0.1.43...tracing-0.1.44)

---
updated-dependencies:
- dependency-name: tracing
  dependency-version: 0.1.44
  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>
2025-12-30 00:50:49 +00:00
dependabot[bot] 4c62e88edb
build(deps): bump derive_more from 2.1.0 to 2.1.1 (#3862)
Bumps [derive_more](https://github.com/JelteF/derive_more) from 2.1.0 to 2.1.1.
- [Release notes](https://github.com/JelteF/derive_more/releases)
- [Changelog](https://github.com/JelteF/derive_more/blob/master/CHANGELOG.md)
- [Commits](https://github.com/JelteF/derive_more/compare/v2.1.0...v2.1.1)

---
updated-dependencies:
- dependency-name: derive_more
  dependency-version: 2.1.1
  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>
2025-12-29 23:15:28 +00:00
dependabot[bot] 2d84d20ebd
build(deps): bump taiki-e/install-action from 2.63.1 to 2.65.6 (#3861)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.63.1 to 2.65.6.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](61e5998d10...28a9d316db)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.65.6
  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>
2025-12-29 23:15:16 +00:00
dependabot[bot] 2b8db90c4d
build(deps): bump rustls-pki-types from 1.13.1 to 1.13.2 (#3860)
Bumps [rustls-pki-types](https://github.com/rustls/pki-types) from 1.13.1 to 1.13.2.
- [Release notes](https://github.com/rustls/pki-types/releases)
- [Commits](https://github.com/rustls/pki-types/compare/v/1.13.1...v/1.13.2)

---
updated-dependencies:
- dependency-name: rustls-pki-types
  dependency-version: 1.13.2
  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>
2025-12-29 01:06:46 +00:00
dependabot[bot] 9c70a886b4
build(deps): bump taiki-e/install-action from 2.62.63 to 2.63.1 (#3857)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.63 to 2.63.1.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](50708e9ba8...61e5998d10)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.63.1
  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>
2025-12-15 21:21:12 +00:00
dependabot[bot] 4058f30cb0
build(deps): bump actions/checkout from 6.0.0 to 6.0.1 (#3852)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-13 12:54:19 +00:00
dependabot[bot] 73f6f5ca42
build(deps): bump log from 0.4.28 to 0.4.29 (#3854)
Bumps [log](https://github.com/rust-lang/log) from 0.4.28 to 0.4.29.
- [Release notes](https://github.com/rust-lang/log/releases)
- [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/log/compare/0.4.28...0.4.29)

---
updated-dependencies:
- dependency-name: log
  dependency-version: 0.4.29
  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>
2025-12-11 23:35:50 +00:00
dependabot[bot] 883a89bfc8
build(deps): bump taiki-e/install-action from 2.62.60 to 2.62.63 (#3851)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.62.60 to 2.62.63.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](3575e53270...50708e9ba8)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.62.63
  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>
2025-12-11 23:34:35 +00:00
dependabot[bot] 1556b10379
build(deps): bump derive_more from 2.0.1 to 2.1.0 (#3853)
Bumps [derive_more](https://github.com/JelteF/derive_more) from 2.0.1 to 2.1.0.
- [Release notes](https://github.com/JelteF/derive_more/releases)
- [Changelog](https://github.com/JelteF/derive_more/blob/master/CHANGELOG.md)
- [Commits](https://github.com/JelteF/derive_more/compare/v2.0.1...v2.1.0)

---
updated-dependencies:
- dependency-name: derive_more
  dependency-version: 2.1.0
  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>
2025-12-11 23:32:35 +00:00
Yuki Okushi 41f99b0ddd
fix(*): replace rustls-pemfile (#3855) 2025-12-12 08:11:24 +09:00
Rob Ede 917b5f2048
chore: address clippy lints 2025-12-11 07:26:07 +00:00
dependabot[bot] 28843c4ed3
build(deps): bump tracing from 0.1.42 to 0.1.43 (#3846)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-01 09:56:58 +00:00
dependabot[bot] f192357f09
build(deps): bump time from 0.3.41 to 0.3.44 (#3848)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-01 09:56:50 +00:00
dependabot[bot] d9c676715b
build(deps): bump taiki-e/install-action from 2.62.56 to 2.62.60 (#3849)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-01 09:56:38 +00:00
dependabot[bot] 16eba96674
build(deps): bump EmbarkStudios/cargo-deny-action from 2.0.13 to 2.0.14 (#3847)
Bumps [EmbarkStudios/cargo-deny-action](https://github.com/embarkstudios/cargo-deny-action) from 2.0.13 to 2.0.14.
- [Release notes](https://github.com/embarkstudios/cargo-deny-action/releases)
- [Commits](f2ba7abc2a...76cd80eb77)

---
updated-dependencies:
- dependency-name: EmbarkStudios/cargo-deny-action
  dependency-version: 2.0.14
  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>
2025-12-01 04:24:56 +00:00
dependabot[bot] c7710036de
build(deps): bump divan from 0.1.15 to 0.1.21 (#3845)
Bumps [divan](https://github.com/nvzqz/divan) from 0.1.15 to 0.1.21.
- [Changelog](https://github.com/nvzqz/divan/blob/main/CHANGELOG.md)
- [Commits](https://github.com/nvzqz/divan/compare/v0.1.15...v0.1.21)

---
updated-dependencies:
- dependency-name: divan
  dependency-version: 0.1.21
  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>
2025-12-01 04:20:13 +00:00
Rob Ede ff50dd03a6
chore: upgrade MSRV to 1.82 (#3844) 2025-11-26 20:26:35 +00:00
Yuki Okushi 24489d4c6d
doc(web): Fix links in README.md (#3834) 2025-11-26 16:40:07 +00:00
Rob Ede 30b82062a1
chore(actix-files): prepare release 0.6.9 2025-11-26 16:37:53 +00:00
Rob Ede fe6c0a3b2b
chore(actix-web): prepare release 4.12.1 2025-11-26 16:34:38 +00:00
Rob Ede 37a8b9caa8
chore(web): fix actix-http requirement
fixes #3832
2025-11-26 16:32:41 +00:00
Rob Ede 37ff707b52
chore: fix derive_more feature requirements for actix-files
fixes #3842
2025-11-26 16:32:38 +00:00
Guillermo Céspedes Tabárez a3e428f7fb
Merge branch 'main' into introspection 2025-11-17 21:30:50 -03:00
Guillermo Céspedes Tabárez f3b64b0f73
Merge branch 'master' into introspection 2025-07-23 17:28:49 -03:00
Guillermo Céspedes Tabárez dfb4aa3baf
Merge branch 'master' into introspection 2025-06-17 19:09:21 -03:00
dertin 7ff7768dc4 feat(introspection): implement experimental introspection feature with multiple App instances 2025-06-11 17:37:26 -03:00
Guillermo Céspedes Tabárez 2b52a60bc2
Merge branch 'master' into introspection 2025-06-10 20:50:56 -03:00
Guillermo Céspedes Tabárez 7296f6ffdb
Merge branch 'master' into introspection 2025-05-27 01:50:58 -03:00
Guillermo Céspedes Tabárez d1706dcdd6 Merge branch 'introspection' of github.com:dertin/actix-web into introspection 2025-05-19 21:57:12 -03:00
Guillermo Céspedes Tabárez 23fed2298e feat(introspection): enhance introspection handlers for JSON and plain text responses 2025-05-19 21:55:49 -03:00
Guillermo Céspedes Tabárez 360baa3130
Merge branch 'master' into introspection 2025-05-19 17:55:08 -03:00
Guillermo Céspedes Tabárez c8a7271d21 optimize debug log and apply clippy/fmt suggestions 2025-05-19 02:41:48 -03:00
Guillermo Céspedes Tabárez 0a9f6c1955 feat(introspection): enhance introspection feature with detailed route registration and full path tracking 2025-05-18 21:19:34 -03:00
Guillermo Céspedes Tabárez 2f64cdb60a fix Cargo.lock 2025-05-12 02:57:39 -03:00
Guillermo Céspedes Tabárez dcad1d9b89 Merge branch 'master' into introspection 2025-05-12 02:37:52 -03:00
Guillermo Céspedes Tabárez d501102610 feat(introspection): rename feature from `resources-introspection` to `experimental-introspection`
- Refactored introspection logic.
- Enhanced route introspection to register HTTP methods and guard names.
- Added example for testing the experimental introspection feature.
2025-05-12 02:29:11 -03:00
Guillermo Céspedes Tabárez 013a8ec6b6
Merge branch 'master' into introspection 2025-04-22 14:24:42 -03:00
Guillermo Céspedes Tabárez c809ee8440
Merge branch 'master' into introspection 2025-04-10 18:10:16 -03:00
Guillermo Céspedes Tabárez 585552f70f
Merge branch 'master' into introspection 2025-04-01 22:39:21 -03:00
Guillermo Céspedes Tabárez 57b5937db3
Merge branch 'master' into introspection 2025-03-21 16:21:25 -03:00
Guillermo Céspedes Tabárez 3506512ed9
Merge branch 'master' into introspection 2025-03-10 16:40:42 -03:00
Guillermo Céspedes Tabárez a449695661 Merge branch 'introspection' of https://github.com/dertin/actix-web into introspection 2025-03-05 02:37:44 -03:00
Guillermo Céspedes Tabárez aebab17c1e refactor(introspection): add GuardDetail enum and remove downcast_ref usage
- Added `GuardDetail` enum to encapsulate various introspection details of a guard.
- Refactored `HttpMethodsExtractor` implementation to use `GuardDetail` instead of `downcast_ref`.
2025-03-05 02:37:12 -03:00
Guillermo Céspedes Tabárez ae08dcf6dc refactor(introspection): add GuardDetail enum and remove downcast_ref usage
- Added `GuardDetail` enum to encapsulate various introspection details of a guard.
- Refactored `HttpMethodsExtractor` implementation to use `GuardDetail` instead of `downcast_ref`.
2025-03-05 02:34:39 -03:00
Guillermo Céspedes Tabárez ee7621594c Merge branch 'introspection' of https://github.com/dertin/actix-web into introspection 2025-03-04 05:50:41 -03:00
Guillermo Céspedes Tabárez 0548b127c3 Merge branch 'introspection' of https://github.com/dertin/actix-web into introspection 2025-03-04 05:50:04 -03:00
Guillermo Céspedes Tabárez 7821ba9c1b Merge branch 'introspection' of https://github.com/dertin/actix-web into introspection 2025-03-04 05:42:54 -03:00
Guillermo Céspedes Tabárez 3b99f86e89 fix(introspection): add conditional arbiter creation for io-uring support 2025-03-04 05:42:07 -03:00
Guillermo Céspedes Tabárez 2bb774a529 fix(introspection): add conditional arbiter creation for io-uring support 2025-03-04 05:36:49 -03:00
Guillermo Céspedes Tabárez a79dc9dc79 refactor: improve thread safety and add unit tests for introspection process 2025-03-04 05:09:18 -03:00
Guillermo Céspedes Tabárez c4be19942b ci: downgrade for msrv zerofrom to version 0.1.5 in justfile 2025-03-03 16:25:50 -03:00
Guillermo Céspedes Tabárez acd7c58097 chore: update changelog and fix docs for CI 2025-03-03 16:12:31 -03:00
Guillermo Céspedes Tabárez 176ea5da77 ci: downgrade for msrv litemap to version 0.7.4 in justfile 2025-03-03 16:05:01 -03:00
Guillermo Céspedes Tabárez 91fa813f0e fix(guards): replace take_guards with get_guards to prevent guard removal and fix test failures 2025-03-03 15:31:20 -03:00
Guillermo Céspedes Tabárez 819ee93e9b style: cargo fmt 2025-03-03 06:46:52 -03:00
Guillermo Céspedes Tabárez 041322ef9c misc: remove debug print 2025-03-03 06:10:45 -03:00
Guillermo Céspedes Tabárez 0d87cc53a1 feat(resources-introspection): add support for resource metadata retrieval 2025-03-03 05:27:52 -03:00
51 changed files with 1280 additions and 865 deletions

View File

@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Install Rust
run: |

View File

@ -28,7 +28,7 @@ jobs:
runs-on: ${{ matrix.target.os }}
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Install nasm
if: matrix.target.os == 'windows-latest'
@ -49,7 +49,7 @@ jobs:
toolchain: ${{ matrix.version.version }}
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
uses: taiki-e/install-action@f79fe7514db78f0a7bdba3cb6dd9c1baa7d046d9 # v2.62.56
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
with:
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
@ -71,7 +71,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Free Disk Space
run: ./scripts/free-disk-space.sh
@ -83,7 +83,7 @@ jobs:
uses: actions-rust-lang/setup-rust-toolchain@1780873c7b576612439a134613cc4cc74ce5538c # v1.15.2
- name: Install just, cargo-hack
uses: taiki-e/install-action@f79fe7514db78f0a7bdba3cb6dd9c1baa7d046d9 # v2.62.56
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
with:
tool: just,cargo-hack

View File

@ -39,7 +39,7 @@ jobs:
runs-on: ${{ matrix.target.os }}
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Install nasm
if: matrix.target.os == 'windows-latest'
@ -64,7 +64,7 @@ jobs:
toolchain: ${{ matrix.version.version }}
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
uses: taiki-e/install-action@f79fe7514db78f0a7bdba3cb6dd9c1baa7d046d9 # v2.62.56
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
with:
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
@ -87,13 +87,13 @@ jobs:
- name: deny check
if: matrix.version.name == 'stable' && matrix.target.os == 'ubuntu-latest'
uses: EmbarkStudios/cargo-deny-action@f2ba7abc2abebaf185c833c3961145a3c275caad # v2.0.13
uses: EmbarkStudios/cargo-deny-action@76cd80eb775d7bbbd2d80292136d74d39e1b4918 # v2.0.14
io-uring:
name: io-uring tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Install Rust
uses: actions-rust-lang/setup-rust-toolchain@1780873c7b576612439a134613cc4cc74ce5538c # v1.15.2
@ -109,7 +109,7 @@ jobs:
name: doc tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Install Rust (nightly)
uses: actions-rust-lang/setup-rust-toolchain@1780873c7b576612439a134613cc4cc74ce5538c # v1.15.2
@ -117,7 +117,7 @@ jobs:
toolchain: nightly
- name: Install just
uses: taiki-e/install-action@f79fe7514db78f0a7bdba3cb6dd9c1baa7d046d9 # v2.62.56
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
with:
tool: just

View File

@ -15,7 +15,7 @@ jobs:
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Install Rust (nightly)
uses: actions-rust-lang/setup-rust-toolchain@1780873c7b576612439a134613cc4cc74ce5538c # v1.15.2
@ -24,7 +24,7 @@ jobs:
components: llvm-tools
- name: Install just, cargo-llvm-cov, cargo-nextest
uses: taiki-e/install-action@f79fe7514db78f0a7bdba3cb6dd9c1baa7d046d9 # v2.62.56
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
with:
tool: just,cargo-llvm-cov,cargo-nextest

View File

@ -15,7 +15,7 @@ jobs:
fmt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Install Rust (nightly)
uses: actions-rust-lang/setup-rust-toolchain@1780873c7b576612439a134613cc4cc74ce5538c # v1.15.2
@ -33,7 +33,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Install Rust
uses: actions-rust-lang/setup-rust-toolchain@1780873c7b576612439a134613cc4cc74ce5538c # v1.15.2
@ -52,7 +52,7 @@ jobs:
lint-docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Install Rust (nightly)
uses: actions-rust-lang/setup-rust-toolchain@1780873c7b576612439a134613cc4cc74ce5538c # v1.15.2
@ -69,7 +69,7 @@ jobs:
if: false # rustdoc mismatch currently
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Install Rust (${{ vars.RUST_VERSION_EXTERNAL_TYPES }})
uses: actions-rust-lang/setup-rust-toolchain@1780873c7b576612439a134613cc4cc74ce5538c # v1.15.2
@ -77,7 +77,7 @@ jobs:
toolchain: ${{ vars.RUST_VERSION_EXTERNAL_TYPES }}
- name: Install just
uses: taiki-e/install-action@f79fe7514db78f0a7bdba3cb6dd9c1baa7d046d9 # v2.62.56
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
with:
tool: just

1022
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -19,7 +19,7 @@ homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web"
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.75"
rust-version = "1.82"
[profile.dev]
# Disabling debug info speeds up builds a bunch and we don't rely on it for debugging that much.

View File

@ -2,6 +2,12 @@
## Unreleased
- Minimum supported Rust version (MSRV) is now 1.82.
## 0.6.9
- Correct `derive_more` dependency feature requirements.
## 0.6.8
- Add `Files::with_permanent_redirect()` method.

View File

@ -1,6 +1,6 @@
[package]
name = "actix-files"
version = "0.6.8"
version = "0.6.9"
authors = ["Nikolay Kim <fafhrd91@gmail.com>", "Rob Ede <robjtede@icloud.com>"]
description = "Static file serving for Actix Web"
keywords = ["actix", "http", "async", "futures"]
@ -24,7 +24,7 @@ actix-web = { version = "4", default-features = false }
bitflags = "2"
bytes = "1"
derive_more = { version = "2", features = ["display", "error", "from"] }
derive_more = { version = "2", features = ["deref", "deref_mut", "display", "error", "from"] }
futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] }
http-range = "0.1.4"
log = "0.4"
@ -37,7 +37,7 @@ v_htmlescape = "0.15.5"
# experimental-io-uring
[target.'cfg(target_os = "linux")'.dependencies]
tokio-uring = { version = "0.5", optional = true, features = ["bytes"] }
actix-server = { version = "2.4", optional = true } # ensure matching tokio-uring versions
actix-server = { version = "2.4", optional = true } # ensure matching tokio-uring versions
[dev-dependencies]
actix-rt = "2.7"

View File

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

View File

@ -294,16 +294,11 @@ mod tests {
let res = HttpRange::parse(header, size);
if res.is_err() {
if let Err(err) = res {
if expected.is_empty() {
continue;
} else {
panic!(
"parse({}, {}) returned error {:?}",
header,
size,
res.unwrap_err()
);
panic!("parse({header}, {size}) returned error {err:?}");
}
}

View File

@ -2,7 +2,7 @@
## Unreleased
- Minimum supported Rust version (MSRV) is now 1.72.
- Minimum supported Rust version (MSRV) is now 1.82.
## 3.2.0

View File

@ -2,6 +2,8 @@
## Unreleased
- Minimum supported Rust version (MSRV) is now 1.82.
## 3.11.2
- Properly wake Payload receivers when feeding errors or EOF.

View File

@ -149,7 +149,7 @@ memchr = "2.4"
once_cell = "1.21"
rcgen = "0.13"
regex = "1.3"
rustls-pemfile = "2"
rustls-pki-types = "1.13.1"
rustversion = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1.0"

View File

@ -45,25 +45,14 @@ async fn main() -> io::Result<()> {
fn rustls_config() -> rustls::ServerConfig {
let rcgen::CertifiedKey { cert, key_pair } =
rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
let cert_file = cert.pem();
let key_file = key_pair.serialize_pem();
let cert_file = &mut io::BufReader::new(cert_file.as_bytes());
let key_file = &mut io::BufReader::new(key_file.as_bytes());
let cert_chain = rustls_pemfile::certs(cert_file)
.collect::<Result<Vec<_>, _>>()
.unwrap();
let mut keys = rustls_pemfile::pkcs8_private_keys(key_file)
.collect::<Result<Vec<_>, _>>()
.unwrap();
let cert_chain = vec![cert.der().clone()];
let key_der = rustls_pki_types::PrivateKeyDer::Pkcs8(
rustls_pki_types::PrivatePkcs8KeyDer::from(key_pair.serialize_der()),
);
let mut config = rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(
cert_chain,
rustls::pki_types::PrivateKeyDer::Pkcs8(keys.remove(0)),
)
.with_single_cert(cert_chain, key_der)
.unwrap();
const H1_ALPN: &[u8] = b"http/1.1";

View File

@ -82,29 +82,16 @@ impl Stream for Heartbeat {
}
fn tls_config() -> rustls::ServerConfig {
use std::io::BufReader;
use rustls_pemfile::{certs, pkcs8_private_keys};
let rcgen::CertifiedKey { cert, key_pair } =
rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
let cert_file = cert.pem();
let key_file = key_pair.serialize_pem();
let cert_file = &mut BufReader::new(cert_file.as_bytes());
let key_file = &mut BufReader::new(key_file.as_bytes());
let cert_chain = certs(cert_file).collect::<Result<Vec<_>, _>>().unwrap();
let mut keys = pkcs8_private_keys(key_file)
.collect::<Result<Vec<_>, _>>()
.unwrap();
let cert_chain = vec![cert.der().clone()];
let key_der = rustls_pki_types::PrivateKeyDer::Pkcs8(
rustls_pki_types::PrivatePkcs8KeyDer::from(key_pair.serialize_der()),
);
let mut config = rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(
cert_chain,
rustls::pki_types::PrivateKeyDer::Pkcs8(keys.remove(0)),
)
.with_single_cert(cert_chain, key_der)
.unwrap();
config.alpn_protocols.push(b"http/1.1".to_vec());

View File

@ -176,11 +176,7 @@ impl Inner {
/// Register future waiting data from payload.
/// Waker would be used in `Inner::wake`
fn register(&mut self, cx: &Context<'_>) {
if self
.task
.as_ref()
.map_or(true, |w| !cx.waker().will_wake(w))
{
if self.task.as_ref().is_none_or(|w| !cx.waker().will_wake(w)) {
self.task = Some(cx.waker().clone());
}
}
@ -191,7 +187,7 @@ impl Inner {
if self
.io_task
.as_ref()
.map_or(true, |w| !cx.waker().will_wake(w))
.is_none_or(|w| !cx.waker().will_wake(w))
{
self.io_task = Some(cx.waker().clone());
}

View File

@ -4,7 +4,7 @@ extern crate tls_rustls_023 as rustls;
use std::{
convert::Infallible,
io::{self, BufReader, Write},
io::{self, Write},
net::{SocketAddr, TcpStream as StdTcpStream},
sync::Arc,
task::Poll,
@ -27,7 +27,7 @@ use derive_more::{Display, Error};
use futures_core::{ready, Stream};
use futures_util::stream::once;
use rustls::{pki_types::ServerName, ServerConfig as RustlsServerConfig};
use rustls_pemfile::{certs, pkcs8_private_keys};
use rustls_pki_types::{PrivateKeyDer, PrivatePkcs8KeyDer};
async fn load_body<S>(stream: S) -> Result<BytesMut, PayloadError>
where
@ -54,23 +54,12 @@ where
fn tls_config() -> RustlsServerConfig {
let rcgen::CertifiedKey { cert, key_pair } =
rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
let cert_file = cert.pem();
let key_file = key_pair.serialize_pem();
let cert_file = &mut BufReader::new(cert_file.as_bytes());
let key_file = &mut BufReader::new(key_file.as_bytes());
let cert_chain = certs(cert_file).collect::<Result<Vec<_>, _>>().unwrap();
let mut keys = pkcs8_private_keys(key_file)
.collect::<Result<Vec<_>, _>>()
.unwrap();
let cert_chain = vec![cert.der().clone()];
let key_der = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(key_pair.serialize_der()));
let mut config = RustlsServerConfig::builder()
.with_no_client_auth()
.with_single_cert(
cert_chain,
rustls::pki_types::PrivateKeyDer::Pkcs8(keys.remove(0)),
)
.with_single_cert(cert_chain, key_der)
.unwrap();
config.alpn_protocols.push(HTTP1_1_ALPN_PROTOCOL.to_vec());

View File

@ -2,6 +2,8 @@
## Unreleased
- Minimum supported Rust version (MSRV) is now 1.82.
## 0.7.0
- Minimum supported Rust version (MSRV) is now 1.72.

View File

@ -16,19 +16,14 @@ use proc_macro2::Ident;
use quote::quote;
use syn::{parse_macro_input, Type};
#[derive(FromMeta)]
#[derive(Default, FromMeta)]
enum DuplicateField {
#[default]
Ignore,
Deny,
Replace,
}
impl Default for DuplicateField {
fn default() -> Self {
Self::Ignore
}
}
#[derive(FromDeriveInput, Default)]
#[darling(attributes(multipart), default)]
struct MultipartFormAttrs {

View File

@ -1,10 +1,10 @@
error: Could not parse size limit `2 bytes`: couldn't parse "bytes" into a known SI unit, couldn't parse unit of "bytes"
error: Could not parse size limit `2 bytes`: couldn't parse "bytes" into a known SI unit, Failed to parse unit "byt..."
--> tests/trybuild/size-limit-parse-fail.rs:6:5
|
6 | description: Text<String>,
| ^^^^^^^^^^^
error: Could not parse size limit `2 megabytes`: couldn't parse "megabytes" into a known SI unit, couldn't parse unit of "megabytes"
error: Could not parse size limit `2 megabytes`: couldn't parse "megabytes" into a known SI unit, Failed to parse unit "meg..."
--> tests/trybuild/size-limit-parse-fail.rs:12:5
|
12 | description: Text<String>,

View File

@ -2,7 +2,7 @@
## Unreleased
- Minimum supported Rust version (MSRV) is now 1.75.
- Minimum supported Rust version (MSRV) is now 1.82.
## 0.7.2

View File

@ -2,6 +2,8 @@
## Unreleased
- Minimum supported Rust version (MSRV) is now 1.82.
## 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.

View File

@ -2,6 +2,8 @@
## Unreleased
- Minimum supported Rust version (MSRV) is now 1.82.
## 0.1.5
- Add `TestServerConfig::listen_address()` method.

View File

@ -2,6 +2,8 @@
## Unreleased
- Minimum supported Rust version (MSRV) is now 1.82.
## 4.3.1 <!-- v4.3.1+deprecated -->
- Reduce memory usage by `take`-ing (rather than `split`-ing) the encoded buffer when yielding bytes in the response stream.

View File

@ -2,6 +2,8 @@
## Unreleased
- Minimum supported Rust version (MSRV) is now 1.82.
## 4.3.0
- Add `#[scope]` macro.

View File

@ -13,14 +13,14 @@ error[E0277]: the trait bound `fn() -> impl std::future::Future<Output = String>
| required by a bound introduced by this call
|
= help: the following other types implement trait `HttpServiceFactory`:
Resource<T>
actix_web::Scope<T>
Vec<T>
Redirect
(A,)
(A, B)
(A, B, C)
(A, B, C, D)
(A, B, C, D, E)
(A, B, C, D, E, F)
(A, B, C, D, E, F, G)
(A, B, C, D, E, F, G, H)
(A, B, C, D, E, F, G, H, I)
and $N others
note: required by a bound in `App::<T>::service`
--> $WORKSPACE/actix-web/src/app.rs

View File

@ -13,14 +13,14 @@ error[E0277]: the trait bound `fn() -> impl std::future::Future<Output = String>
| required by a bound introduced by this call
|
= help: the following other types implement trait `HttpServiceFactory`:
Resource<T>
actix_web::Scope<T>
Vec<T>
Redirect
(A,)
(A, B)
(A, B, C)
(A, B, C, D)
(A, B, C, D, E)
(A, B, C, D, E, F)
(A, B, C, D, E, F, G)
(A, B, C, D, E, F, G, H)
(A, B, C, D, E, F, G, H, I)
and $N others
note: required by a bound in `App::<T>::service`
--> $WORKSPACE/actix-web/src/app.rs

View File

@ -15,14 +15,14 @@ error[E0277]: the trait bound `fn() -> impl std::future::Future<Output = String>
| required by a bound introduced by this call
|
= help: the following other types implement trait `HttpServiceFactory`:
Resource<T>
actix_web::Scope<T>
Vec<T>
Redirect
(A,)
(A, B)
(A, B, C)
(A, B, C, D)
(A, B, C, D, E)
(A, B, C, D, E, F)
(A, B, C, D, E, F, G)
(A, B, C, D, E, F, G, H)
(A, B, C, D, E, F, G, H, I)
and $N others
note: required by a bound in `App::<T>::service`
--> $WORKSPACE/actix-web/src/app.rs

View File

@ -29,14 +29,14 @@ error[E0277]: the trait bound `fn() -> impl std::future::Future<Output = String>
| required by a bound introduced by this call
|
= help: the following other types implement trait `HttpServiceFactory`:
Resource<T>
actix_web::Scope<T>
Vec<T>
Redirect
(A,)
(A, B)
(A, B, C)
(A, B, C, D)
(A, B, C, D, E)
(A, B, C, D, E, F)
(A, B, C, D, E, F, G)
(A, B, C, D, E, F, G, H)
(A, B, C, D, E, F, G, H, I)
and $N others
note: required by a bound in `App::<T>::service`
--> $WORKSPACE/actix-web/src/app.rs

View File

@ -15,14 +15,14 @@ error[E0277]: the trait bound `fn() -> impl std::future::Future<Output = String>
| required by a bound introduced by this call
|
= help: the following other types implement trait `HttpServiceFactory`:
Resource<T>
actix_web::Scope<T>
Vec<T>
Redirect
(A,)
(A, B)
(A, B, C)
(A, B, C, D)
(A, B, C, D, E)
(A, B, C, D, E, F)
(A, B, C, D, E, F, G)
(A, B, C, D, E, F, G, H)
(A, B, C, D, E, F, G, H, I)
and $N others
note: required by a bound in `App::<T>::service`
--> $WORKSPACE/actix-web/src/app.rs

View File

@ -2,6 +2,13 @@
## Unreleased
- Add `experimental-introspection` feature for retrieving configured route paths and HTTP methods.
- Minimum supported Rust version (MSRV) is now 1.82.
## 4.12.1
- Correct `actix-http` dependency requirement.
## 4.12.0
- `actix_web::response::builder::HttpResponseBuilder::streaming()` now sets `Content-Type` to `application/octet-stream` if `Content-Type` does not exist.

View File

@ -1,6 +1,6 @@
[package]
name = "actix-web"
version = "4.12.0"
version = "4.12.1"
description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust"
authors = ["Nikolay Kim <fafhrd91@gmail.com>", "Rob Ede <robjtede@icloud.com>"]
keywords = ["actix", "http", "web", "framework", "async"]
@ -125,6 +125,9 @@ 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"]
# Enabling the retrieval of metadata for initialized resources, including path and HTTP method.
experimental-introspection = []
[dependencies]
actix-codec = "0.5"
actix-macros = { version = "0.2.3", optional = true }
@ -134,7 +137,7 @@ actix-service = "2"
actix-tls = { version = "3.4", default-features = false, optional = true }
actix-utils = "3"
actix-http = "3.11"
actix-http = "3.11.2"
actix-router = { version = "0.5.3", default-features = false, features = ["http"] }
actix-web-codegen = { version = "4.3", optional = true, default-features = false }
@ -179,7 +182,7 @@ flate2 = "1.0.13"
futures-util = { version = "0.3.17", default-features = false, features = ["std"] }
rand = "0.9"
rcgen = "0.13"
rustls-pemfile = "2"
rustls-pki-types = "1.13.1"
serde = { version = "1", features = ["derive"] }
static_assertions = "1"
tls-openssl = { package = "openssl", version = "0.10.55" }

View File

@ -8,10 +8,10 @@
<!-- prettier-ignore-start -->
[![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.12.0)](https://docs.rs/actix-web/4.12.0)
[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.12.1)](https://docs.rs/actix-web/4.12.1)
![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)
[![Dependency Status](https://deps.rs/crate/actix-web/4.12.0/status.svg)](https://deps.rs/crate/actix-web/4.12.0)
[![Dependency Status](https://deps.rs/crate/actix-web/4.12.1/status.svg)](https://deps.rs/crate/actix-web/4.12.1)
<br />
[![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/graph/badge.svg?token=dSwOnp9QCv)](https://codecov.io/gh/actix/actix-web)
@ -78,23 +78,23 @@ async fn main() -> std::io::Result<()> {
### More Examples
- [Hello World](https://github.com/actix/examples/tree/mainasics/hello-world)
- [Basic Setup](https://github.com/actix/examples/tree/mainasics/basics)
- [Application State](https://github.com/actix/examples/tree/mainasics/state)
- [JSON Handling](https://github.com/actix/examples/tree/mainson/json)
- [Multipart Streams](https://github.com/actix/examples/tree/mainorms/multipart)
- [MongoDB Integration](https://github.com/actix/examples/tree/mainatabases/mongodb)
- [Diesel Integration](https://github.com/actix/examples/tree/mainatabases/diesel)
- [SQLite Integration](https://github.com/actix/examples/tree/mainatabases/sqlite)
- [Postgres Integration](https://github.com/actix/examples/tree/mainatabases/postgres)
- [Tera Templates](https://github.com/actix/examples/tree/mainemplating/tera)
- [Askama Templates](https://github.com/actix/examples/tree/mainemplating/askama)
- [HTTPS using Rustls](https://github.com/actix/examples/tree/mainttps-tls/rustls)
- [HTTPS using OpenSSL](https://github.com/actix/examples/tree/mainttps-tls/openssl)
- [Simple WebSocket](https://github.com/actix/examples/tree/mainebsockets)
- [WebSocket Chat](https://github.com/actix/examples/tree/mainebsockets/chat)
- [Hello World](https://github.com/actix/examples/tree/main/basics/hello-world)
- [Basic Setup](https://github.com/actix/examples/tree/main/basics/basics)
- [Application State](https://github.com/actix/examples/tree/main/basics/state)
- [JSON Handling](https://github.com/actix/examples/tree/main/json/json)
- [Multipart Streams](https://github.com/actix/examples/tree/main/forms/multipart)
- [MongoDB Integration](https://github.com/actix/examples/tree/main/databases/mongodb)
- [Diesel Integration](https://github.com/actix/examples/tree/main/databases/diesel)
- [SQLite Integration](https://github.com/actix/examples/tree/main/databases/sqlite)
- [Postgres Integration](https://github.com/actix/examples/tree/main/databases/postgres)
- [Tera Templates](https://github.com/actix/examples/tree/main/templating/tera)
- [Askama Templates](https://github.com/actix/examples/tree/main/templating/askama)
- [HTTPS using Rustls](https://github.com/actix/examples/tree/main/https-tls/rustls)
- [HTTPS using OpenSSL](https://github.com/actix/examples/tree/main/https-tls/openssl)
- [Simple WebSocket](https://github.com/actix/examples/tree/main/websockets)
- [WebSocket Chat](https://github.com/actix/examples/tree/main/websockets/chat)
You may consider checking out [this directory](https://github.com/actix/examples/tree/mainfor more examples.
You may consider checking out [this directory](https://github.com/actix/examples/tree/main) for more examples.
## Benchmarks

View File

@ -0,0 +1,255 @@
// Example showcasing the experimental introspection feature.
// Run with: `cargo run --features experimental-introspection --example introspection`
#[actix_web::main]
async fn main() -> std::io::Result<()> {
#[cfg(feature = "experimental-introspection")]
{
use actix_web::{dev::Service, guard, web, App, HttpResponse, HttpServer, Responder};
use serde::Deserialize;
// Initialize logging
env_logger::Builder::new()
.filter_level(log::LevelFilter::Debug)
.init();
// Custom guard to check if the Content-Type header is present.
struct ContentTypeGuard;
impl guard::Guard for ContentTypeGuard {
fn check(&self, req: &guard::GuardContext<'_>) -> bool {
req.head()
.headers()
.contains_key(actix_web::http::header::CONTENT_TYPE)
}
}
// Data structure for endpoints that receive JSON.
#[derive(Deserialize)]
struct UserInfo {
username: String,
age: u8,
}
// GET /introspection for JSON response
async fn introspection_handler_json(
tree: web::Data<actix_web::introspection::IntrospectionTree>,
) -> impl Responder {
let report = tree.report_as_json();
HttpResponse::Ok()
.content_type("application/json")
.body(report)
}
// GET /introspection for plain text response
async fn introspection_handler_text(
tree: web::Data<actix_web::introspection::IntrospectionTree>,
) -> impl Responder {
let report = tree.report_as_text();
HttpResponse::Ok().content_type("text/plain").body(report)
}
// GET /api/v1/item/{id} and GET /v1/item/{id}
#[actix_web::get("/item/{id}")]
async fn get_item(path: web::Path<u32>) -> impl Responder {
let id = path.into_inner();
HttpResponse::Ok().body(format!("Requested item with id: {}", id))
}
// POST /api/v1/info
#[actix_web::post("/info")]
async fn post_user_info(info: web::Json<UserInfo>) -> impl Responder {
HttpResponse::Ok().json(format!(
"User {} with age {} received",
info.username, info.age
))
}
// /api/v1/guarded
async fn guarded_handler() -> impl Responder {
HttpResponse::Ok().body("Passed the Content-Type guard!")
}
// GET /api/v2/hello
async fn hello_v2() -> impl Responder {
HttpResponse::Ok().body("Hello from API v2!")
}
// GET /admin/dashboard
async fn admin_dashboard() -> impl Responder {
HttpResponse::Ok().body("Welcome to the Admin Dashboard!")
}
// GET /admin/settings
async fn get_settings() -> impl Responder {
HttpResponse::Ok().body("Current settings: ...")
}
// POST /admin/settings
async fn update_settings() -> impl Responder {
HttpResponse::Ok().body("Settings have been updated!")
}
// GET and POST on /
async fn root_index() -> impl Responder {
HttpResponse::Ok().body("Welcome to the Root Endpoint!")
}
// Additional endpoints for /extra
fn extra_endpoints(cfg: &mut web::ServiceConfig) {
cfg.service(
web::scope("/extra")
.route(
"/ping",
web::get().to(|| async { HttpResponse::Ok().body("pong") }), // GET /extra/ping
)
.service(
web::resource("/multi")
.route(web::get().to(|| async {
HttpResponse::Ok().body("GET response from /extra/multi")
})) // GET /extra/multi
.route(web::post().to(|| async {
HttpResponse::Ok().body("POST response from /extra/multi")
})), // POST /extra/multi
)
.service(
web::scope("{entities_id:\\d+}")
.service(
web::scope("/secure")
.route(
"",
web::get().to(|| async {
HttpResponse::Ok()
.body("GET response from /extra/secure")
}),
) // GET /extra/{entities_id}/secure/
.route(
"/post",
web::post().to(|| async {
HttpResponse::Ok()
.body("POST response from /extra/secure")
}),
), // POST /extra/{entities_id}/secure/post
)
.wrap_fn(|req, srv| {
println!(
"Request to /extra/secure with id: {}",
req.match_info().get("entities_id").unwrap()
);
let fut = srv.call(req);
async move {
let res = fut.await?;
Ok(res)
}
}),
),
);
}
// Additional endpoints for /foo
fn other_endpoints(cfg: &mut web::ServiceConfig) {
cfg.service(
web::scope("/extra")
.route(
"/ping",
web::post()
.to(|| async { HttpResponse::Ok().body("post from /extra/ping") }), // POST /foo/extra/ping
)
.route(
"/ping",
web::delete()
.to(|| async { HttpResponse::Ok().body("delete from /extra/ping") }), // DELETE /foo/extra/ping
),
);
}
// Create the HTTP server with all the routes and handlers
let server = HttpServer::new(|| {
App::new()
// Get introspection report
// curl --location '127.0.0.1:8080/introspection' --header 'Accept: application/json'
// curl --location '127.0.0.1:8080/introspection' --header 'Accept: text/plain'
.service(
web::resource("/introspection")
.route(
web::get()
.guard(guard::Header("accept", "application/json"))
.to(introspection_handler_json),
)
.route(
web::get()
.guard(guard::Header("accept", "text/plain"))
.to(introspection_handler_text),
),
)
// API endpoints under /api
.service(
web::scope("/api")
// Endpoints under /api/v1
.service(
web::scope("/v1")
.service(get_item) // GET /api/v1/item/{id}
.service(post_user_info) // POST /api/v1/info
.route(
"/guarded",
web::route().guard(ContentTypeGuard).to(guarded_handler), // /api/v1/guarded
),
)
// Endpoints under /api/v2
.service(web::scope("/v2").route("/hello", web::get().to(hello_v2))), // GET /api/v2/hello
)
// Endpoints under /v1 (outside /api)
.service(web::scope("/v1").service(get_item)) // GET /v1/item/{id}
// Admin endpoints under /admin
.service(
web::scope("/admin")
.route("/dashboard", web::get().to(admin_dashboard)) // GET /admin/dashboard
.service(
web::resource("/settings")
.route(web::get().to(get_settings)) // GET /admin/settings
.route(web::post().to(update_settings)), // POST /admin/settings
),
)
// Root endpoints
.service(
web::resource("/")
.route(web::get().to(root_index)) // GET /
.route(web::post().to(root_index)), // POST /
)
// Endpoints under /bar
.service(web::scope("/bar").configure(extra_endpoints)) // /bar/extra/ping, /bar/extra/multi, etc.
// Endpoints under /foo
.service(web::scope("/foo").configure(other_endpoints)) // /foo/extra/ping with POST and DELETE
// Additional endpoints under /extra
.configure(extra_endpoints) // /extra/ping, /extra/multi, etc.
.configure(other_endpoints)
// Endpoint that rejects GET on /not_guard (allows other methods)
.route(
"/not_guard",
web::route()
.guard(guard::Not(guard::Get()))
.to(HttpResponse::MethodNotAllowed),
)
// Endpoint that requires GET with header or POST on /all_guard
.route(
"/all_guard",
web::route()
.guard(
guard::All(guard::Get())
.and(guard::Header("content-type", "plain/text"))
.and(guard::Any(guard::Post())),
)
.to(HttpResponse::MethodNotAllowed),
)
})
.workers(5)
.bind("127.0.0.1:8080")?;
server.run().await
}
#[cfg(not(feature = "experimental-introspection"))]
{
eprintln!("This example requires the 'experimental-introspection' feature to be enabled.");
std::process::exit(1);
}
}

View File

@ -0,0 +1,52 @@
// Example showcasing the experimental introspection feature with multiple App instances.
// Run with: `cargo run --features experimental-introspection --example introspection_multi_servers`
#[actix_web::main]
async fn main() -> std::io::Result<()> {
#[cfg(feature = "experimental-introspection")]
{
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
use futures_util::future;
async fn introspection_handler(
tree: web::Data<actix_web::introspection::IntrospectionTree>,
) -> impl Responder {
HttpResponse::Ok()
.content_type("text/plain")
.body(tree.report_as_text())
}
async fn index() -> impl Responder {
HttpResponse::Ok().body("Hello from app")
}
let srv1 = HttpServer::new(|| {
App::new()
.service(web::resource("/a").route(web::get().to(index)))
.service(
web::resource("/introspection").route(web::get().to(introspection_handler)),
)
})
.workers(8)
.bind("127.0.0.1:8081")?
.run();
let srv2 = HttpServer::new(|| {
App::new()
.service(web::resource("/b").route(web::get().to(index)))
.service(
web::resource("/introspection").route(web::get().to(introspection_handler)),
)
})
.workers(3)
.bind("127.0.0.1:8082")?
.run();
future::try_join(srv1, srv2).await?;
}
#[cfg(not(feature = "experimental-introspection"))]
{
eprintln!("This example requires the 'experimental-introspection' feature to be enabled.");
}
Ok(())
}

View File

@ -30,6 +30,8 @@ pub struct App<T> {
data_factories: Vec<FnDataFactory>,
external: Vec<ResourceDef>,
extensions: Extensions,
#[cfg(feature = "experimental-introspection")]
introspector: Rc<RefCell<crate::introspection::IntrospectionCollector>>,
}
impl App<AppEntry> {
@ -46,6 +48,10 @@ impl App<AppEntry> {
factory_ref,
external: Vec::new(),
extensions: Extensions::new(),
#[cfg(feature = "experimental-introspection")]
introspector: Rc::new(RefCell::new(
crate::introspection::IntrospectionCollector::new(),
)),
}
}
}
@ -366,6 +372,8 @@ where
factory_ref: self.factory_ref,
external: self.external,
extensions: self.extensions,
#[cfg(feature = "experimental-introspection")]
introspector: self.introspector,
}
}
@ -429,6 +437,8 @@ where
factory_ref: self.factory_ref,
external: self.external,
extensions: self.extensions,
#[cfg(feature = "experimental-introspection")]
introspector: self.introspector,
}
}
}
@ -453,6 +463,8 @@ where
default: self.default,
factory_ref: self.factory_ref,
extensions: RefCell::new(Some(self.extensions)),
#[cfg(feature = "experimental-introspection")]
introspector: Rc::clone(&self.introspector),
}
}
}

View File

@ -41,6 +41,8 @@ where
pub(crate) default: Option<Rc<BoxedHttpServiceFactory>>,
pub(crate) factory_ref: Rc<RefCell<Option<AppRoutingFactory>>>,
pub(crate) external: RefCell<Vec<ResourceDef>>,
#[cfg(feature = "experimental-introspection")]
pub(crate) introspector: Rc<RefCell<crate::introspection::IntrospectionCollector>>,
}
impl<T, B> ServiceFactory<Request> for AppInit<T, B>
@ -72,6 +74,10 @@ where
// create App config to pass to child services
let mut config = AppService::new(config, Rc::clone(&default));
#[cfg(feature = "experimental-introspection")]
{
config.introspector = Rc::clone(&self.introspector);
}
// register services
mem::take(&mut *self.services.borrow_mut())
@ -80,6 +86,9 @@ where
let mut rmap = ResourceMap::new(ResourceDef::prefix(""));
#[cfg(feature = "experimental-introspection")]
let (config, services, _) = config.into_services();
#[cfg(not(feature = "experimental-introspection"))]
let (config, services) = config.into_services();
// complete pipeline creation.
@ -110,6 +119,8 @@ where
// construct app service and middleware service factory future.
let endpoint_fut = self.endpoint.new_service(());
#[cfg(feature = "experimental-introspection")]
let introspector = Rc::clone(&self.introspector);
// take extensions or create new one as app data container.
let mut app_data = self.extensions.borrow_mut().take().unwrap_or_default();
@ -130,6 +141,12 @@ where
factory.create(&mut app_data);
}
#[cfg(feature = "experimental-introspection")]
{
let tree = introspector.borrow_mut().finalize();
app_data.insert(crate::web::Data::new(tree));
}
Ok(AppInitService {
service,
app_data: Rc::new(app_data),

View File

@ -30,6 +30,11 @@ pub struct AppService {
Option<Guards>,
Option<Rc<ResourceMap>>,
)>,
#[cfg(feature = "experimental-introspection")]
pub current_prefix: String,
#[cfg(feature = "experimental-introspection")]
pub(crate) introspector:
std::rc::Rc<std::cell::RefCell<crate::introspection::IntrospectionCollector>>,
}
impl AppService {
@ -40,6 +45,12 @@ impl AppService {
default,
root: true,
services: Vec::new(),
#[cfg(feature = "experimental-introspection")]
current_prefix: "".to_string(),
#[cfg(feature = "experimental-introspection")]
introspector: std::rc::Rc::new(std::cell::RefCell::new(
crate::introspection::IntrospectionCollector::new(),
)),
}
}
@ -49,6 +60,24 @@ impl AppService {
}
#[allow(clippy::type_complexity)]
#[cfg(feature = "experimental-introspection")]
pub(crate) fn into_services(
self,
) -> (
AppConfig,
Vec<(
ResourceDef,
BoxedHttpServiceFactory,
Option<Guards>,
Option<Rc<ResourceMap>>,
)>,
std::rc::Rc<std::cell::RefCell<crate::introspection::IntrospectionCollector>>,
) {
(self.config, self.services, self.introspector)
}
#[allow(clippy::type_complexity)]
#[cfg(not(feature = "experimental-introspection"))]
pub(crate) fn into_services(
self,
) -> (
@ -71,6 +100,10 @@ impl AppService {
default: Rc::clone(&self.default),
services: Vec::new(),
root: false,
#[cfg(feature = "experimental-introspection")]
current_prefix: self.current_prefix.clone(),
#[cfg(feature = "experimental-introspection")]
introspector: std::rc::Rc::clone(&self.introspector),
}
}
@ -101,9 +134,70 @@ impl AppService {
InitError = (),
> + 'static,
{
#[cfg(feature = "experimental-introspection")]
{
use std::borrow::Borrow;
// Build the full path for introspection
let pat = rdef.pattern().unwrap_or("").to_string();
let full_path = if self.current_prefix.is_empty() {
pat.clone()
} else {
format!(
"{}/{}",
self.current_prefix.trim_end_matches('/'),
pat.trim_start_matches('/')
)
};
// Extract methods and guards for introspection
let guard_list: &[Box<dyn Guard>] = guards.borrow().as_ref().map_or(&[], |v| &v[..]);
let methods = guard_list
.iter()
.flat_map(|g| g.details().unwrap_or_default())
.flat_map(|d| {
if let crate::guard::GuardDetail::HttpMethods(v) = d {
v.into_iter()
.filter_map(|s| s.parse().ok())
.collect::<Vec<_>>()
} else {
Vec::new()
}
})
.collect::<Vec<_>>();
let guard_names = guard_list
.iter()
.map(|g| g.name().to_string())
.collect::<Vec<_>>();
// Determine if the registered service is a resource
let is_resource = rdef.pattern().is_some();
self.introspector.borrow_mut().register_pattern_detail(
full_path,
methods,
guard_names,
is_resource,
);
}
self.services
.push((rdef, boxed::factory(factory.into_factory()), guards, nested));
}
/// Update the current path prefix.
#[cfg(feature = "experimental-introspection")]
pub(crate) fn update_prefix(&mut self, prefix: &str) {
self.current_prefix = if self.current_prefix.is_empty() {
prefix.to_string()
} else {
format!(
"{}/{}",
self.current_prefix.trim_end_matches('/'),
prefix.trim_start_matches('/')
)
};
}
}
/// Application connection config.

View File

@ -11,7 +11,7 @@
//! or handler. This interface is defined by the [`Guard`] trait.
//!
//! Commonly-used guards are provided in this module as well as a way of creating a guard from a
//! closure ([`fn_guard`]). The [`Not`], [`Any`], and [`All`] guards are noteworthy, as they can be
//! closure ([`fn_guard`]). The [`Not`], [`Any()`], and [`All()`] guards are noteworthy, as they can be
//! used to compose other guards in a more flexible and semantic way than calling `.guard(...)` on
//! services multiple times (which might have different combining behavior than you want).
//!
@ -66,6 +66,17 @@ pub use self::{
host::{Host, HostGuard},
};
/// Enum to encapsulate various introspection details of a Guard.
#[derive(Debug, Clone)]
pub enum GuardDetail {
/// Detail associated with HTTP methods.
HttpMethods(Vec<String>),
/// Detail associated with headers (header, value).
Headers(Vec<(String, String)>),
/// Generic detail.
Generic(String),
}
/// Provides access to request parts that are useful during routing.
#[derive(Debug)]
pub struct GuardContext<'a> {
@ -124,12 +135,28 @@ impl<'a> GuardContext<'a> {
pub trait Guard {
/// Returns true if predicate condition is met for a given request.
fn check(&self, ctx: &GuardContext<'_>) -> bool;
/// Returns a nominal representation of the guard.
fn name(&self) -> String {
std::any::type_name::<Self>().to_string()
}
/// Returns detailed introspection information.
fn details(&self) -> Option<Vec<GuardDetail>> {
None
}
}
impl Guard for Rc<dyn Guard> {
fn check(&self, ctx: &GuardContext<'_>) -> bool {
(**self).check(ctx)
}
fn name(&self) -> String {
(**self).name()
}
fn details(&self) -> Option<Vec<GuardDetail>> {
(**self).details()
}
}
/// Creates a guard using the given function.
@ -195,7 +222,7 @@ pub fn Any<F: Guard + 'static>(guard: F) -> AnyGuard {
///
/// That is, only one contained guard needs to match in order for the aggregate guard to match.
///
/// Construct an `AnyGuard` using [`Any`].
/// Construct an `AnyGuard` using [`Any()`].
pub struct AnyGuard {
guards: Vec<Box<dyn Guard>>,
}
@ -219,6 +246,24 @@ impl Guard for AnyGuard {
false
}
fn name(&self) -> String {
format!(
"AnyGuard({})",
self.guards
.iter()
.map(|g| g.name())
.collect::<Vec<_>>()
.join(", ")
)
}
fn details(&self) -> Option<Vec<GuardDetail>> {
Some(
self.guards
.iter()
.flat_map(|g| g.details().unwrap_or_default())
.collect(),
)
}
}
/// Creates a guard that matches if all added guards match.
@ -247,7 +292,7 @@ pub fn All<F: Guard + 'static>(guard: F) -> AllGuard {
///
/// That is, **all** contained guard needs to match in order for the aggregate guard to match.
///
/// Construct an `AllGuard` using [`All`].
/// Construct an `AllGuard` using [`All()`].
pub struct AllGuard {
guards: Vec<Box<dyn Guard>>,
}
@ -271,6 +316,24 @@ impl Guard for AllGuard {
true
}
fn name(&self) -> String {
format!(
"AllGuard({})",
self.guards
.iter()
.map(|g| g.name())
.collect::<Vec<_>>()
.join(", ")
)
}
fn details(&self) -> Option<Vec<GuardDetail>> {
Some(
self.guards
.iter()
.flat_map(|g| g.details().unwrap_or_default())
.collect(),
)
}
}
/// Wraps a guard and inverts the outcome of its `Guard` implementation.
@ -291,6 +354,12 @@ impl<G: Guard> Guard for Not<G> {
fn check(&self, ctx: &GuardContext<'_>) -> bool {
!self.0.check(ctx)
}
fn name(&self) -> String {
format!("Not({})", self.0.name())
}
fn details(&self) -> Option<Vec<GuardDetail>> {
self.0.details()
}
}
/// Creates a guard that matches a specified HTTP method.
@ -320,6 +389,12 @@ impl Guard for MethodGuard {
ctx.head().method == self.0
}
fn name(&self) -> String {
self.0.to_string()
}
fn details(&self) -> Option<Vec<GuardDetail>> {
Some(vec![GuardDetail::HttpMethods(vec![self.0.to_string()])])
}
}
macro_rules! method_guard {
@ -382,6 +457,15 @@ impl Guard for HeaderGuard {
false
}
fn name(&self) -> String {
format!("Header({}, {})", self.0, self.1.to_str().unwrap_or(""))
}
fn details(&self) -> Option<Vec<GuardDetail>> {
Some(vec![GuardDetail::Headers(vec![(
self.0.to_string(),
self.1.to_str().unwrap_or("").to_string(),
)])])
}
}
#[cfg(test)]

View File

@ -0,0 +1,209 @@
use std::{collections::HashMap, fmt::Write as FmtWrite};
use serde::Serialize;
use crate::http::Method;
#[derive(Clone)]
pub struct RouteDetail {
methods: Vec<Method>,
guards: Vec<String>,
is_resource: bool,
}
#[derive(Debug, Clone, Copy)]
pub enum ResourceType {
App,
Scope,
Resource,
}
#[derive(Debug, Clone)]
pub struct IntrospectionNode {
pub kind: ResourceType,
pub pattern: String,
pub full_path: String,
pub methods: Vec<Method>,
pub guards: Vec<String>,
pub children: Vec<IntrospectionNode>,
}
#[derive(Debug, Clone, Serialize)]
pub struct IntrospectionReportItem {
pub full_path: String,
pub methods: Vec<String>,
pub guards: Vec<String>,
}
impl IntrospectionNode {
pub fn new(kind: ResourceType, pattern: String, full_path: String) -> Self {
IntrospectionNode {
kind,
pattern,
full_path,
methods: Vec::new(),
guards: Vec::new(),
children: Vec::new(),
}
}
}
impl From<&IntrospectionNode> for Vec<IntrospectionReportItem> {
fn from(node: &IntrospectionNode) -> Self {
fn collect_report_items(
node: &IntrospectionNode,
parent_path: &str,
report_items: &mut Vec<IntrospectionReportItem>,
) {
let full_path = if parent_path.is_empty() {
node.pattern.clone()
} else {
format!(
"{}/{}",
parent_path.trim_end_matches('/'),
node.pattern.trim_start_matches('/')
)
};
if !node.methods.is_empty() || !node.guards.is_empty() {
let filtered_guards: Vec<String> = node
.guards
.iter()
.filter(|guard| !node.methods.iter().any(|m| m.to_string() == **guard))
.cloned()
.collect();
report_items.push(IntrospectionReportItem {
full_path: full_path.clone(),
methods: node.methods.iter().map(|m| m.to_string()).collect(),
guards: filtered_guards,
});
}
for child in &node.children {
collect_report_items(child, &full_path, report_items);
}
}
let mut report_items = Vec::new();
collect_report_items(node, "/", &mut report_items);
report_items
}
}
#[derive(Clone, Default)]
pub struct IntrospectionCollector {
details: HashMap<String, RouteDetail>,
}
impl IntrospectionCollector {
pub fn new() -> Self {
Self {
details: HashMap::new(),
}
}
pub fn register_pattern_detail(
&mut self,
full_path: String,
methods: Vec<Method>,
guards: Vec<String>,
is_resource: bool,
) {
self.details
.entry(full_path)
.and_modify(|d| {
update_unique(&mut d.methods, &methods);
update_unique(&mut d.guards, &guards);
if !d.is_resource && is_resource {
d.is_resource = true;
}
})
.or_insert(RouteDetail {
methods,
guards,
is_resource,
});
}
pub fn finalize(&mut self) -> IntrospectionTree {
let detail_registry = std::mem::take(&mut self.details);
let mut root = IntrospectionNode::new(ResourceType::App, "".into(), "".into());
for (full_path, _) in detail_registry.iter() {
let parts: Vec<&str> = full_path.split('/').collect();
let mut current_node = &mut root;
for (i, part) in parts.iter().enumerate() {
let existing_child_index = current_node
.children
.iter()
.position(|n| n.pattern == *part);
let child_index = if let Some(idx) = existing_child_index {
idx
} else {
let child_full_path = parts[..=i].join("/");
let kind = if detail_registry
.get(&child_full_path)
.is_some_and(|d| d.is_resource)
{
ResourceType::Resource
} else {
ResourceType::Scope
};
let new_node = IntrospectionNode::new(kind, part.to_string(), child_full_path);
current_node.children.push(new_node);
current_node.children.len() - 1
};
current_node = &mut current_node.children[child_index];
if let ResourceType::Resource = current_node.kind {
if let Some(detail) = detail_registry.get(&current_node.full_path) {
update_unique(&mut current_node.methods, &detail.methods);
update_unique(&mut current_node.guards, &detail.guards);
}
}
}
}
IntrospectionTree { root }
}
}
#[derive(Clone)]
pub struct IntrospectionTree {
pub root: IntrospectionNode,
}
impl IntrospectionTree {
pub fn report_as_text(&self) -> String {
let report_items: Vec<IntrospectionReportItem> = (&self.root).into();
let mut buf = String::new();
for item in report_items {
writeln!(
buf,
"{} => Methods: {:?} | Guards: {:?}",
item.full_path, item.methods, item.guards
)
.unwrap();
}
buf
}
pub fn report_as_json(&self) -> String {
let report_items: Vec<IntrospectionReportItem> = (&self.root).into();
serde_json::to_string_pretty(&report_items).unwrap()
}
}
fn update_unique<T: Clone + PartialEq>(existing: &mut Vec<T>, new_items: &[T]) {
for item in new_items {
if !existing.contains(item) {
existing.push(item.clone());
}
}
}

View File

@ -108,6 +108,9 @@ mod thin_data;
pub(crate) mod types;
pub mod web;
#[cfg(feature = "experimental-introspection")]
pub mod introspection;
#[doc(inline)]
pub use crate::error::Result;
pub use crate::{

View File

@ -417,6 +417,8 @@ where
B: MessageBody + 'static,
{
fn register(mut self, config: &mut AppService) {
let routes = std::mem::take(&mut self.routes);
let guards = if self.guards.is_empty() {
None
} else {
@ -428,13 +430,55 @@ where
} else {
ResourceDef::new(self.rdef.clone())
};
#[cfg(feature = "experimental-introspection")]
{
use crate::http::Method;
let guards_routes = routes.iter().map(|r| r.guards()).collect::<Vec<_>>();
let pat = rdef.pattern().unwrap_or("").to_string();
let full_path = if config.current_prefix.is_empty() {
pat.clone()
} else {
format!(
"{}/{}",
config.current_prefix.trim_end_matches('/'),
pat.trim_start_matches('/')
)
};
for route_guards in guards_routes {
// Log the guards and methods for introspection
let guard_names = route_guards.iter().map(|g| g.name()).collect::<Vec<_>>();
let methods = route_guards
.iter()
.flat_map(|g| g.details().unwrap_or_default())
.flat_map(|d| {
if let crate::guard::GuardDetail::HttpMethods(v) = d {
v.into_iter()
.filter_map(|s| s.parse::<Method>().ok())
.collect::<Vec<_>>()
} else {
Vec::new()
}
})
.collect::<Vec<_>>();
config.introspector.borrow_mut().register_pattern_detail(
full_path.clone(),
methods,
guard_names,
true,
);
}
}
if let Some(ref name) = self.name {
rdef.set_name(name);
}
*self.factory_ref.borrow_mut() = Some(ResourceFactory {
routes: self.routes,
routes,
default: self.default,
});

View File

@ -159,6 +159,11 @@ impl Route {
self
}
#[cfg(feature = "experimental-introspection")]
pub(crate) fn guards(&self) -> &Vec<Box<dyn Guard>> {
&self.guards
}
/// Set handler function, use request extractors for parameters.
///
/// # Examples

View File

@ -384,6 +384,11 @@ where
// register nested services
let mut cfg = config.clone_config();
// Update the prefix for the nested scope
#[cfg(feature = "experimental-introspection")]
cfg.update_prefix(&self.rdef);
self.services
.into_iter()
.for_each(|mut srv| srv.register(&mut cfg));

View File

@ -688,30 +688,20 @@ async fn test_brotli_encoding_large_openssl() {
#[cfg(feature = "rustls-0_23")]
mod plus_rustls {
use std::io::BufReader;
use rustls::{pki_types::PrivateKeyDer, ServerConfig as RustlsServerConfig};
use rustls_pemfile::{certs, pkcs8_private_keys};
use rustls_pki_types::PrivatePkcs8KeyDer;
use super::*;
fn tls_config() -> RustlsServerConfig {
let rcgen::CertifiedKey { cert, key_pair } =
rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
let cert_file = cert.pem();
let key_file = key_pair.serialize_pem();
let cert_file = &mut BufReader::new(cert_file.as_bytes());
let key_file = &mut BufReader::new(key_file.as_bytes());
let cert_chain = certs(cert_file).collect::<Result<Vec<_>, _>>().unwrap();
let mut keys = pkcs8_private_keys(key_file)
.collect::<Result<Vec<_>, _>>()
.unwrap();
let cert_chain = vec![cert.der().clone()];
let key_der = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(key_pair.serialize_der()));
RustlsServerConfig::builder()
.with_no_client_auth()
.with_single_cert(cert_chain, PrivateKeyDer::Pkcs8(keys.remove(0)))
.with_single_cert(cert_chain, key_der)
.unwrap()
}

View File

@ -2,6 +2,8 @@
## Unreleased
- Minimum supported Rust version (MSRV) is now 1.82.
## 3.8.1
- Fix a bug where `GO_AWAY` errors did not stop connections from returning to the pool.

View File

@ -149,7 +149,7 @@ flate2 = "1.0.13"
futures-util = { version = "0.3.17", default-features = false }
static_assertions = "1.1"
rcgen = "0.13"
rustls-pemfile = "2"
rustls-pki-types = "1.13.1"
tokio = { version = "1.38.2", features = ["rt-multi-thread", "macros"] }
zstd = "0.13"
tls-rustls-0_23 = { package = "rustls", version = "0.23" } # add rustls 0.23 with default features to make aws_lc_rs work in tests

View File

@ -309,10 +309,7 @@ impl ClientRequest {
/// Freeze request builder and construct `FrozenClientRequest`,
/// which could be used for sending same request multiple times.
pub fn freeze(self) -> Result<FrozenClientRequest, FreezeRequestError> {
let slf = match self.prep_for_sending() {
Ok(slf) => slf,
Err(err) => return Err(err.into()),
};
let slf = self.prep_for_sending()?;
let request = FrozenClientRequest {
head: Rc::new(slf.head),

View File

@ -2,12 +2,9 @@
extern crate tls_rustls_0_23 as rustls;
use std::{
io::BufReader,
sync::{
atomic::{AtomicUsize, Ordering},
Arc,
},
use std::sync::{
atomic::{AtomicUsize, Ordering},
Arc,
};
use actix_http::HttpService;
@ -16,29 +13,18 @@ use actix_service::{fn_service, map_config, ServiceFactoryExt};
use actix_tls::connect::rustls_0_23::webpki_roots_cert_store;
use actix_utils::future::ok;
use actix_web::{dev::AppConfig, http::Version, web, App, HttpResponse};
use rustls::{
pki_types::{CertificateDer, PrivateKeyDer, ServerName},
ClientConfig, ServerConfig,
};
use rustls_pemfile::{certs, pkcs8_private_keys};
use rustls::{pki_types::ServerName, ClientConfig, ServerConfig};
use rustls_pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
fn tls_config() -> ServerConfig {
let rcgen::CertifiedKey { cert, key_pair } =
rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
let cert_file = cert.pem();
let key_file = key_pair.serialize_pem();
let cert_file = &mut BufReader::new(cert_file.as_bytes());
let key_file = &mut BufReader::new(key_file.as_bytes());
let cert_chain = certs(cert_file).collect::<Result<Vec<_>, _>>().unwrap();
let mut keys = pkcs8_private_keys(key_file)
.collect::<Result<Vec<_>, _>>()
.unwrap();
let cert_chain = vec![cert.der().clone()];
let key_der = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(key_pair.serialize_der()));
ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(cert_chain, PrivateKeyDer::Pkcs8(keys.remove(0)))
.with_single_cert(cert_chain, key_der)
.unwrap()
}

View File

@ -12,14 +12,7 @@ fmt:
# Downgrade dependencies necessary to run MSRV checks/tests.
[private]
downgrade-for-msrv:
cargo {{ toolchain }} update -p=divan --precise=0.1.15 # next ver: 1.80.0
cargo {{ toolchain }} update -p=rayon --precise=1.10.0 # next ver: 1.80.0
cargo {{ toolchain }} update -p=rayon-core --precise=1.12.1 # next ver: 1.80.0
cargo {{ toolchain }} update -p=half --precise=2.4.1 # next ver: 1.81.0
cargo {{ toolchain }} update -p=idna_adapter --precise=1.2.0 # next ver: 1.82.0
cargo {{ toolchain }} update -p=litemap --precise=0.7.4 # next ver: 1.81.0
cargo {{ toolchain }} update -p=zerofrom --precise=0.1.5 # next ver: 1.81.0
cargo {{ toolchain }} update -p=time --precise=0.3.41 # next ver: 1.81.0
# no downgrades currently needed
msrv := ```
cargo metadata --format-version=1 \