1
0
Fork 0
mirror of https://github.com/actix/actix-web.git synced 2024-05-20 09:18:26 +00:00

Compare commits

...

57 commits

Author SHA1 Message Date
Rob Ede fdff3775a8
ci: use mold linker (#3370) 2024-05-19 20:24:33 +01:00
Rob Ede b342b8fc82
chore(actix-router): prepare release 0.5.3 2024-05-19 12:09:46 +01:00
Rob Ede 804a344565
ci: limit cargo hack concurrency 2024-05-19 12:06:20 +01:00
Rob Ede acb740584c
fix: correct aws rustls v0.23 feature gating 2024-05-19 11:55:12 +01:00
Rob Ede 9a437fe835
chore(awc): prepare release 3.5.0 2024-05-19 10:16:16 +01:00
Rob Ede 59115bca49
chore(actix-web): prepare release 4.6.0 2024-05-19 10:15:48 +01:00
Rob Ede fe7268487a
chore(actix-http): prepare release 3.7.0 2024-05-19 10:14:30 +01:00
Rob Ede e8262da138
chore: update rcgen to 0.13 2024-05-19 10:12:32 +01:00
Rob Ede 18e02b83d5
docs: fix middleware docs warning 2024-05-18 20:35:12 +01:00
asonix 2e63ff5928
actix-web: Add rustls 0.23 (#3363)
* Fix type confusion in some scenarios

When the feature for rustls 0.22 is enabled, and rustls 0.23 is also
present in a project, there suddently exist multiple paths for errors
when building middleware chains due to the use of two consecutive `?`
operators without specifying the intermediate error type.

This commit addresses the issue by removing the first `?`, so that the
first error type will always be known, and the second `?` always has a
well defined implementation.

* Add CHANGES entry about type confusion

* actix-http: add rustls 0.23 support

* actix-http: update ws example, tests for rustls 0.23

* actix-http: add rustls 0.23 to changelog

* Update comments to mention 0.23 instead of 0.22

* awc: add rustls 0.23 support

This also fixes certificate lookup when native-roots is enabled for rustls 0.22.

* awc: update changelog for rustls 0.23

* awc: Add base rustls-0_23 feature without roots to better enable custom config

* actix-test: add rustls-0.23

* actix-test: add rustls 0.23 to changelog

* awc: update changelog with rustls 0.23 tweaks

* actix-web: add rustls 0.23

* Add rustls-0_23 to CI

* Update tls_rustls.rs

* review nits

* review nits part 2

* fix doc test

---------

Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-05-18 19:05:58 +00:00
Raphael C 48d7adb7bf
Documentation for actix multipart (#3344)
example for actix-multipart readme & crate docs

Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-05-18 19:02:00 +00:00
Matt Palmer 0a2788d662
actix-test: re-export types from awc (#3349)
This allows us to pass these types around in functions, without having
to add `awc` as a direct (dev-)dependency.

Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-05-18 18:57:35 +00:00
asonix 2d035c066e
actix-http: Add rustls 0.23 (#3361)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-05-18 19:22:53 +01:00
dependabot[bot] fff45b28f4
build(deps): update brotli requirement from 3.3.3 to 6.0.0 (#3353)
* build(deps): update brotli requirement from 3.3.3 to 6.0.0

Updates the requirements on [brotli](https://github.com/dropbox/rust-brotli) to permit the latest version.
- [Release notes](https://github.com/dropbox/rust-brotli/releases)
- [Commits](https://github.com/dropbox/rust-brotli/compare/3.3.3...6.0.0)

---
updated-dependencies:
- dependency-name: brotli
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* docs: update changelogs

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-05-14 08:58:05 +00:00
dependabot[bot] c20603fc83
build(deps): bump taiki-e/install-action from 2.33.16 to 2.33.22 (#3364)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.33.16 to 2.33.22.
- [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.16...v2.33.22)

---
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-14 05:50:32 +00:00
asonix 33c47c0ba9
Fix type confusion in some scenarios (#3348)
* Fix type confusion in some scenarios

When the feature for rustls 0.22 is enabled, and rustls 0.23 is also
present in a project, there suddently exist multiple paths for errors
when building middleware chains due to the use of two consecutive `?`
operators without specifying the intermediate error type.

This commit addresses the issue by removing the first `?`, so that the
first error type will always be known, and the second `?` always has a
well defined implementation.

* Add CHANGES entry about type confusion

---------

Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-05-14 05:45:35 +00:00
Rob Ede 3c9a930bd1
ci: rely more on justfiles (#3365) 2024-05-14 06:30:58 +01:00
asonix 44f502e050
awc: gate TlsConnectorService behind any feature that uses it (#3350) 2024-05-14 05:57:58 +01:00
Rob Ede c1a6388614
refactor: address clippy warnings 2024-05-06 06:03:44 +01:00
dependabot[bot] bb65628de5
build(deps): bump taiki-e/install-action from 2.33.12 to 2.33.16 (#3355)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-06 03:28:35 +01:00
dependabot[bot] e4b9d17355
build(deps): bump codecov/codecov-action from 4.3.0 to 4.3.1 (#3354)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-06 03:27:31 +01:00
dependabot[bot] babac131d4
build(deps): bump taiki-e/install-action from 2.32.17 to 2.33.12 (#3347)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.32.17 to 2.33.12.
- [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.32.17...v2.33.12)

---
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-05-02 02:39:28 +00:00
dependabot[bot] 7f15a95d8e
build(deps): bump JamesIves/github-pages-deploy-action from 4.5.0 to 4.6.0 (#3339)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-02 03:22:59 +01:00
Rob Ede 7fc73d58a9
ci: fix public api 2024-05-02 03:22:20 +01:00
dependabot[bot] ba7fd048b6
build(deps): bump codecov/codecov-action from 4.2.0 to 4.3.0 (#3331)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.2.0 to 4.3.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.2.0...v4.3.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-04-15 01:02:44 +00:00
dependabot[bot] d98938b125
build(deps): bump taiki-e/install-action from 2.32.9 to 2.32.17 (#3332)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.32.9 to 2.32.17.
- [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.32.9...v2.32.17)

---
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-04-15 01:02:30 +00:00
dependabot[bot] 5a5486b484
build(deps): bump taiki-e/install-action from 2.32.0 to 2.32.9 (#3325)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.32.0 to 2.32.9.
- [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.32.0...v2.32.9)

---
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-04-08 07:19:03 +00:00
dependabot[bot] 76b2b2734b
build(deps): bump codecov/codecov-action from 4.1.1 to 4.2.0 (#3326)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.1.1 to 4.2.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.1.1...v4.2.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-04-08 07:17:45 +00:00
dependabot[bot] ccfa8d3817
build(deps): bump codecov/codecov-action from 4.1.0 to 4.1.1 (#3321)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.1.0 to 4.1.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.1.0...v4.1.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-04-01 01:41:43 +00:00
dependabot[bot] 09851f4a54
build(deps): bump taiki-e/install-action from 2.29.7 to 2.32.0 (#3322)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.29.7 to 2.32.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.29.7...v2.32.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-04-01 01:38:30 +00:00
dependabot[bot] db76ad0f61
build(deps): bump taiki-e/install-action from 2.28.16 to 2.29.7 (#3316)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-25 15:03:40 +00:00
dependabot[bot] 0383f4bdd1
build(deps): bump taiki-e/install-action from 2.28.10 to 2.28.16 (#3312)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.28.10 to 2.28.16.
- [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.28.10...v2.28.16)

---
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-03-18 02:47:10 +00:00
dependabot[bot] 9c3b4c61f7
build(deps): bump taiki-e/install-action from 2.28.0 to 2.28.10 (#3305)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-16 11:14:22 +00:00
LoveSy 52b0d5fbf9
CI: Free space before test (#3303) 2024-03-11 15:34:04 +00:00
Nelson Dominguez ba7bddeadc
docs(actix-web): add missing 'that' to doc comments for Compress middleware (#3304)
docs(actix-web): add missing 'that' in doc comments for Compress middleware
2024-03-06 00:17:18 +00:00
dependabot[bot] d2150a3312
build(deps): bump taiki-e/install-action from 2.27.9 to 2.28.0 (#3301)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.27.9 to 2.28.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.27.9...v2.28.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-03-04 02:18:50 +00:00
dependabot[bot] 58dd00bccf
build(deps): bump codecov/codecov-action from 4.0.2 to 4.1.0 (#3302)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.0.2 to 4.1.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.0.2...v4.1.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-03-04 02:18:38 +00:00
Rob Ede a4df623b0c
chore: bump env_logger to v0.11 2024-03-03 23:43:54 +00:00
Rob Ede 49020e79ae
chore: update base64 to v0.22 2024-03-03 22:18:29 +00:00
LoveSy c10f05a867
Add unicode feature to switch between regex and regex-lite crates as a trade-off between full unicode support and binary size (#3291)
* - Add `unicode` feature to switch between `regex` and `regex-lite`

as a trade-off between full unicode support and binary size.

* Update CHANGES.md

* Update CHANGES.md

* refactor: move regexset code selection to own module

* docs: add docs within RegexSet module

* chore: restore manifests

* test: ensure all actix-router codepaths are tested

---------

Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-03-03 15:50:16 +00:00
dependabot[bot] 994ea45d91
build(deps): bump codecov/codecov-action from 4.0.1 to 4.0.2 (#3296)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.0.1 to 4.0.2.
- [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.0.1...v4.0.2)

---
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>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-03-02 18:40:38 +00:00
dependabot[bot] f8a0f3e188
build(deps): bump taiki-e/install-action from 2.27.2 to 2.27.9 (#3297)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.27.2 to 2.27.9.
- [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.27.2...v2.27.9)

---
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>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-03-02 18:24:18 +00:00
Rob Ede 7f0504e32b
chore: temp allow #[allow(non_local_definitions)] 2024-03-01 18:11:30 +00:00
dependabot[bot] 8c31d137aa
build(deps): bump taiki-e/install-action from 2.26.18 to 2.27.2 (#3294)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-02-19 12:31:10 +00:00
dependabot[bot] 289f749e9f
build(deps): bump taiki-e/install-action from 2.26.12 to 2.26.18 (#3289)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-16 13:38:38 +00:00
Rob Ede 82f8ddc38f
feat: multipart testing utilities (#3288) 2024-02-14 22:22:07 +00:00
Rob Ede 3819767fa0
test: fix trybuild tests 2024-02-13 02:14:03 +00:00
Rob Ede 9ce5e33b72
ci: workaround clap MSRV in dev deps 2024-02-13 01:42:54 +00:00
Rob Ede 1e08ebabf9
build: bump MSRV to 1.72 2024-02-13 01:24:34 +00:00
Rob Ede 022b052bd9
chore: clippy 2024-02-12 23:02:45 +00:00
Rob Ede 1e2ef6f92f
perf: remove unnecessary allocation when writing http dates (#3261) 2024-02-07 03:47:30 +00:00
dependabot[bot] 7e4e12b0aa
build(deps): bump taiki-e/install-action from 2.26.8 to 2.26.12 (#3280)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-05 14:38:02 +00:00
dependabot[bot] 373d4ca970
build(deps): bump codecov/codecov-action from 4.0.0 to 4.0.1 (#3279)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-05 01:27:57 +00:00
Rob Ede e518170a30
test: fix test_server 2024-02-04 03:40:58 +00:00
Rob Ede f5f6132f94
test: update rustls for test_server 2024-02-04 03:30:16 +00:00
Rob Ede d9b31b80ac
fix: standardize body stream error reporting 2024-02-04 03:11:48 +00:00
Rob Ede 2b8c528e54
chore(actix-web): prepare release 4.5.1 2024-02-04 01:22:36 +00:00
126 changed files with 1527 additions and 459 deletions

View file

@ -6,9 +6,5 @@ lint-all = "clippy --workspace --all-features --all-targets -- -Dclippy::todo"
ci-check-min = "hack --workspace check --no-default-features"
ci-check-default = "hack --workspace check"
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-linux="hack --workspace --feature-powerset --skip=__compress check"
# testing
ci-doctest-default = "test --workspace --doc --no-fail-fast -- --nocapture"
ci-doctest = "test --workspace --all-features --doc --no-fail-fast -- --nocapture"
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 --depth=4 --skip=__compress check"

View file

@ -30,6 +30,10 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Install nasm
if: matrix.target.os == 'windows-latest'
uses: ilammy/setup-nasm@v1.5.1
- name: Install OpenSSL
if: matrix.target.os == 'windows-latest'
shell: bash
@ -44,10 +48,10 @@ jobs:
with:
toolchain: ${{ matrix.version.version }}
- name: Install cargo-hack and cargo-ci-cache-clean
uses: taiki-e/install-action@v2.26.8
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
uses: taiki-e/install-action@v2.33.22
with:
tool: cargo-hack,cargo-ci-cache-clean
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
- name: check minimal
run: cargo ci-check-min
@ -57,19 +61,7 @@ jobs:
- name: tests
timeout-minutes: 60
shell: bash
run: |
set -e
cargo test --lib --tests -p=actix-router --all-features
cargo test --lib --tests -p=actix-http --all-features
cargo test --lib --tests -p=actix-web --features=rustls-0_20,rustls-0_21,rustls-0_22,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls
cargo test --lib --tests -p=actix-web-codegen --all-features
cargo test --lib --tests -p=awc --all-features
cargo test --lib --tests -p=actix-http-test --all-features
cargo test --lib --tests -p=actix-test --all-features
cargo test --lib --tests -p=actix-files
cargo test --lib --tests -p=actix-multipart --all-features
cargo test --lib --tests -p=actix-web-actors --all-features
run: just test
- name: CI cache clean
run: cargo-ci-cache-clean
@ -81,11 +73,14 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Free Disk Space
run: ./scripts/free-disk-space.sh
- name: Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
- name: Install cargo-hack
uses: taiki-e/install-action@v2.26.8
uses: taiki-e/install-action@v2.33.22
with:
tool: cargo-hack
@ -94,21 +89,3 @@ jobs:
- name: check feature combinations
run: cargo ci-check-all-feature-powerset-linux
nextest:
name: nextest
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
- name: Install nextest
uses: taiki-e/install-action@v2.26.8
with:
tool: nextest
- name: Test with cargo-nextest
run: cargo nextest run

View file

@ -16,7 +16,13 @@ concurrency:
cancel-in-progress: true
jobs:
read_msrv:
name: Read MSRV
uses: actions-rust-lang/msrv/.github/workflows/msrv.yml@main
build_and_test:
needs: read_msrv
strategy:
fail-fast: false
matrix:
@ -26,7 +32,7 @@ jobs:
- { name: macOS, os: macos-latest, triple: x86_64-apple-darwin }
- { name: Windows, os: windows-latest, triple: x86_64-pc-windows-msvc }
version:
- { name: msrv, version: 1.68.0 }
- { name: msrv, version: "${{ needs.read_msrv.outputs.msrv }}" }
- { name: stable, version: stable }
name: ${{ matrix.target.name }} / ${{ matrix.version.name }}
@ -35,6 +41,10 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Install nasm
if: matrix.target.os == 'windows-latest'
uses: ilammy/setup-nasm@v1.5.1
- name: Install OpenSSL
if: matrix.target.os == 'windows-latest'
shell: bash
@ -44,24 +54,23 @@ jobs:
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' >> $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 }})
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
with:
toolchain: ${{ matrix.version.version }}
- name: Install cargo-hack and cargo-ci-cache-clean
uses: taiki-e/install-action@v2.26.8
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
uses: taiki-e/install-action@v2.33.22
with:
tool: cargo-hack,cargo-ci-cache-clean
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
- name: workaround MSRV issues
if: matrix.version.name == 'msrv'
run: |
cargo update -p=ciborium --precise=0.2.1
cargo update -p=ciborium-ll --precise=0.2.1
cargo update -p=clap --precise=4.3.24
cargo update -p=clap_lex --precise=0.5.0
cargo update -p=anstyle --precise=1.0.2
run: just downgrade-for-msrv
- name: check minimal
run: cargo ci-check-min
@ -71,19 +80,7 @@ jobs:
- name: tests
timeout-minutes: 60
shell: bash
run: |
set -e
cargo test --lib --tests -p=actix-router --all-features
cargo test --lib --tests -p=actix-http --all-features
cargo test --lib --tests -p=actix-web --features=rustls-0_20,rustls-0_21,rustls-0_22,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls
cargo test --lib --tests -p=actix-web-codegen --all-features
cargo test --lib --tests -p=awc --all-features
cargo test --lib --tests -p=actix-http-test --all-features
cargo test --lib --tests -p=actix-test --all-features
cargo test --lib --tests -p=actix-files
cargo test --lib --tests -p=actix-multipart --all-features
cargo test --lib --tests -p=actix-web-actors --all-features
run: just test
- name: CI cache clean
run: cargo-ci-cache-clean
@ -115,6 +112,10 @@ jobs:
with:
toolchain: nightly
- name: Install just
uses: taiki-e/install-action@v2.33.22
with:
tool: just
- name: doc tests
run: cargo ci-doctest
timeout-minutes: 60
run: just test-docs

View file

@ -23,7 +23,7 @@ jobs:
components: llvm-tools-preview
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@v2.26.8
uses: taiki-e/install-action@v2.33.22
with:
tool: cargo-llvm-cov
@ -31,7 +31,7 @@ jobs:
run: cargo llvm-cov --workspace --all-features --codecov --output-path codecov.json
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4.0.0
uses: codecov/codecov-action@v4.3.1
with:
files: codecov.json
fail_ci_if_error: true

View file

@ -79,10 +79,10 @@ jobs:
- name: Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
with:
toolchain: nightly-2023-08-25
toolchain: nightly-2024-04-26
- name: Install cargo-public-api
uses: taiki-e/install-action@v2.26.8
uses: taiki-e/install-action@v2.33.22
with:
tool: cargo-public-api

View file

@ -35,7 +35,7 @@ jobs:
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.5.0
uses: JamesIves/github-pages-deploy-action@v4.6.0
with:
folder: target/doc
single-commit: true

View file

@ -15,9 +15,11 @@ members = [
]
[workspace.package]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web"
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.68"
rust-version = "1.72"
[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,8 @@
## Unreleased
- Minimum supported Rust version (MSRV) is now 1.72.
## 0.6.5
- Fix handling of special characters in filenames.

View file

@ -47,5 +47,5 @@ actix-server = { version = "2.2", optional = true } # ensure matching tokio-urin
actix-rt = "2.7"
actix-test = "0.1"
actix-web = "4"
env_logger = "0.10"
env_logger = "0.11"
tempfile = "3.2"

View file

@ -4,7 +4,7 @@
[![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.5)](https://docs.rs/actix-files/0.6.5)
![Version](https://img.shields.io/badge/rustc-1.68+-ab6000.svg)
![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.5/status.svg)](https://deps.rs/crate/actix-files/0.6.5)

View file

@ -75,7 +75,7 @@ mod tests {
dev::ServiceFactory,
guard,
http::{
header::{self, ContentDisposition, DispositionParam, DispositionType},
header::{self, ContentDisposition, DispositionParam},
Method, StatusCode,
},
middleware::Compress,

View file

@ -2,6 +2,8 @@
## Unreleased
- Minimum supported Rust version (MSRV) is now 1.72.
## 3.2.0
- Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.

View file

@ -6,7 +6,7 @@
[![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test)
[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.2.0)](https://docs.rs/actix-http-test/3.2.0)
![Version](https://img.shields.io/badge/rustc-1.68+-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-http-test)
<br>
[![Dependency Status](https://deps.rs/crate/actix-http-test/3.2.0/status.svg)](https://deps.rs/crate/actix-http-test/3.2.0)
@ -14,8 +14,3 @@
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
<!-- prettier-ignore-end -->
## Documentation & Resources
- [API Documentation](https://docs.rs/actix-http-test)
- Minimum Supported Rust Version (MSRV): 1.68

View file

@ -2,6 +2,18 @@
## Unreleased
## 3.7.0
### Added
- Add `rustls-0_23` crate feature
- Add `{h1::H1Service, h2::H2Service, HttpService}::rustls_0_23()` and `HttpService::rustls_0_23_with_config()` service constructors.
### Changed
- Update `brotli` dependency to `6`.
- Minimum supported Rust version (MSRV) is now 1.72.
## 3.6.0
### Added

View file

@ -1,6 +1,6 @@
[package]
name = "actix-http"
version = "3.6.0"
version = "3.7.0"
authors = [
"Nikolay Kim <fafhrd91@gmail.com>",
"Rob Ede <robjtede@icloud.com>",
@ -28,6 +28,7 @@ features = [
"rustls-0_20",
"rustls-0_21",
"rustls-0_22",
"rustls-0_23",
"compress-brotli",
"compress-gzip",
"compress-zstd",
@ -66,6 +67,9 @@ rustls-0_21 = ["actix-tls/accept", "actix-tls/rustls-0_21"]
# TLS via Rustls v0.22
rustls-0_22 = ["actix-tls/accept", "actix-tls/rustls-0_22"]
# TLS via Rustls v0.23
rustls-0_23 = ["actix-tls/accept", "actix-tls/rustls-0_23"]
# Compression codecs
compress-brotli = ["__compress", "brotli"]
compress-gzip = ["__compress", "flate2"]
@ -106,31 +110,32 @@ h2 = { version = "0.3.24", optional = true }
# websockets
local-channel = { version = "0.1", optional = true }
base64 = { version = "0.21", optional = true }
base64 = { version = "0.22", optional = true }
rand = { version = "0.8", optional = true }
sha1 = { version = "0.10", optional = true }
# openssl/rustls
actix-tls = { version = "3.3", default-features = false, optional = true }
actix-tls = { version = "3.4", default-features = false, optional = true }
# compress-*
brotli = { version = "3.3.3", optional = true }
brotli = { version = "6", optional = true }
flate2 = { version = "1.0.13", optional = true }
zstd = { version = "0.13", optional = true }
[dev-dependencies]
actix-http-test = { version = "3", features = ["openssl"] }
actix-server = "2"
actix-tls = { version = "3.3", features = ["openssl", "rustls-0_22-webpki-roots"] }
actix-tls = { version = "3.4", features = ["openssl", "rustls-0_23-webpki-roots"] }
actix-web = "4"
async-stream = "0.3"
criterion = { version = "0.5", features = ["html_reports"] }
env_logger = "0.10"
divan = "0.1.8"
env_logger = "0.11"
futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] }
memchr = "2.4"
once_cell = "1.9"
rcgen = "0.12"
rcgen = "0.13"
regex = "1.3"
rustversion = "1"
rustls-pemfile = "2"
@ -138,18 +143,22 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
static_assertions = "1"
tls-openssl = { package = "openssl", version = "0.10.55" }
tls-rustls_022 = { package = "rustls", version = "0.22" }
tls-rustls_023 = { package = "rustls", version = "0.23" }
tokio = { version = "1.24.2", features = ["net", "rt", "macros"] }
[[example]]
name = "ws"
required-features = ["ws", "rustls-0_22"]
required-features = ["ws", "rustls-0_23"]
[[example]]
name = "tls_rustls"
required-features = ["http2", "rustls-0_22"]
required-features = ["http2", "rustls-0_23"]
[[bench]]
name = "response-body-compression"
harness = false
required-features = ["compress-brotli", "compress-gzip", "compress-zstd"]
[[bench]]
name = "date-formatting"
harness = false

View file

@ -5,21 +5,16 @@
<!-- prettier-ignore-start -->
[![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http)
[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.6.0)](https://docs.rs/actix-http/3.6.0)
![Version](https://img.shields.io/badge/rustc-1.68+-ab6000.svg)
[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.7.0)](https://docs.rs/actix-http/3.7.0)
![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
<br />
[![dependency status](https://deps.rs/crate/actix-http/3.6.0/status.svg)](https://deps.rs/crate/actix-http/3.6.0)
[![dependency status](https://deps.rs/crate/actix-http/3.7.0/status.svg)](https://deps.rs/crate/actix-http/3.7.0)
[![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
<!-- prettier-ignore-end -->
## Documentation & Resources
- [API Documentation](https://docs.rs/actix-http)
- Minimum Supported Rust Version (MSRV): 1.68
## Examples
```rust

View file

@ -0,0 +1,20 @@
use std::time::SystemTime;
use actix_http::header::HttpDate;
use divan::{black_box, AllocProfiler, Bencher};
#[global_allocator]
static ALLOC: AllocProfiler = AllocProfiler::system();
#[divan::bench]
fn date_formatting(b: Bencher<'_, '_>) {
let now = SystemTime::now();
b.bench(|| {
black_box(HttpDate::from(black_box(now)).to_string());
})
}
fn main() {
divan::main();
}

View file

@ -8,7 +8,7 @@
use std::{convert::Infallible, io};
use actix_http::{HttpService, Request, Response, StatusCode};
use actix_http::{body::BodyStream, HttpService, Request, Response, StatusCode};
use actix_server::Server;
#[tokio::main(flavor = "current_thread")]
@ -19,7 +19,12 @@ async fn main() -> io::Result<()> {
.bind("h2c-detect", ("127.0.0.1", 8080), || {
HttpService::build()
.finish(|_req: Request| async move {
Ok::<_, Infallible>(Response::build(StatusCode::OK).body("Hello!"))
Ok::<_, Infallible>(Response::build(StatusCode::OK).body(BodyStream::new(
futures_util::stream::iter([
Ok::<_, String>("123".into()),
Err("wertyuikmnbvcxdfty6t".to_owned()),
]),
)))
})
.tcp_auto_h2c()
})?

View file

@ -12,7 +12,7 @@
//! Protocol: HTTP/1.1
//! ```
extern crate tls_rustls_022 as rustls;
extern crate tls_rustls_023 as rustls;
use std::io;
@ -36,16 +36,17 @@ async fn main() -> io::Result<()> {
);
ok::<_, Error>(Response::ok().set_body(body))
})
.rustls_0_22(rustls_config())
.rustls_0_23(rustls_config())
})?
.run()
.await
}
fn rustls_config() -> rustls::ServerConfig {
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
let cert_file = cert.serialize_pem().unwrap();
let key_file = cert.serialize_private_key_pem();
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());

View file

@ -1,7 +1,7 @@
//! Sets up a WebSocket server over TCP and TLS.
//! Sends a heartbeat message every 4 seconds but does not respond to any incoming frames.
extern crate tls_rustls_022 as rustls;
extern crate tls_rustls_023 as rustls;
use std::{
io,
@ -30,7 +30,7 @@ async fn main() -> io::Result<()> {
.bind("tls", ("127.0.0.1", 8443), || {
HttpService::build()
.finish(handler)
.rustls_0_22(tls_config())
.rustls_0_23(tls_config())
})?
.run()
.await
@ -87,9 +87,10 @@ fn tls_config() -> rustls::ServerConfig {
use rustls_pemfile::{certs, pkcs8_private_keys};
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
let cert_file = cert.serialize_pem().unwrap();
let key_file = cert.serialize_private_key_pem();
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());

View file

@ -531,7 +531,6 @@ where
mod tests {
use actix_rt::pin;
use actix_utils::future::poll_fn;
use bytes::{Bytes, BytesMut};
use futures_util::stream;
use super::*;

View file

@ -28,7 +28,7 @@ impl Date {
fn update(&mut self) {
self.pos = 0;
write!(self, "{}", httpdate::fmt_http_date(SystemTime::now())).unwrap();
write!(self, "{}", httpdate::HttpDate::from(SystemTime::now())).unwrap();
}
}

View file

@ -399,9 +399,7 @@ pub enum ContentTypeError {
#[cfg(test)]
mod tests {
use std::io;
use http::{Error as HttpError, StatusCode};
use http::Error as HttpError;
use super::*;

View file

@ -198,9 +198,6 @@ impl Encoder<Message<(Response<()>, BodySize)>> for Codec {
#[cfg(test)]
mod tests {
use bytes::BytesMut;
use http::Method;
use super::*;
use crate::HttpMessage as _;

View file

@ -563,15 +563,8 @@ impl Decoder for PayloadDecoder {
#[cfg(test)]
mod tests {
use bytes::{Bytes, BytesMut};
use http::{Method, Version};
use super::*;
use crate::{
error::ParseError,
header::{HeaderName, SET_COOKIE},
HttpMessage as _,
};
use crate::{header::SET_COOKIE, HttpMessage as _};
impl PayloadType {
pub(crate) fn unwrap(self) -> PayloadDecoder {

View file

@ -512,8 +512,10 @@ where
}
Poll::Ready(Some(Err(err))) => {
let err = err.into();
tracing::error!("Response payload stream error: {err:?}");
this.flags.insert(Flags::FINISHED);
return Err(DispatchError::Body(err.into()));
return Err(DispatchError::Body(err));
}
Poll::Pending => return Ok(PollResponse::DoNothing),
@ -549,6 +551,7 @@ where
}
Poll::Ready(Some(Err(err))) => {
tracing::error!("Response payload stream error: {err:?}");
this.flags.insert(Flags::FINISHED);
return Err(DispatchError::Body(
Error::new_body().with_cause(err).into(),
@ -703,7 +706,7 @@ where
req.head_mut().peer_addr = *this.peer_addr;
req.conn_data = this.conn_data.as_ref().map(Rc::clone);
req.conn_data.clone_from(this.conn_data);
match this.codec.message_type() {
// request has no payload

View file

@ -335,6 +335,67 @@ mod rustls_0_22 {
}
}
#[cfg(feature = "rustls-0_23")]
mod rustls_0_23 {
use std::io;
use actix_service::ServiceFactoryExt as _;
use actix_tls::accept::{
rustls_0_23::{reexports::ServerConfig, Acceptor, TlsStream},
TlsError,
};
use super::*;
impl<S, B, X, U> H1Service<TlsStream<TcpStream>, S, B, X, U>
where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Response<BoxBody>>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
B: MessageBody,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Response<BoxBody>>,
X::InitError: fmt::Debug,
U: ServiceFactory<
(Request, Framed<TlsStream<TcpStream>, Codec>),
Config = (),
Response = (),
>,
U::Future: 'static,
U::Error: fmt::Display + Into<Response<BoxBody>>,
U::InitError: fmt::Debug,
{
/// Create Rustls v0.23 based service.
pub fn rustls_0_23(
self,
config: ServerConfig,
) -> impl ServiceFactory<
TcpStream,
Config = (),
Response = (),
Error = TlsError<io::Error, DispatchError>,
InitError = (),
> {
Acceptor::new(config)
.map_init_err(|_| {
unreachable!("TLS acceptor service factory does not error on init")
})
.map_err(TlsError::into_service_error)
.map(|io: TlsStream<TcpStream>| {
let peer_addr = io.get_ref().0.peer_addr().ok();
(io, peer_addr)
})
.and_then(self.map_err(TlsError::Service))
}
}
}
impl<T, S, B, X, U> H1Service<T, S, B, X, U>
where
S: ServiceFactory<Request, Config = ()>,

View file

@ -4,7 +4,7 @@ use std::{
future::Future,
marker::PhantomData,
net,
pin::Pin,
pin::{pin, Pin},
rc::Rc,
task::{Context, Poll},
};
@ -20,7 +20,6 @@ use h2::{
Ping, PingPong,
};
use pin_project_lite::pin_project;
use tracing::{error, trace, warn};
use crate::{
body::{BodySize, BoxBody, MessageBody},
@ -127,7 +126,7 @@ where
head.headers = parts.headers.into();
head.peer_addr = this.peer_addr;
req.conn_data = this.conn_data.as_ref().map(Rc::clone);
req.conn_data.clone_from(&this.conn_data);
let fut = this.flow.service.call(req);
let config = this.config.clone();
@ -147,11 +146,13 @@ where
if let Err(err) = res {
match err {
DispatchError::SendResponse(err) => {
trace!("Error sending HTTP/2 response: {:?}", err)
tracing::trace!("Error sending response: {err:?}");
}
DispatchError::SendData(err) => {
tracing::warn!("Send data error: {err:?}");
}
DispatchError::SendData(err) => warn!("{:?}", err),
DispatchError::ResponseBody(err) => {
error!("Response payload stream error: {:?}", err)
tracing::error!("Response payload stream error: {err:?}");
}
}
}
@ -228,9 +229,9 @@ where
return Ok(());
}
// poll response body and send chunks to client
actix_rt::pin!(body);
let mut body = pin!(body);
// poll response body and send chunks to client
while let Some(res) = poll_fn(|cx| body.as_mut().poll_next(cx)).await {
let mut chunk = res.map_err(|err| DispatchError::ResponseBody(err.into()))?;

View file

@ -293,6 +293,57 @@ mod rustls_0_22 {
}
}
#[cfg(feature = "rustls-0_23")]
mod rustls_0_23 {
use std::io;
use actix_service::ServiceFactoryExt as _;
use actix_tls::accept::{
rustls_0_23::{reexports::ServerConfig, Acceptor, TlsStream},
TlsError,
};
use super::*;
impl<S, B> H2Service<TlsStream<TcpStream>, S, B>
where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Response<BoxBody>> + 'static,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
{
/// Create Rustls v0.23 based service.
pub fn rustls_0_23(
self,
mut config: ServerConfig,
) -> impl ServiceFactory<
TcpStream,
Config = (),
Response = (),
Error = TlsError<io::Error, DispatchError>,
InitError = S::InitError,
> {
let mut protos = vec![b"h2".to_vec()];
protos.extend_from_slice(&config.alpn_protocols);
config.alpn_protocols = protos;
Acceptor::new(config)
.map_init_err(|_| {
unreachable!("TLS acceptor service factory does not error on init")
})
.map_err(TlsError::into_service_error)
.map(|io: TlsStream<TcpStream>| {
let peer_addr = io.get_ref().0.peer_addr().ok();
(io, peer_addr)
})
.and_then(self.map_err(TlsError::Service))
}
}
}
impl<T, S, B> ServiceFactory<(T, Option<net::SocketAddr>)> for H2Service<T, S, B>
where
T: AsyncRead + AsyncWrite + Unpin + 'static,

View file

@ -24,8 +24,7 @@ impl FromStr for HttpDate {
impl fmt::Display for HttpDate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let date_str = httpdate::fmt_http_date(self.0);
f.write_str(&date_str)
httpdate::HttpDate::from(self.0).fmt(f)
}
}
@ -37,7 +36,7 @@ impl TryIntoHeaderValue for HttpDate {
let mut wrt = MutWriter(&mut buf);
// unwrap: date output is known to be well formed and of known length
write!(wrt, "{}", httpdate::fmt_http_date(self.0)).unwrap();
write!(wrt, "{}", self).unwrap();
HeaderValue::from_maybe_shared(buf.split().freeze())
}

View file

@ -80,18 +80,18 @@ mod tests {
#[test]
fn comma_delimited_parsing() {
let headers = vec![];
let headers = [];
let res: Vec<usize> = from_comma_delimited(headers.iter()).unwrap();
assert_eq!(res, vec![0; 0]);
let headers = vec![
let headers = [
HeaderValue::from_static("1, 2"),
HeaderValue::from_static("3,4"),
];
let res: Vec<usize> = from_comma_delimited(headers.iter()).unwrap();
assert_eq!(res, vec![1, 2, 3, 4]);
let headers = vec![
let headers = [
HeaderValue::from_static(""),
HeaderValue::from_static(","),
HeaderValue::from_static(" "),

View file

@ -6,7 +6,10 @@
//! | ------------------- | ------------------------------------------- |
//! | `http2` | HTTP/2 support via [h2]. |
//! | `openssl` | TLS support via [OpenSSL]. |
//! | `rustls` | TLS support via [rustls]. |
//! | `rustls` | TLS support via [rustls] 0.20. |
//! | `rustls-0_21` | TLS support via [rustls] 0.21. |
//! | `rustls-0_22` | TLS support via [rustls] 0.22. |
//! | `rustls-0_23` | TLS support via [rustls] 0.23. |
//! | `compress-brotli` | Payload compression support: Brotli. |
//! | `compress-gzip` | Payload compression support: Deflate, Gzip. |
//! | `compress-zstd` | Payload compression support: Zstd. |
@ -28,7 +31,7 @@
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
pub use ::http::{uri, uri::Uri, Method, StatusCode, Version};
pub use http::{uri, uri::Uri, Method, StatusCode, Version};
pub mod body;
mod builder;
@ -63,6 +66,7 @@ pub use self::payload::PayloadStream;
feature = "rustls-0_20",
feature = "rustls-0_21",
feature = "rustls-0_22",
feature = "rustls-0_23",
))]
pub use self::service::TlsAcceptorConfig;
pub use self::{

View file

@ -5,7 +5,7 @@
use std::cell::RefCell;
thread_local! {
static NOTIFY_DROPPED: RefCell<Option<bool>> = RefCell::new(None);
static NOTIFY_DROPPED: RefCell<Option<bool>> = const { RefCell::new(None) };
}
/// Check if the spawned task is dropped.

View file

@ -246,6 +246,7 @@ where
feature = "rustls-0_20",
feature = "rustls-0_21",
feature = "rustls-0_22",
feature = "rustls-0_23",
))]
#[derive(Debug, Default)]
pub struct TlsAcceptorConfig {
@ -257,6 +258,7 @@ pub struct TlsAcceptorConfig {
feature = "rustls-0_20",
feature = "rustls-0_21",
feature = "rustls-0_22",
feature = "rustls-0_23",
))]
impl TlsAcceptorConfig {
/// Set TLS handshake timeout duration.
@ -650,6 +652,102 @@ mod rustls_0_22 {
}
}
#[cfg(feature = "rustls-0_23")]
mod rustls_0_23 {
use std::io;
use actix_service::ServiceFactoryExt as _;
use actix_tls::accept::{
rustls_0_23::{reexports::ServerConfig, Acceptor, TlsStream},
TlsError,
};
use super::*;
impl<S, B, X, U> HttpService<TlsStream<TcpStream>, S, B, X, U>
where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Response<BoxBody>> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Response<BoxBody>>,
X::InitError: fmt::Debug,
U: ServiceFactory<
(Request, Framed<TlsStream<TcpStream>, h1::Codec>),
Config = (),
Response = (),
>,
U::Future: 'static,
U::Error: fmt::Display + Into<Response<BoxBody>>,
U::InitError: fmt::Debug,
{
/// Create Rustls v0.23 based service.
pub fn rustls_0_23(
self,
config: ServerConfig,
) -> impl ServiceFactory<
TcpStream,
Config = (),
Response = (),
Error = TlsError<io::Error, DispatchError>,
InitError = (),
> {
self.rustls_0_23_with_config(config, TlsAcceptorConfig::default())
}
/// Create Rustls v0.23 based service with custom TLS acceptor configuration.
pub fn rustls_0_23_with_config(
self,
mut config: ServerConfig,
tls_acceptor_config: TlsAcceptorConfig,
) -> impl ServiceFactory<
TcpStream,
Config = (),
Response = (),
Error = TlsError<io::Error, DispatchError>,
InitError = (),
> {
let mut protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
protos.extend_from_slice(&config.alpn_protocols);
config.alpn_protocols = protos;
let mut acceptor = Acceptor::new(config);
if let Some(handshake_timeout) = tls_acceptor_config.handshake_timeout {
acceptor.set_handshake_timeout(handshake_timeout);
}
acceptor
.map_init_err(|_| {
unreachable!("TLS acceptor service factory does not error on init")
})
.map_err(TlsError::into_service_error)
.and_then(|io: TlsStream<TcpStream>| async {
let proto = if let Some(protos) = io.get_ref().1.alpn_protocol() {
if protos.windows(2).any(|window| window == b"h2") {
Protocol::Http2
} else {
Protocol::Http1
}
} else {
Protocol::Http1
};
let peer_addr = io.get_ref().0.peer_addr().ok();
Ok((io, proto, peer_addr))
})
.and_then(self.map_err(TlsError::Service))
}
}
}
impl<T, S, B, X, U> ServiceFactory<(T, Protocol, Option<net::SocketAddr>)>
for HttpService<T, S, B, X, U>
where

View file

@ -221,7 +221,7 @@ pub fn handshake_response(req: &RequestHead) -> ResponseBuilder {
#[cfg(test)]
mod tests {
use super::*;
use crate::{header, test::TestRequest, Method};
use crate::{header, test::TestRequest};
#[test]
fn test_handshake() {

View file

@ -1,7 +1,4 @@
use std::{
convert::{From, Into},
fmt,
};
use std::fmt;
use base64::prelude::*;
use tracing::error;

View file

@ -42,9 +42,11 @@ where
}
fn tls_config() -> SslAcceptor {
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
let cert_file = cert.serialize_pem().unwrap();
let key_file = cert.serialize_private_key_pem();
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 = X509::from_pem(cert_file.as_bytes()).unwrap();
let key = PKey::private_key_from_pem(key_file.as_bytes()).unwrap();

View file

@ -1,6 +1,6 @@
#![cfg(feature = "rustls-0_22")]
#![cfg(feature = "rustls-0_23")]
extern crate tls_rustls_022 as rustls;
extern crate tls_rustls_023 as rustls;
use std::{
convert::Infallible,
@ -20,7 +20,7 @@ use actix_http::{
use actix_http_test::test_server;
use actix_rt::pin;
use actix_service::{fn_factory_with_config, fn_service};
use actix_tls::connect::rustls_0_22::webpki_roots_cert_store;
use actix_tls::connect::rustls_0_23::webpki_roots_cert_store;
use actix_utils::future::{err, ok, poll_fn};
use bytes::{Bytes, BytesMut};
use derive_more::{Display, Error};
@ -52,9 +52,10 @@ where
}
fn tls_config() -> RustlsServerConfig {
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
let cert_file = cert.serialize_pem().unwrap();
let key_file = cert.serialize_private_key_pem();
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());
@ -108,7 +109,7 @@ async fn h1() -> io::Result<()> {
let srv = test_server(move || {
HttpService::build()
.h1(|_| ok::<_, Error>(Response::ok()))
.rustls_0_22(tls_config())
.rustls_0_23(tls_config())
})
.await;
@ -122,7 +123,7 @@ async fn h2() -> io::Result<()> {
let srv = test_server(move || {
HttpService::build()
.h2(|_| ok::<_, Error>(Response::ok()))
.rustls_0_22(tls_config())
.rustls_0_23(tls_config())
})
.await;
@ -140,7 +141,7 @@ async fn h1_1() -> io::Result<()> {
assert_eq!(req.version(), Version::HTTP_11);
ok::<_, Error>(Response::ok())
})
.rustls_0_22(tls_config())
.rustls_0_23(tls_config())
})
.await;
@ -158,7 +159,7 @@ async fn h2_1() -> io::Result<()> {
assert_eq!(req.version(), Version::HTTP_2);
ok::<_, Error>(Response::ok())
})
.rustls_0_22_with_config(
.rustls_0_23_with_config(
tls_config(),
TlsAcceptorConfig::default().handshake_timeout(Duration::from_secs(5)),
)
@ -179,7 +180,7 @@ async fn h2_body1() -> io::Result<()> {
let body = load_body(req.take_payload()).await?;
Ok::<_, Error>(Response::ok().set_body(body))
})
.rustls_0_22(tls_config())
.rustls_0_23(tls_config())
})
.await;
@ -205,7 +206,7 @@ async fn h2_content_length() {
];
ok::<_, Infallible>(Response::new(statuses[indx]))
})
.rustls_0_22(tls_config())
.rustls_0_23(tls_config())
})
.await;
@ -277,7 +278,7 @@ async fn h2_headers() {
}
ok::<_, Infallible>(config.body(data.clone()))
})
.rustls_0_22(tls_config())
.rustls_0_23(tls_config())
})
.await;
@ -316,7 +317,7 @@ async fn h2_body2() {
let mut srv = test_server(move || {
HttpService::build()
.h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
.rustls_0_22(tls_config())
.rustls_0_23(tls_config())
})
.await;
@ -333,7 +334,7 @@ async fn h2_head_empty() {
let mut srv = test_server(move || {
HttpService::build()
.finish(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
.rustls_0_22(tls_config())
.rustls_0_23(tls_config())
})
.await;
@ -359,7 +360,7 @@ async fn h2_head_binary() {
let mut srv = test_server(move || {
HttpService::build()
.h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
.rustls_0_22(tls_config())
.rustls_0_23(tls_config())
})
.await;
@ -384,7 +385,7 @@ async fn h2_head_binary2() {
let srv = test_server(move || {
HttpService::build()
.h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
.rustls_0_22(tls_config())
.rustls_0_23(tls_config())
})
.await;
@ -410,7 +411,7 @@ async fn h2_body_length() {
Response::ok().set_body(SizedStream::new(STR.len() as u64, body)),
)
})
.rustls_0_22(tls_config())
.rustls_0_23(tls_config())
})
.await;
@ -434,7 +435,7 @@ async fn h2_body_chunked_explicit() {
.body(BodyStream::new(body)),
)
})
.rustls_0_22(tls_config())
.rustls_0_23(tls_config())
})
.await;
@ -463,7 +464,7 @@ async fn h2_response_http_error_handling() {
)
}))
}))
.rustls_0_22(tls_config())
.rustls_0_23(tls_config())
})
.await;
@ -493,7 +494,7 @@ async fn h2_service_error() {
let mut srv = test_server(move || {
HttpService::build()
.h2(|_| err::<Response<BoxBody>, _>(BadRequest))
.rustls_0_22(tls_config())
.rustls_0_23(tls_config())
})
.await;
@ -510,7 +511,7 @@ async fn h1_service_error() {
let mut srv = test_server(move || {
HttpService::build()
.h1(|_| err::<Response<BoxBody>, _>(BadRequest))
.rustls_0_22(tls_config())
.rustls_0_23(tls_config())
})
.await;
@ -533,7 +534,7 @@ async fn alpn_h1() -> io::Result<()> {
config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec());
HttpService::build()
.h1(|_| ok::<_, Error>(Response::ok()))
.rustls_0_22(config)
.rustls_0_23(config)
})
.await;
@ -555,7 +556,7 @@ async fn alpn_h2() -> io::Result<()> {
config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec());
HttpService::build()
.h2(|_| ok::<_, Error>(Response::ok()))
.rustls_0_22(config)
.rustls_0_23(config)
})
.await;
@ -581,7 +582,7 @@ async fn alpn_h2_1() -> io::Result<()> {
config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec());
HttpService::build()
.finish(|_| ok::<_, Error>(Response::ok()))
.rustls_0_22(config)
.rustls_0_23(config)
})
.await;

View file

@ -2,6 +2,8 @@
## Unreleased
- Minimum supported Rust version (MSRV) is now 1.72.
## 0.6.1
- Update `syn` dependency to `2`.

View file

@ -4,10 +4,11 @@ version = "0.6.1"
authors = ["Jacob Halsey <jacob@jhalsey.com>"]
description = "Multipart form derive macro for Actix Web"
keywords = ["http", "web", "framework", "async", "futures"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web"
license = "MIT OR Apache-2.0"
edition = "2021"
homepage.workspace = true
repository.workspace = true
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]

View file

@ -6,7 +6,7 @@
[![crates.io](https://img.shields.io/crates/v/actix-multipart-derive?label=latest)](https://crates.io/crates/actix-multipart-derive)
[![Documentation](https://docs.rs/actix-multipart-derive/badge.svg?version=0.6.1)](https://docs.rs/actix-multipart-derive/0.6.1)
![Version](https://img.shields.io/badge/rustc-1.68+-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-derive.svg)
<br />
[![dependency status](https://deps.rs/crate/actix-multipart-derive/0.6.1/status.svg)](https://deps.rs/crate/actix-multipart-derive/0.6.1)
@ -14,8 +14,3 @@
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
<!-- prettier-ignore-end -->
## Documentation & Resources
- [API Documentation](https://docs.rs/actix-multipart-derive)
- Minimum Supported Rust Version (MSRV): 1.68

View file

@ -1,4 +1,4 @@
#[rustversion::stable(1.68)] // MSRV
#[rustversion::stable(1.72)] // MSRV
#[test]
fn compile_macros() {
let t = trybuild::TestCases::new();

View file

@ -2,6 +2,9 @@
## Unreleased
- Add testing utilities under new module `test`.
- Minimum supported Rust version (MSRV) is now 1.72.
## 0.6.1
- Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.

View file

@ -35,6 +35,7 @@ local-waker = "0.1"
log = "0.4"
memchr = "2.5"
mime = "0.3"
rand = "0.8"
serde = "1"
serde_json = "1"
serde_plain = "1"
@ -46,7 +47,9 @@ actix-http = "3"
actix-multipart-rfc7578 = "0.10"
actix-rt = "2.2"
actix-test = "0.1"
actix-web = "4"
awc = "3"
futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] }
multer = "3"
tokio = { version = "1.24.2", features = ["sync"] }
tokio-stream = "0.1"

View file

@ -6,7 +6,7 @@
[![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)
![Version](https://img.shields.io/badge/rustc-1.68+-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)
<br />
[![dependency status](https://deps.rs/crate/actix-multipart/0.6.1/status.svg)](https://deps.rs/crate/actix-multipart/0.6.1)
@ -15,7 +15,64 @@
<!-- prettier-ignore-end -->
## Documentation & Resources
- [API Documentation](https://docs.rs/actix-multipart)
- Minimum Supported Rust Version (MSRV): 1.68
## Example
Dependencies:
```toml
[dependencies]
actix-multipart = "0.6"
actix-web = "4.5"
serde = { version = "1.0", features = ["derive"] }
```
Code:
```rust
use actix_web::{post, App, HttpServer, Responder};
use actix_multipart::form::{json::Json as MPJson, tempfile::TempFile, MultipartForm};
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct Metadata {
name: String,
}
#[derive(Debug, MultipartForm)]
struct UploadForm {
#[multipart(limit = "100MB")]
file: TempFile,
json: MPJson<Metadata>,
}
#[post("/videos")]
pub async fn post_video(MultipartForm(form): MultipartForm<UploadForm>) -> impl Responder {
format!(
"Uploaded file {}, with size: {}",
form.json.name, form.file.size
)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(move || App::new().service(post_video))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
```
Curl request :
```bash
curl -v --request POST \
--url http://localhost:8080/videos \
-F 'json={"name": "Cargo.lock"};type=application/json' \
-F file=@./Cargo.lock
```
### Examples
https://github.com/actix/examples/tree/master/forms/multipart

View file

@ -131,14 +131,13 @@ impl Default for JsonConfig {
#[cfg(test)]
mod tests {
use std::{collections::HashMap, io::Cursor};
use std::collections::HashMap;
use actix_multipart_rfc7578::client::multipart;
use actix_web::{http::StatusCode, web, App, HttpResponse, Responder};
use bytes::Bytes;
use crate::form::{
json::{Json, JsonConfig},
tests::send_form,
MultipartForm,
};
@ -155,6 +154,8 @@ mod tests {
HttpResponse::Ok().finish()
}
const TEST_JSON: &str = r#"{"key1": "value1", "key2": "value2"}"#;
#[actix_rt::test]
async fn test_json_without_content_type() {
let srv = actix_test::start(|| {
@ -163,10 +164,16 @@ mod tests {
.app_data(JsonConfig::default().validate_content_type(false))
});
let mut form = multipart::Form::default();
form.add_text("json", "{\"key1\": \"value1\", \"key2\": \"value2\"}");
let response = send_form(&srv, form, "/").await;
assert_eq!(response.status(), StatusCode::OK);
let (body, headers) = crate::test::create_form_data_payload_and_headers(
"json",
None,
None,
Bytes::from_static(TEST_JSON.as_bytes()),
);
let mut req = srv.post("/");
*req.headers_mut() = headers;
let res = req.send_body(body).await.unwrap();
assert_eq!(res.status(), StatusCode::OK);
}
#[actix_rt::test]
@ -178,17 +185,27 @@ mod tests {
});
// Deny because wrong content type
let bytes = Cursor::new("{\"key1\": \"value1\", \"key2\": \"value2\"}");
let mut form = multipart::Form::default();
form.add_reader_file_with_mime("json", bytes, "", mime::APPLICATION_OCTET_STREAM);
let response = send_form(&srv, form, "/").await;
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
let (body, headers) = crate::test::create_form_data_payload_and_headers(
"json",
None,
Some(mime::APPLICATION_OCTET_STREAM),
Bytes::from_static(TEST_JSON.as_bytes()),
);
let mut req = srv.post("/");
*req.headers_mut() = headers;
let res = req.send_body(body).await.unwrap();
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
// Allow because correct content type
let bytes = Cursor::new("{\"key1\": \"value1\", \"key2\": \"value2\"}");
let mut form = multipart::Form::default();
form.add_reader_file_with_mime("json", bytes, "", mime::APPLICATION_JSON);
let response = send_form(&srv, form, "/").await;
assert_eq!(response.status(), StatusCode::OK);
let (body, headers) = crate::test::create_form_data_payload_and_headers(
"json",
None,
Some(mime::APPLICATION_JSON),
Bytes::from_static(TEST_JSON.as_bytes()),
);
let mut req = srv.post("/");
*req.headers_mut() = headers;
let res = req.send_body(body).await.unwrap();
assert_eq!(res.status(), StatusCode::OK);
}
}

View file

@ -313,7 +313,8 @@ where
let entry = field_limits
.entry(field.name().to_owned())
.or_insert_with(|| T::limit(field.name()));
limits.field_limit_remaining = entry.to_owned();
limits.field_limit_remaining.clone_from(entry);
T::handle_field(&req, field, &mut limits, &mut state).await?;

View file

@ -1,4 +1,39 @@
//! Multipart form support for Actix Web.
//! # Examples
//! ```no_run
//! use actix_web::{post, App, HttpServer, Responder};
//!
//! use actix_multipart::form::{json::Json as MPJson, tempfile::TempFile, MultipartForm};
//! use serde::Deserialize;
//!
//! #[derive(Debug, Deserialize)]
//! struct Metadata {
//! name: String,
//! }
//!
//! #[derive(Debug, MultipartForm)]
//! struct UploadForm {
//! #[multipart(limit = "100MB")]
//! file: TempFile,
//! json: MPJson<Metadata>,
//! }
//!
//! #[post("/videos")]
//! pub async fn post_video(MultipartForm(form): MultipartForm<UploadForm>) -> impl Responder {
//! format!(
//! "Uploaded file {}, with size: {}",
//! form.json.name, form.file.size
//! )
//! }
//!
//! #[actix_web::main]
//! async fn main() -> std::io::Result<()> {
//! HttpServer::new(move || App::new().service(post_video))
//! .bind(("127.0.0.1", 8080))?
//! .run()
//! .await
//! }
//! ```
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)]
@ -13,11 +48,14 @@ extern crate self as actix_multipart;
mod error;
mod extractor;
mod server;
pub mod form;
mod server;
pub mod test;
pub use self::{
error::MultipartError,
server::{Field, Multipart},
test::{
create_form_data_payload_and_headers, create_form_data_payload_and_headers_with_boundary,
},
};

View file

@ -863,13 +863,15 @@ mod tests {
test::TestRequest,
FromRequest,
};
use bytes::Bytes;
use bytes::BufMut as _;
use futures_util::{future::lazy, StreamExt as _};
use tokio::sync::mpsc;
use tokio_stream::wrappers::UnboundedReceiverStream;
use super::*;
const BOUNDARY: &str = "abbc761f78ff4d7cb7573b5a23f96ef0";
#[actix_rt::test]
async fn test_boundary() {
let headers = HeaderMap::new();
@ -965,6 +967,26 @@ mod tests {
}
fn create_simple_request_with_header() -> (Bytes, HeaderMap) {
let (body, headers) = crate::test::create_form_data_payload_and_headers_with_boundary(
BOUNDARY,
"file",
Some("fn.txt".to_owned()),
Some(mime::TEXT_PLAIN_UTF_8),
Bytes::from_static(b"data"),
);
let mut buf = BytesMut::with_capacity(body.len() + 14);
// add junk before form to test pre-boundary data rejection
buf.put("testasdadsad\r\n".as_bytes());
buf.put(body);
(buf.freeze(), headers)
}
// TODO: use test utility when multi-file support is introduced
fn create_double_request_with_header() -> (Bytes, HeaderMap) {
let bytes = Bytes::from(
"testasdadsad\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
@ -990,7 +1012,7 @@ mod tests {
#[actix_rt::test]
async fn test_multipart_no_end_crlf() {
let (sender, payload) = create_stream();
let (mut bytes, headers) = create_simple_request_with_header();
let (mut bytes, headers) = create_double_request_with_header();
let bytes_stripped = bytes.split_to(bytes.len()); // strip crlf
sender.send(Ok(bytes_stripped)).unwrap();
@ -1017,7 +1039,7 @@ mod tests {
#[actix_rt::test]
async fn test_multipart() {
let (sender, payload) = create_stream();
let (bytes, headers) = create_simple_request_with_header();
let (bytes, headers) = create_double_request_with_header();
sender.send(Ok(bytes)).unwrap();
@ -1080,7 +1102,7 @@ mod tests {
#[actix_rt::test]
async fn test_stream() {
let (bytes, headers) = create_simple_request_with_header();
let (bytes, headers) = create_double_request_with_header();
let payload = SlowStream::new(bytes);
let mut multipart = Multipart::new(&headers, payload);
@ -1319,7 +1341,7 @@ mod tests {
#[actix_rt::test]
async fn test_drop_field_awaken_multipart() {
let (sender, payload) = create_stream();
let (bytes, headers) = create_simple_request_with_header();
let (bytes, headers) = create_double_request_with_header();
sender.send(Ok(bytes)).unwrap();
drop(sender); // eof

217
actix-multipart/src/test.rs Normal file
View file

@ -0,0 +1,217 @@
use actix_web::http::header::{self, HeaderMap};
use bytes::{BufMut as _, Bytes, BytesMut};
use mime::Mime;
use rand::{
distributions::{Alphanumeric, DistString as _},
thread_rng,
};
const CRLF: &[u8] = b"\r\n";
const CRLF_CRLF: &[u8] = b"\r\n\r\n";
const HYPHENS: &[u8] = b"--";
const BOUNDARY_PREFIX: &str = "------------------------";
/// Constructs a `multipart/form-data` payload from bytes and metadata.
///
/// Returned header map can be extended or merged with existing headers.
///
/// Multipart boundary used is a random alphanumeric string.
///
/// # Examples
///
/// ```
/// use actix_multipart::test::create_form_data_payload_and_headers;
/// use actix_web::test::TestRequest;
/// use bytes::Bytes;
/// use memchr::memmem::find;
///
/// let (body, headers) = create_form_data_payload_and_headers(
/// "foo",
/// Some("lorem.txt".to_owned()),
/// Some(mime::TEXT_PLAIN_UTF_8),
/// Bytes::from_static(b"Lorem ipsum."),
/// );
///
/// assert!(find(&body, b"foo").is_some());
/// assert!(find(&body, b"lorem.txt").is_some());
/// assert!(find(&body, b"text/plain; charset=utf-8").is_some());
/// assert!(find(&body, b"Lorem ipsum.").is_some());
///
/// let req = TestRequest::default();
///
/// // merge header map into existing test request and set multipart body
/// let req = headers
/// .into_iter()
/// .fold(req, |req, hdr| req.insert_header(hdr))
/// .set_payload(body)
/// .to_http_request();
///
/// assert!(
/// req.headers()
/// .get("content-type")
/// .unwrap()
/// .to_str()
/// .unwrap()
/// .starts_with("multipart/form-data; boundary=\"")
/// );
/// ```
pub fn create_form_data_payload_and_headers(
name: &str,
filename: Option<String>,
content_type: Option<Mime>,
file: Bytes,
) -> (Bytes, HeaderMap) {
let boundary = Alphanumeric.sample_string(&mut thread_rng(), 32);
create_form_data_payload_and_headers_with_boundary(
&boundary,
name,
filename,
content_type,
file,
)
}
/// Constructs a `multipart/form-data` payload from bytes and metadata with a fixed boundary.
///
/// See [`create_form_data_payload_and_headers`] for more details.
pub fn create_form_data_payload_and_headers_with_boundary(
boundary: &str,
name: &str,
filename: Option<String>,
content_type: Option<Mime>,
file: Bytes,
) -> (Bytes, HeaderMap) {
let mut buf = BytesMut::with_capacity(file.len() + 128);
let boundary_str = [BOUNDARY_PREFIX, boundary].concat();
let boundary = boundary_str.as_bytes();
buf.put(HYPHENS);
buf.put(boundary);
buf.put(CRLF);
buf.put(format!("Content-Disposition: form-data; name=\"{name}\"").as_bytes());
if let Some(filename) = filename {
buf.put(format!("; filename=\"{filename}\"").as_bytes());
}
buf.put(CRLF);
if let Some(ct) = content_type {
buf.put(format!("Content-Type: {ct}").as_bytes());
buf.put(CRLF);
}
buf.put(format!("Content-Length: {}", file.len()).as_bytes());
buf.put(CRLF_CRLF);
buf.put(file);
buf.put(CRLF);
buf.put(HYPHENS);
buf.put(boundary);
buf.put(HYPHENS);
buf.put(CRLF);
let mut headers = HeaderMap::new();
headers.insert(
header::CONTENT_TYPE,
format!("multipart/form-data; boundary=\"{boundary_str}\"")
.parse()
.unwrap(),
);
(buf.freeze(), headers)
}
#[cfg(test)]
mod tests {
use std::convert::Infallible;
use futures_util::stream;
use super::*;
fn find_boundary(headers: &HeaderMap) -> String {
headers
.get("content-type")
.unwrap()
.to_str()
.unwrap()
.parse::<mime::Mime>()
.unwrap()
.get_param(mime::BOUNDARY)
.unwrap()
.as_str()
.to_owned()
}
#[test]
fn wire_format() {
let (pl, headers) = create_form_data_payload_and_headers_with_boundary(
"qWeRtYuIoP",
"foo",
None,
None,
Bytes::from_static(b"Lorem ipsum dolor\nsit ame."),
);
assert_eq!(
find_boundary(&headers),
"------------------------qWeRtYuIoP",
);
assert_eq!(
std::str::from_utf8(&pl).unwrap(),
"--------------------------qWeRtYuIoP\r\n\
Content-Disposition: form-data; name=\"foo\"\r\n\
Content-Length: 26\r\n\
\r\n\
Lorem ipsum dolor\n\
sit ame.\r\n\
--------------------------qWeRtYuIoP--\r\n",
);
let (pl, _headers) = create_form_data_payload_and_headers_with_boundary(
"qWeRtYuIoP",
"foo",
Some("Lorem.txt".to_owned()),
Some(mime::TEXT_PLAIN_UTF_8),
Bytes::from_static(b"Lorem ipsum dolor\nsit ame."),
);
assert_eq!(
std::str::from_utf8(&pl).unwrap(),
"--------------------------qWeRtYuIoP\r\n\
Content-Disposition: form-data; name=\"foo\"; filename=\"Lorem.txt\"\r\n\
Content-Type: text/plain; charset=utf-8\r\n\
Content-Length: 26\r\n\
\r\n\
Lorem ipsum dolor\n\
sit ame.\r\n\
--------------------------qWeRtYuIoP--\r\n",
);
}
/// Test using an external library to prevent the two-wrongs-make-a-right class of errors.
#[actix_web::test]
async fn ecosystem_compat() {
let (pl, headers) = create_form_data_payload_and_headers(
"foo",
None,
None,
Bytes::from_static(b"Lorem ipsum dolor\nsit ame."),
);
let boundary = find_boundary(&headers);
let pl = stream::once(async { Ok::<_, Infallible>(pl) });
let mut form = multer::Multipart::new(pl, boundary);
let field = form.next_field().await.unwrap().unwrap();
assert_eq!(field.name().unwrap(), "foo");
assert_eq!(field.file_name(), None);
assert_eq!(field.content_type(), None);
assert!(field.bytes().await.unwrap().starts_with(b"Lorem"));
}
}

View file

@ -2,6 +2,11 @@
## 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.
- Minimum supported Rust version (MSRV) is now 1.72.
## 0.5.2
- Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.

View file

@ -1,6 +1,6 @@
[package]
name = "actix-router"
version = "0.5.2"
version = "0.5.3"
authors = [
"Nikolay Kim <fafhrd91@gmail.com>",
"Ali MJ Al-Nasrawy <alimjalnasrawy@gmail.com>",
@ -17,12 +17,16 @@ name = "actix_router"
path = "src/lib.rs"
[features]
default = ["http"]
default = ["http", "unicode"]
http = ["dep:http"]
unicode = ["dep:regex"]
[dependencies]
bytestring = ">=0.1.5, <2"
cfg-if = "1"
http = { version = "0.2.7", optional = true }
regex = "1.5"
regex = { version = "1.5", optional = true }
regex-lite = "0.1"
serde = "1"
tracing = { version = "0.1.30", default-features = false, features = ["log"] }
@ -35,6 +39,7 @@ percent-encoding = "2.1"
[[bench]]
name = "router"
harness = false
required-features = ["unicode"]
[[bench]]
name = "quoter"

View file

@ -3,11 +3,11 @@
<!-- prettier-ignore-start -->
[![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)
![Version](https://img.shields.io/badge/rustc-1.68+-ab6000.svg)
[![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)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-router.svg)
<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)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)

View file

@ -500,10 +500,10 @@ impl<'de> de::VariantAccess<'de> for UnitVariant {
#[cfg(test)]
mod tests {
use serde::{de, Deserialize};
use serde::Deserialize;
use super::*;
use crate::{path::Path, router::Router, ResourceDef};
use crate::{router::Router, ResourceDef};
#[derive(Deserialize)]
struct MyStruct {

View file

@ -10,6 +10,7 @@ mod de;
mod path;
mod pattern;
mod quoter;
mod regex_set;
mod resource;
mod resource_path;
mod router;

View file

@ -154,15 +154,11 @@ impl<T: ResourcePath> Path<T> {
None
}
/// Get matched parameter by name.
/// Returns matched parameter by name.
///
/// If keyed parameter is not available empty string is used as default value.
pub fn query(&self, key: &str) -> &str {
if let Some(s) = self.get(key) {
s
} else {
""
}
self.get(key).unwrap_or_default()
}
/// Return iterator to items in parameter container.

View file

@ -0,0 +1,66 @@
//! Abstraction over `regex` and `regex-lite` depending on whether we have `unicode` crate feature
//! enabled.
use cfg_if::cfg_if;
#[cfg(feature = "unicode")]
pub(crate) use regex::{escape, Regex};
#[cfg(not(feature = "unicode"))]
pub(crate) use regex_lite::{escape, Regex};
#[cfg(feature = "unicode")]
#[derive(Debug, Clone)]
pub(crate) struct RegexSet(regex::RegexSet);
#[cfg(not(feature = "unicode"))]
#[derive(Debug, Clone)]
pub(crate) struct RegexSet(Vec<regex_lite::Regex>);
impl RegexSet {
/// Create a new regex set.
///
/// # Panics
///
/// Panics if any path patterns are malformed.
pub(crate) fn new(re_set: Vec<String>) -> Self {
cfg_if! {
if #[cfg(feature = "unicode")] {
Self(regex::RegexSet::new(re_set).unwrap())
} else {
Self(re_set.iter().map(|re| Regex::new(re).unwrap()).collect())
}
}
}
/// Create a new empty regex set.
pub(crate) fn empty() -> Self {
cfg_if! {
if #[cfg(feature = "unicode")] {
Self(regex::RegexSet::empty())
} else {
Self(Vec::new())
}
}
}
/// Returns true if regex set matches `path`.
pub(crate) fn is_match(&self, path: &str) -> bool {
cfg_if! {
if #[cfg(feature = "unicode")] {
self.0.is_match(path)
} else {
self.0.iter().any(|re| re.is_match(path))
}
}
}
/// Returns index within `path` of first match.
pub(crate) fn first_match_idx(&self, path: &str) -> Option<usize> {
cfg_if! {
if #[cfg(feature = "unicode")] {
self.0.matches(path).into_iter().next()
} else {
Some(self.0.iter().enumerate().find(|(_, re)| re.is_match(path))?.0)
}
}
}
}

View file

@ -5,10 +5,13 @@ use std::{
mem,
};
use regex::{escape, Regex, RegexSet};
use tracing::error;
use crate::{path::PathItem, IntoPatterns, Patterns, Resource, ResourcePath};
use crate::{
path::PathItem,
regex_set::{escape, Regex, RegexSet},
IntoPatterns, Patterns, Resource, ResourcePath,
};
const MAX_DYNAMIC_SEGMENTS: usize = 16;
@ -233,7 +236,7 @@ enum PatternSegment {
Var(String),
}
#[derive(Clone, Debug)]
#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
enum PatternType {
/// Single constant/literal segment.
@ -603,7 +606,7 @@ impl ResourceDef {
PatternType::Dynamic(re, _) => Some(re.captures(path)?[1].len()),
PatternType::DynamicSet(re, params) => {
let idx = re.matches(path).into_iter().next()?;
let idx = re.first_match_idx(path)?;
let (ref pattern, _) = params[idx];
Some(pattern.captures(path)?[1].len())
}
@ -706,7 +709,7 @@ impl ResourceDef {
PatternType::DynamicSet(re, params) => {
let path = path.unprocessed();
let (pattern, names) = match re.matches(path).into_iter().next() {
let (pattern, names) = match re.first_match_idx(path) {
Some(idx) => &params[idx],
_ => return false,
};
@ -870,7 +873,7 @@ impl ResourceDef {
}
}
let pattern_re_set = RegexSet::new(re_set).unwrap();
let pattern_re_set = RegexSet::new(re_set);
let segments = segments.unwrap_or_default();
(

View file

@ -2,6 +2,10 @@
## Unreleased
- 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.
- Various types from `awc`, such as `ClientRequest` and `ClientResponse`, are now re-exported.
## 0.1.3
- Add `TestServerConfig::rustls_0_22()` method for Rustls v0.22 support behind new `rustls-0_22` crate feature.

View file

@ -29,19 +29,21 @@ rustls-0_20 = ["tls-rustls-0_20", "actix-http/rustls-0_20", "awc/rustls-0_20"]
rustls-0_21 = ["tls-rustls-0_21", "actix-http/rustls-0_21", "awc/rustls-0_21"]
# TLS via Rustls v0.22
rustls-0_22 = ["tls-rustls-0_22", "actix-http/rustls-0_22", "awc/rustls-0_22-webpki-roots"]
# TLS via Rustls v0.23
rustls-0_23 = ["tls-rustls-0_23", "actix-http/rustls-0_23", "awc/rustls-0_23-webpki-roots"]
# TLS via OpenSSL
openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"]
[dependencies]
actix-codec = "0.5"
actix-http = "3.6"
actix-http = "3.7"
actix-http-test = "3"
actix-rt = "2.1"
actix-service = "2"
actix-utils = "3"
actix-web = { version = "4.5", default-features = false, features = ["cookies"] }
awc = { version = "3.4", default-features = false, features = ["cookies"] }
actix-web = { version = "4.6", default-features = false, features = ["cookies"] }
awc = { version = "3.5", default-features = false, features = ["cookies"] }
futures-core = { version = "0.3.17", default-features = false, features = ["std"] }
futures-util = { version = "0.3.17", default-features = false, features = [] }
@ -53,4 +55,5 @@ tls-openssl = { package = "openssl", version = "0.10.55", optional = true }
tls-rustls-0_20 = { package = "rustls", version = "0.20", optional = true }
tls-rustls-0_21 = { package = "rustls", version = "0.21", optional = true }
tls-rustls-0_22 = { package = "rustls", version = "0.22", optional = true }
tls-rustls-0_23 = { package = "rustls", version = "0.23", default-features = false, optional = true }
tokio = { version = "1.24.2", features = ["sync"] }

View file

@ -52,7 +52,7 @@ use actix_web::{
rt::{self, System},
web, Error,
};
use awc::{error::PayloadError, Client, ClientRequest, ClientResponse, Connector};
pub use awc::{error::PayloadError, Client, ClientRequest, ClientResponse, Connector};
use futures_core::Stream;
use tokio::sync::mpsc;
@ -145,6 +145,8 @@ where
StreamType::Rustls021(_) => true,
#[cfg(feature = "rustls-0_22")]
StreamType::Rustls022(_) => true,
#[cfg(feature = "rustls-0_23")]
StreamType::Rustls023(_) => true,
};
// run server in separate orphaned thread
@ -371,6 +373,48 @@ where
.rustls_0_22(config.clone())
}),
},
#[cfg(feature = "rustls-0_23")]
StreamType::Rustls023(config) => match cfg.tp {
HttpVer::Http1 => builder.listen("test", tcp, move || {
let app_cfg =
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
let fac = factory()
.into_factory()
.map_err(|err| err.into().error_response());
HttpService::build()
.client_request_timeout(timeout)
.h1(map_config(fac, move |_| app_cfg.clone()))
.rustls_0_23(config.clone())
}),
HttpVer::Http2 => builder.listen("test", tcp, move || {
let app_cfg =
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
let fac = factory()
.into_factory()
.map_err(|err| err.into().error_response());
HttpService::build()
.client_request_timeout(timeout)
.h2(map_config(fac, move |_| app_cfg.clone()))
.rustls_0_23(config.clone())
}),
HttpVer::Both => builder.listen("test", tcp, move || {
let app_cfg =
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
let fac = factory()
.into_factory()
.map_err(|err| err.into().error_response());
HttpService::build()
.client_request_timeout(timeout)
.finish(map_config(fac, move |_| app_cfg.clone()))
.rustls_0_23(config.clone())
}),
},
}
.expect("test server could not be created");
@ -447,6 +491,8 @@ enum StreamType {
Rustls021(tls_rustls_0_21::ServerConfig),
#[cfg(feature = "rustls-0_22")]
Rustls022(tls_rustls_0_22::ServerConfig),
#[cfg(feature = "rustls-0_23")]
Rustls023(tls_rustls_0_23::ServerConfig),
}
/// Create default test server config.
@ -537,6 +583,13 @@ impl TestServerConfig {
self
}
/// Accepts secure connections via Rustls v0.23.
#[cfg(feature = "rustls-0_23")]
pub fn rustls_0_23(mut self, config: tls_rustls_0_23::ServerConfig) -> Self {
self.stream = StreamType::Rustls023(config);
self
}
/// Sets client timeout for first request.
pub fn client_request_timeout(mut self, dur: Duration) -> Self {
self.client_request_timeout = dur;

View file

@ -2,6 +2,8 @@
## Unreleased
- Minimum supported Rust version (MSRV) is now 1.72.
## 4.3.0
- Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.

View file

@ -32,6 +32,6 @@ actix-test = "0.1"
awc = { version = "3", default-features = false }
actix-web = { version = "4", features = ["macros"] }
env_logger = "0.10"
env_logger = "0.11"
futures-util = { version = "0.3.17", default-features = false, features = ["std"] }
mime = "0.3"

View file

@ -6,7 +6,7 @@
[![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors)
[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.3.0)](https://docs.rs/actix-web-actors/4.3.0)
![Version](https://img.shields.io/badge/rustc-1.68+-ab6000.svg)
![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg)
![License](https://img.shields.io/crates/l/actix-web-actors.svg)
<br />
[![dependency status](https://deps.rs/crate/actix-web-actors/4.3.0/status.svg)](https://deps.rs/crate/actix-web-actors/4.3.0)
@ -14,8 +14,3 @@
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
<!-- prettier-ignore-end -->
## Documentation & Resources
- [API Documentation](https://docs.rs/actix-web-actors)
- Minimum Supported Rust Version (MSRV): 1.68

View file

@ -248,13 +248,11 @@ where
mod tests {
use std::time::Duration;
use actix::Actor;
use actix_web::{
http::StatusCode,
test::{call_service, init_service, read_body, TestRequest},
web, App, HttpResponse,
};
use bytes::Bytes;
use super::*;

View file

@ -817,10 +817,7 @@ where
#[cfg(test)]
mod tests {
use actix_web::{
http::{header, Method},
test::TestRequest,
};
use actix_web::test::TestRequest;
use super::*;

View file

@ -2,6 +2,8 @@
## Unreleased
- Minimum supported Rust version (MSRV) is now 1.72.
## 4.2.2
- Fix regression when declaring `wrap` attribute using an expression.

View file

@ -2,14 +2,15 @@
name = "actix-web-codegen"
version = "4.2.2"
description = "Routing and runtime macros for Actix Web"
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web"
authors = [
"Nikolay Kim <fafhrd91@gmail.com>",
"Rob Ede <robjtede@icloud.com>",
]
license = "MIT OR Apache-2.0"
edition = "2021"
homepage.workspace = true
repository.workspace = true
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[lib]
proc-macro = true

View file

@ -6,7 +6,7 @@
[![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)
![Version](https://img.shields.io/badge/rustc-1.68+-ab6000.svg)
![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg)
![License](https://img.shields.io/crates/l/actix-web-codegen.svg)
<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)
@ -15,11 +15,6 @@
<!-- prettier-ignore-end -->
## Documentation & Resources
- [API Documentation](https://docs.rs/actix-web-codegen)
- Minimum Supported Rust Version (MSRV): 1.68
## Compile Testing
Uses the [`trybuild`] crate. All compile fail tests should include a stderr file generated by `trybuild`. See the [workflow section](https://github.com/dtolnay/trybuild#workflow) of the trybuild docs for info on how to do this.

View file

@ -1,4 +1,4 @@
#[rustversion::stable(1.68)] // MSRV
#[rustversion::stable(1.72)] // MSRV
#[test]
fn compile_macros() {
let t = trybuild::TestCases::new();

View file

@ -13,17 +13,20 @@ 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
|
| pub fn service<F>(mut self, factory: F) -> Self
| ------- required by a bound in this associated function
| where
| F: HttpServiceFactory + 'static,
| ^^^^^^^^^^^^^^^^^^ required by this bound in `App::<T>::service`

View file

@ -13,17 +13,20 @@ 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
|
| pub fn service<F>(mut self, factory: F) -> Self
| ------- required by a bound in this associated function
| where
| F: HttpServiceFactory + 'static,
| ^^^^^^^^^^^^^^^^^^ required by this bound in `App::<T>::service`

View file

@ -15,17 +15,20 @@ 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
|
| pub fn service<F>(mut self, factory: F) -> Self
| ------- required by a bound in this associated function
| where
| F: HttpServiceFactory + 'static,
| ^^^^^^^^^^^^^^^^^^ required by this bound in `App::<T>::service`

View file

@ -29,17 +29,20 @@ 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
|
| pub fn service<F>(mut self, factory: F) -> Self
| ------- required by a bound in this associated function
| where
| F: HttpServiceFactory + 'static,
| ^^^^^^^^^^^^^^^^^^ required by this bound in `App::<T>::service`

View file

@ -15,17 +15,20 @@ 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
|
| pub fn service<F>(mut self, factory: F) -> Self
| ------- required by a bound in this associated function
| where
| F: HttpServiceFactory + 'static,
| ^^^^^^^^^^^^^^^^^^ required by this bound in `App::<T>::service`

View file

@ -2,8 +2,34 @@
## Unreleased
## 4.6.0
### Added
- 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 `rustls-0_23` crate feature.
- Add `HttpServer::{bind_rustls_0_23, listen_rustls_0_23}()` builder methods.
- Add `HttpServer::tls_handshake_timeout()` builder method for `rustls-0_22` and `rustls-0_23`.
### Changed
- Update `brotli` dependency to `6`.
- Minimum supported Rust version (MSRV) is now 1.72.
### Fixed
- Avoid type confusion with `rustls` in some circumstances.
## 4.5.1
### Fixed
- Fix missing import when using enabling Rustls v0.22 support.
## 4.5.0
### Added
- Add `rustls-0_22` crate feature.
- Add `HttpServer::{bind_rustls_0_22, listen_rustls_0_22}()` builder methods.

View file

@ -1,6 +1,6 @@
[package]
name = "actix-web"
version = "4.5.0"
version = "4.6.0"
description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust"
authors = [
"Nikolay Kim <fafhrd91@gmail.com>",
@ -27,6 +27,7 @@ features = [
"rustls-0_20",
"rustls-0_21",
"rustls-0_22",
"rustls-0_23",
"compress-brotli",
"compress-gzip",
"compress-zstd",
@ -40,7 +41,7 @@ name = "actix_web"
path = "src/lib.rs"
[features]
default = ["macros", "compress-brotli", "compress-gzip", "compress-zstd", "cookies", "http2"]
default = ["macros", "compress-brotli", "compress-gzip", "compress-zstd", "cookies", "http2", "unicode"]
# Brotli algorithm content-encoding support
compress-brotli = ["actix-http/compress-brotli", "__compress"]
@ -71,6 +72,11 @@ rustls-0_20 = ["http2", "actix-http/rustls-0_20", "actix-tls/accept", "actix-tls
rustls-0_21 = ["http2", "actix-http/rustls-0_21", "actix-tls/accept", "actix-tls/rustls-0_21"]
# TLS via Rustls v0.22
rustls-0_22 = ["http2", "actix-http/rustls-0_22", "actix-tls/accept", "actix-tls/rustls-0_22"]
# TLS via Rustls v0.23
rustls-0_23 = ["http2", "actix-http/rustls-0_23", "actix-tls/accept", "actix-tls/rustls-0_23"]
# Full unicode support
unicode = ["dep:regex", "actix-router/unicode"]
# Internal (PRIVATE!) features used to aid testing and checking feature status.
# Don't rely on these whatsoever. They may disappear at anytime.
@ -86,10 +92,10 @@ actix-rt = { version = "2.6", default-features = false }
actix-server = "2"
actix-service = "2"
actix-utils = "3"
actix-tls = { version = "3.3", default-features = false, optional = true }
actix-tls = { version = "3.4", default-features = false, optional = true }
actix-http = { version = "3.6", features = ["ws"] }
actix-router = "0.5"
actix-http = { version = "3.7", features = ["ws"] }
actix-router = { version = "0.5.3", default-features = false, features = ["http"] }
actix-web-codegen = { version = "4.2", optional = true }
ahash = "0.8"
@ -107,7 +113,8 @@ log = "0.4"
mime = "0.3"
once_cell = "1.5"
pin-project-lite = "0.2.7"
regex = "1.5.5"
regex = { version = "1.5.5", optional = true }
regex-lite = "0.1"
serde = "1.0"
serde_json = "1.0"
serde_urlencoded = "0.7"
@ -118,22 +125,22 @@ url = "2.1"
[dev-dependencies]
actix-files = "0.6"
actix-test = { version = "0.1", features = ["openssl", "rustls-0_21"] }
actix-test = { version = "0.1", features = ["openssl", "rustls-0_23"] }
awc = { version = "3", features = ["openssl"] }
brotli = "3.3.3"
brotli = "6"
const-str = "0.5"
criterion = { version = "0.5", features = ["html_reports"] }
env_logger = "0.10"
env_logger = "0.11"
flate2 = "1.0.13"
futures-util = { version = "0.3.17", default-features = false, features = ["std"] }
rand = "0.8"
rcgen = "0.11"
rustls-pemfile = "1"
rcgen = "0.13"
rustls-pemfile = "2"
serde = { version = "1.0", features = ["derive"] }
static_assertions = "1"
tls-openssl = { package = "openssl", version = "0.10.55" }
tls-rustls = { package = "rustls", version = "0.21" }
tls-rustls = { package = "rustls", version = "0.23" }
tokio = { version = "1.24.2", features = ["rt-multi-thread", "macros"] }
zstd = "0.13"

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.5.0)](https://docs.rs/actix-web/4.5.0)
![MSRV](https://img.shields.io/badge/rustc-1.68+-ab6000.svg)
[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.6.0)](https://docs.rs/actix-web/4.6.0)
![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.5.0/status.svg)](https://deps.rs/crate/actix-web/4.5.0)
[![Dependency Status](https://deps.rs/crate/actix-web/4.6.0/status.svg)](https://deps.rs/crate/actix-web/4.6.0)
<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/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web)
@ -37,7 +37,7 @@
- SSL support using OpenSSL or Rustls
- Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/))
- Integrates with the [`awc` HTTP client](https://docs.rs/awc/)
- Runs on stable Rust 1.68+
- Runs on stable Rust 1.72+
## Documentation

View file

@ -471,7 +471,6 @@ mod tests {
Method, StatusCode,
},
middleware::DefaultHeaders,
service::ServiceRequest,
test::{call_service, init_service, read_body, try_init_service, TestRequest},
web, HttpRequest, HttpResponse,
};

View file

@ -263,8 +263,9 @@ impl ServiceFactory<ServiceRequest> for AppRoutingFactory {
let guards = guards.borrow_mut().take().unwrap_or_default();
let factory_fut = factory.new_service(());
async move {
let service = factory_fut.await?;
Ok((path, guards, service))
factory_fut
.await
.map(move |service| (path, guards, service))
}
}));

View file

@ -148,7 +148,7 @@ impl AppConfig {
#[cfg(test)]
pub(crate) fn set_host(&mut self, host: &str) {
self.host = host.to_owned();
host.clone_into(&mut self.host);
}
}

View file

@ -380,7 +380,7 @@ impl Guard for HeaderGuard {
#[cfg(test)]
mod tests {
use actix_http::{header, Method};
use actix_http::Method;
use super::*;
use crate::test::TestRequest;

View file

@ -13,7 +13,10 @@
use std::fmt::{self, Write};
use once_cell::sync::Lazy;
#[cfg(feature = "unicode")]
use regex::Regex;
#[cfg(not(feature = "unicode"))]
use regex_lite::Regex;
use super::{ExtendedValue, Header, TryIntoHeaderValue, Writer};
use crate::http::header;

View file

@ -126,7 +126,7 @@ mod tests {
use std::fmt;
use super::*;
use crate::{http::header::Header, test::TestRequest, HttpRequest};
use crate::{test::TestRequest, HttpRequest};
fn req_from_raw_headers<H: Header, I: IntoIterator<Item = V>, V: AsRef<[u8]>>(
header_lines: I,

View file

@ -64,7 +64,10 @@
//! - `compress-gzip` - gzip and deflate content encoding compression support (enabled by default)
//! - `compress-zstd` - zstd content encoding compression support (enabled by default)
//! - `openssl` - HTTPS support via `openssl` crate, supports `HTTP/2`
//! - `rustls` - HTTPS support via `rustls` crate, supports `HTTP/2`
//! - `rustls` - HTTPS support via `rustls` 0.20 crate, supports `HTTP/2`
//! - `rustls-0_21` - HTTPS support via `rustls` 0.21 crate, supports `HTTP/2`
//! - `rustls-0_22` - HTTPS support via `rustls` 0.22 crate, supports `HTTP/2`
//! - `rustls-0_23` - HTTPS support via `rustls` 0.23 crate, supports `HTTP/2`
//! - `secure-cookies` - secure cookies support
#![deny(rust_2018_idioms, nonstandard_style)]

View file

@ -33,7 +33,7 @@ use crate::{
/// considered in this selection process.
///
/// # Pre-compressed Payload
/// If you are serving some data is already using a compressed representation (e.g., a gzip
/// If you are serving some data that is already using a compressed representation (e.g., a gzip
/// compressed HTML file from disk) you can signal this to `Compress` by setting an appropriate
/// `Content-Encoding` header. In addition to preventing double compressing the payload, this header
/// is required by the spec when using compressed representations and will inform the client that

View file

@ -135,7 +135,7 @@ mod tests {
use super::*;
use crate::{
body::BoxBody,
dev::{ServiceRequest, ServiceResponse},
dev::ServiceRequest,
error::Result,
http::{
header::{HeaderValue, CONTENT_TYPE},

View file

@ -190,8 +190,6 @@ mod tests {
use super::*;
use crate::{
dev::ServiceRequest,
http::header::CONTENT_TYPE,
test::{self, TestRequest},
HttpResponse,
};

View file

@ -407,10 +407,7 @@ mod tests {
use super::*;
use crate::{
body,
http::{
header::{HeaderValue, CONTENT_TYPE},
StatusCode,
},
http::header::{HeaderValue, CONTENT_TYPE},
test::{self, TestRequest},
};

View file

@ -18,7 +18,10 @@ use bytes::Bytes;
use futures_core::ready;
use log::{debug, warn};
use pin_project_lite::pin_project;
use regex::{Regex, RegexSet};
#[cfg(feature = "unicode")]
use regex::Regex;
#[cfg(not(feature = "unicode"))]
use regex_lite::Regex;
use time::{format_description::well_known::Rfc3339, OffsetDateTime};
use crate::{
@ -87,7 +90,7 @@ pub struct Logger(Rc<Inner>);
struct Inner {
format: Format,
exclude: HashSet<String>,
exclude_regex: RegexSet,
exclude_regex: Vec<Regex>,
log_target: Cow<'static, str>,
}
@ -97,7 +100,7 @@ impl Logger {
Logger(Rc::new(Inner {
format: Format::new(format),
exclude: HashSet::new(),
exclude_regex: RegexSet::empty(),
exclude_regex: Vec::new(),
log_target: Cow::Borrowed(module_path!()),
}))
}
@ -114,10 +117,7 @@ impl Logger {
/// Ignore and do not log access info for paths that match regex.
pub fn exclude_regex<T: Into<String>>(mut self, path: T) -> Self {
let inner = Rc::get_mut(&mut self.0).unwrap();
let mut patterns = inner.exclude_regex.patterns().to_vec();
patterns.push(path.into());
let regex_set = RegexSet::new(patterns).unwrap();
inner.exclude_regex = regex_set;
inner.exclude_regex.push(Regex::new(&path.into()).unwrap());
self
}
@ -240,7 +240,7 @@ impl Default for Logger {
Logger(Rc::new(Inner {
format: Format::default(),
exclude: HashSet::new(),
exclude_regex: RegexSet::empty(),
exclude_regex: Vec::new(),
log_target: Cow::Borrowed(module_path!()),
}))
}
@ -300,7 +300,11 @@ where
fn call(&self, req: ServiceRequest) -> Self::Future {
let excluded = self.inner.exclude.contains(req.path())
|| self.inner.exclude_regex.is_match(req.path());
|| self
.inner
.exclude_regex
.iter()
.any(|r| r.is_match(req.path()));
if excluded {
LoggerResponse {
@ -716,7 +720,7 @@ impl<'a> fmt::Display for FormatDisplay<'a> {
#[cfg(test)]
mod tests {
use actix_service::{IntoService, Service, Transform};
use actix_service::IntoService;
use actix_utils::future::ok;
use super::*;

View file

@ -33,13 +33,13 @@
//!
//! # fn main() {
//! # // These aren't snake_case, because they are supposed to be unit structs.
//! # let MiddlewareA = middleware::Compress::default();
//! # let MiddlewareB = middleware::Compress::default();
//! # let MiddlewareC = middleware::Compress::default();
//! # type MiddlewareA = middleware::Compress;
//! # type MiddlewareB = middleware::Compress;
//! # type MiddlewareC = middleware::Compress;
//! let app = App::new()
//! .wrap(MiddlewareA)
//! .wrap(MiddlewareB)
//! .wrap(MiddlewareC)
//! .wrap(MiddlewareA::default())
//! .wrap(MiddlewareB::default())
//! .wrap(MiddlewareC::default())
//! .service(service);
//! # }
//! ```

View file

@ -4,7 +4,10 @@ use actix_http::uri::{PathAndQuery, Uri};
use actix_service::{Service, Transform};
use actix_utils::future::{ready, Ready};
use bytes::Bytes;
#[cfg(feature = "unicode")]
use regex::Regex;
#[cfg(not(feature = "unicode"))]
use regex_lite::Regex;
use crate::{
service::{ServiceRequest, ServiceResponse},
@ -205,7 +208,6 @@ mod tests {
use super::*;
use crate::{
dev::ServiceRequest,
guard::fn_guard,
test::{call_service, init_service, TestRequest},
web, App, HttpResponse,

View file

@ -182,7 +182,7 @@ impl Responder for Redirect {
#[cfg(test)]
mod tests {
use super::*;
use crate::{dev::Service, http::StatusCode, test, App};
use crate::{dev::Service, test, App};
#[actix_rt::test]
async fn absolute_redirects() {

View file

@ -523,7 +523,7 @@ mod tests {
use super::*;
use crate::{
dev::{ResourceDef, ResourceMap, Service},
dev::{ResourceDef, Service},
http::{header, StatusCode},
test::{self, call_service, init_service, read_body, TestRequest},
web, App, HttpResponse,

View file

@ -540,20 +540,14 @@ mod tests {
use std::time::Duration;
use actix_rt::time::sleep;
use actix_service::Service;
use actix_utils::future::ok;
use super::*;
use crate::{
guard,
http::{
header::{self, HeaderValue},
Method, StatusCode,
},
http::{header::HeaderValue, Method, StatusCode},
middleware::DefaultHeaders,
service::{ServiceRequest, ServiceResponse},
test::{call_service, init_service, TestRequest},
web, App, Error, HttpMessage, HttpResponse,
App, HttpMessage,
};
#[test]
@ -777,7 +771,7 @@ mod tests {
data3: web::Data<f64>| {
assert_eq!(**data1, 10);
assert_eq!(**data2, '*');
let error = std::f64::EPSILON;
let error = f64::EPSILON;
assert!((**data3 - 1.0).abs() < error);
HttpResponse::Ok()
},

View file

@ -408,10 +408,7 @@ mod tests {
use super::*;
use crate::{
body,
http::{
header::{self, HeaderValue, CONTENT_TYPE},
StatusCode,
},
http::header::{HeaderValue, CONTENT_TYPE},
test::assert_body_eq,
};

View file

@ -175,10 +175,7 @@ mod tests {
use super::*;
use crate::{
http::{
header::{HeaderValue, CONTENT_TYPE},
StatusCode,
},
http::header::{HeaderValue, CONTENT_TYPE},
test::TestRequest,
};

View file

@ -188,15 +188,11 @@ impl_into_string_responder!(Cow<'_, str>);
pub(crate) mod tests {
use actix_http::body::to_bytes;
use actix_service::Service;
use bytes::{Bytes, BytesMut};
use super::*;
use crate::{
error,
http::{
header::{HeaderValue, CONTENT_TYPE},
StatusCode,
},
http::header::{HeaderValue, CONTENT_TYPE},
test::{assert_body_eq, init_service, TestRequest},
web, App,
};

View file

@ -399,7 +399,7 @@ mod tests {
use static_assertions::assert_impl_all;
use super::*;
use crate::http::header::{HeaderValue, COOKIE};
use crate::http::header::COOKIE;
assert_impl_all!(HttpResponse: Responder);
assert_impl_all!(HttpResponse<String>: Responder);

View file

@ -92,6 +92,7 @@ pub struct RouteService {
}
impl RouteService {
// TODO(breaking): remove pass by ref mut
#[allow(clippy::needless_pass_by_ref_mut)]
pub fn check(&self, req: &mut ServiceRequest) -> bool {
let guard_ctx = req.guard_ctx();

Some files were not shown because too many files have changed in this diff Show more