From 83cd061c862e82931895a8003fef5c191db0e5dd Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Tue, 25 Oct 2022 23:37:04 +0800 Subject: [PATCH 01/24] remove fakeshadow from author lists (#2921) --- actix-files/Cargo.toml | 1 - awc/Cargo.toml | 5 +---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 33de0e6d9..018acdfb1 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -3,7 +3,6 @@ name = "actix-files" version = "0.6.2" authors = [ "Nikolay Kim ", - "fakeshadow <24548779@qq.com>", "Rob Ede ", ] description = "Static file serving for Actix Web" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 2f0027725..e7ac43d22 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,10 +1,7 @@ [package] name = "awc" version = "3.0.1" -authors = [ - "Nikolay Kim ", - "fakeshadow <24548779@qq.com>", -] +authors = ["Nikolay Kim "] description = "Async HTTP and WebSocket client library" keywords = ["actix", "http", "framework", "async", "web"] categories = [ From a2e2c30d59a2b04880c03953e3d2bb7c7ba48b10 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 30 Oct 2022 19:47:49 +0000 Subject: [PATCH 02/24] use tokio-util deps directly where possible --- .github/workflows/upload-doc.yml | 2 +- actix-http/Cargo.toml | 2 ++ actix-http/examples/ws.rs | 2 +- actix-http/src/h1/client.rs | 2 +- actix-http/src/h1/codec.rs | 2 +- actix-http/src/h1/dispatcher.rs | 6 ++++-- actix-http/src/ws/codec.rs | 2 +- actix-http/src/ws/dispatcher.rs | 4 +++- actix-web-actors/Cargo.toml | 1 + actix-web-actors/src/ws.rs | 2 +- actix-web/src/http/header/accept.rs | 10 +++++----- 11 files changed, 21 insertions(+), 14 deletions(-) diff --git a/.github/workflows/upload-doc.yml b/.github/workflows/upload-doc.yml index 07f839e34..c47ea1d70 100644 --- a/.github/workflows/upload-doc.yml +++ b/.github/workflows/upload-doc.yml @@ -28,7 +28,7 @@ jobs: run: echo '' > target/doc/index.html - name: Deploy to GitHub Pages - uses: JamesIves/github-pages-deploy-action@v4.4.0 + uses: JamesIves/github-pages-deploy-action@v4.4.1 with: folder: target/doc single-commit: true diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 30e436160..a8b888ef4 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -77,6 +77,8 @@ mime = "0.3" percent-encoding = "2.1" pin-project-lite = "0.2" smallvec = "1.6.1" +tokio = { version = "1.13.1", features = [] } +tokio-util = { version = "0.7", features = ["io", "codec"] } tracing = { version = "0.1.30", default-features = false, features = ["log"] } # http2 diff --git a/actix-http/examples/ws.rs b/actix-http/examples/ws.rs index c4f0503cd..6af6d5095 100644 --- a/actix-http/examples/ws.rs +++ b/actix-http/examples/ws.rs @@ -10,13 +10,13 @@ use std::{ time::Duration, }; -use actix_codec::Encoder; use actix_http::{body::BodyStream, error::Error, ws, HttpService, Request, Response}; use actix_rt::time::{interval, Interval}; use actix_server::Server; use bytes::{Bytes, BytesMut}; use bytestring::ByteString; use futures_core::{ready, Stream}; +use tokio_util::codec::Encoder; use tracing::{info, trace}; #[actix_rt::main] diff --git a/actix-http/src/h1/client.rs b/actix-http/src/h1/client.rs index 75c88d00c..6a0d531d0 100644 --- a/actix-http/src/h1/client.rs +++ b/actix-http/src/h1/client.rs @@ -1,9 +1,9 @@ use std::{fmt, io}; -use actix_codec::{Decoder, Encoder}; use bitflags::bitflags; use bytes::{Bytes, BytesMut}; use http::{Method, Version}; +use tokio_util::codec::{Decoder, Encoder}; use super::{ decoder::{self, PayloadDecoder, PayloadItem, PayloadType}, diff --git a/actix-http/src/h1/codec.rs b/actix-http/src/h1/codec.rs index 80afd7455..e11f175c9 100644 --- a/actix-http/src/h1/codec.rs +++ b/actix-http/src/h1/codec.rs @@ -1,9 +1,9 @@ use std::{fmt, io}; -use actix_codec::{Decoder, Encoder}; use bitflags::bitflags; use bytes::BytesMut; use http::{Method, Version}; +use tokio_util::codec::{Decoder, Encoder}; use super::{ decoder::{self, PayloadDecoder, PayloadItem, PayloadType}, diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 81090667d..60660b85b 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -8,13 +8,15 @@ use std::{ task::{Context, Poll}, }; -use actix_codec::{AsyncRead, AsyncWrite, Decoder as _, Encoder as _, Framed, FramedParts}; +use actix_codec::{Framed, FramedParts}; use actix_rt::time::sleep_until; use actix_service::Service; use bitflags::bitflags; use bytes::{Buf, BytesMut}; use futures_core::ready; use pin_project_lite::pin_project; +use tokio::io::{AsyncRead, AsyncWrite}; +use tokio_util::codec::{Decoder as _, Encoder as _}; use tracing::{error, trace}; use crate::{ @@ -1004,7 +1006,7 @@ where this.read_buf.reserve(HW_BUFFER_SIZE - remaining); } - match actix_codec::poll_read_buf(io.as_mut(), cx, this.read_buf) { + match tokio_util::io::poll_read_buf(io.as_mut(), cx, this.read_buf) { Poll::Ready(Ok(n)) => { this.flags.remove(Flags::FINISHED); diff --git a/actix-http/src/ws/codec.rs b/actix-http/src/ws/codec.rs index 4a2e741b6..6a149f9a4 100644 --- a/actix-http/src/ws/codec.rs +++ b/actix-http/src/ws/codec.rs @@ -1,7 +1,7 @@ -use actix_codec::{Decoder, Encoder}; use bitflags::bitflags; use bytes::{Bytes, BytesMut}; use bytestring::ByteString; +use tokio_util::codec::{Decoder, Encoder}; use tracing::error; use super::{ diff --git a/actix-http/src/ws/dispatcher.rs b/actix-http/src/ws/dispatcher.rs index 2f6b2363b..396f1e86c 100644 --- a/actix-http/src/ws/dispatcher.rs +++ b/actix-http/src/ws/dispatcher.rs @@ -76,7 +76,9 @@ mod inner { use pin_project_lite::pin_project; use tracing::debug; - use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed}; + use actix_codec::Framed; + use tokio::io::{AsyncRead, AsyncWrite}; + use tokio_util::codec::{Decoder, Encoder}; use crate::{body::BoxBody, Response}; diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 8222fc864..26b1c09de 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -24,6 +24,7 @@ bytestring = "1" futures-core = { version = "0.3.7", default-features = false } pin-project-lite = "0.2" tokio = { version = "1.13.1", features = ["sync"] } +tokio-util = { version = "0.7", features = ["codec"] } [dev-dependencies] actix-rt = "2.2" diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index 9a4880159..e1110eddb 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -74,7 +74,6 @@ use actix::{ Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message as ActixMessage, SpawnHandle, }; -use actix_codec::{Decoder as _, Encoder as _}; use actix_http::ws::{hash_key, Codec}; pub use actix_http::ws::{ CloseCode, CloseReason, Frame, HandshakeError, Message, ProtocolError, @@ -92,6 +91,7 @@ use bytestring::ByteString; use futures_core::Stream; use pin_project_lite::pin_project; use tokio::sync::oneshot; +use tokio_util::codec::{Decoder as _, Encoder as _}; /// Builder for Websocket session response. /// diff --git a/actix-web/src/http/header/accept.rs b/actix-web/src/http/header/accept.rs index 744c9b6e8..1be136b19 100644 --- a/actix-web/src/http/header/accept.rs +++ b/actix-web/src/http/header/accept.rs @@ -6,8 +6,7 @@ use super::{common_header, QualityItem}; use crate::http::header; common_header! { - /// `Accept` header, defined - /// in [RFC 7231 §5.3.2](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2) + /// `Accept` header, defined in [RFC 7231 §5.3.2]. /// /// The `Accept` header field can be used by user agents to specify /// response media types that are acceptable. Accept header fields can @@ -71,6 +70,8 @@ common_header! { /// ]) /// ); /// ``` + /// + /// [RFC 7231 §5.3.2]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2 (Accept, header::ACCEPT) => (QualityItem)* test_parse_and_format { @@ -101,13 +102,12 @@ common_header! { vec![b"text/plain; charset=utf-8"], Some(Accept(vec![ QualityItem::max(mime::TEXT_PLAIN_UTF_8), - ]))); + ]))); crate::http::header::common_header_test!( test4, vec![b"text/plain; charset=utf-8; q=0.5"], Some(Accept(vec![ - QualityItem::new(mime::TEXT_PLAIN_UTF_8, - q(0.5)), + QualityItem::new(mime::TEXT_PLAIN_UTF_8, q(0.5)), ]))); #[test] From 45b77c68195eb933231290a09e9f6a0cca56aad8 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 4 Nov 2022 02:42:22 +0200 Subject: [PATCH 03/24] GitHub Workflows security hardening (#2923) --- .github/workflows/bench.yml | 3 +++ .github/workflows/ci-post-merge.yml | 3 +++ .github/workflows/ci.yml | 3 +++ .github/workflows/upload-doc.yml | 4 ++++ 4 files changed, 13 insertions(+) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index a4b54ca7a..008c33f89 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -5,6 +5,9 @@ on: branches: - master +permissions: + contents: read # to fetch code (actions/checkout) + jobs: check_benchmark: runs-on: ubuntu-latest diff --git a/.github/workflows/ci-post-merge.yml b/.github/workflows/ci-post-merge.yml index 1ee97b591..6d76301d8 100644 --- a/.github/workflows/ci-post-merge.yml +++ b/.github/workflows/ci-post-merge.yml @@ -4,6 +4,9 @@ on: push: branches: [master] +permissions: + contents: read # to fetch code (actions/checkout) + jobs: build_and_test_nightly: strategy: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de1e1fe18..07e21ef43 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,9 @@ on: push: branches: [master] +permissions: + contents: read # to fetch code (actions/checkout) + jobs: build_and_test: strategy: diff --git a/.github/workflows/upload-doc.yml b/.github/workflows/upload-doc.yml index c47ea1d70..ac181b3f9 100644 --- a/.github/workflows/upload-doc.yml +++ b/.github/workflows/upload-doc.yml @@ -4,8 +4,12 @@ on: push: branches: [master] +permissions: {} jobs: build: + permissions: + contents: write # to push changes in repo (jamesives/github-pages-deploy-action) + runs-on: ubuntu-latest steps: From 10650435281bd98027b3b60a2b26bb8a243f0b02 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 25 Nov 2022 17:00:59 +0000 Subject: [PATCH 04/24] ci: use dtolnay's rust-toolchain action --- .github/workflows/ci-post-merge.yml | 33 ++++++------------------ .github/workflows/ci.yml | 23 ++++------------- .github/workflows/clippy-fmt.yml | 39 ++++++++--------------------- .github/workflows/upload-doc.yml | 14 +++-------- 4 files changed, 28 insertions(+), 81 deletions(-) diff --git a/.github/workflows/ci-post-merge.yml b/.github/workflows/ci-post-merge.yml index 6d76301d8..7ac6388d4 100644 --- a/.github/workflows/ci-post-merge.yml +++ b/.github/workflows/ci-post-merge.yml @@ -95,29 +95,21 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Install stable - uses: actions-rs/toolchain@v1 - with: - toolchain: stable-x86_64-unknown-linux-gnu - profile: minimal - override: true + - uses: dtolnay/rust-toolchain@stable - name: Install cargo-hack uses: taiki-e/install-action@cargo-hack - name: Generate Cargo.lock - uses: actions-rs/cargo@v1 - with: { command: generate-lockfile } + run: cargo generate-lockfile - name: Cache Dependencies uses: Swatinem/rust-cache@v1.2.0 - name: check feature combinations - uses: actions-rs/cargo@v1 - with: { command: ci-check-all-feature-powerset } - + run: cargo ci-check-all-feature-powerset + - name: check feature combinations - uses: actions-rs/cargo@v1 - with: { command: ci-check-all-feature-powerset-linux } + run: cargo ci-check-all-feature-powerset-linux nextest: name: nextest @@ -130,24 +122,15 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - override: true + - uses: dtolnay/rust-toolchain@stable - name: Install nextest uses: taiki-e/install-action@nextest - name: Generate Cargo.lock - uses: actions-rs/cargo@v1 - with: { command: generate-lockfile } + run: cargo generate-lockfile - name: Cache Dependencies uses: Swatinem/rust-cache@v1.3.0 - name: Test with cargo-nextest - uses: actions-rs/cargo@v1 - with: - command: nextest - args: run + run: cargo nextest run diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 07e21ef43..b45f5469f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -99,16 +99,10 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable-x86_64-unknown-linux-gnu - profile: minimal - override: true + - uses: dtolnay/rust-toolchain@stable - name: Generate Cargo.lock - uses: actions-rs/cargo@v1 - with: { command: generate-lockfile } + run: cargo generate-lockfile - name: Cache Dependencies uses: Swatinem/rust-cache@v1.3.0 @@ -126,20 +120,13 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Install Rust (nightly) - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly-x86_64-unknown-linux-gnu - profile: minimal - override: true + - uses: dtolnay/rust-toolchain@nightly - name: Generate Cargo.lock - uses: actions-rs/cargo@v1 - with: { command: generate-lockfile } + run: cargo generate-lockfile - name: Cache Dependencies uses: Swatinem/rust-cache@v1.3.0 - name: doc tests - uses: actions-rs/cargo@v1 + run: cargo ci-doctest timeout-minutes: 60 - with: { command: ci-doctest } diff --git a/.github/workflows/clippy-fmt.yml b/.github/workflows/clippy-fmt.yml index bc2cec145..1587a0b1b 100644 --- a/.github/workflows/clippy-fmt.yml +++ b/.github/workflows/clippy-fmt.yml @@ -9,54 +9,37 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - components: rustfmt - - name: Check with rustfmt - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check + - uses: dtolnay/rust-toolchain@nightly + - run: cargo fmt --all -- --check clippy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - components: clippy - override: true + - uses: dtolnay/rust-toolchain@stable + with: { components: clippy } - name: Generate Cargo.lock uses: actions-rs/cargo@v1 - with: { command: generate-lockfile } + run: cargo generate-lockfile - name: Cache Dependencies uses: Swatinem/rust-cache@v1.2.0 - + - name: Check with Clippy uses: actions-rs/clippy-check@v1 with: - token: ${{ secrets.GITHUB_TOKEN }} args: --workspace --tests --examples --all-features + token: ${{ secrets.GITHUB_TOKEN }} lint-docs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - components: rust-docs + + - uses: dtolnay/rust-toolchain@stable + with: { components: rust-docs } + - name: Check for broken intra-doc links uses: actions-rs/cargo@v1 env: diff --git a/.github/workflows/upload-doc.yml b/.github/workflows/upload-doc.yml index ac181b3f9..9aadafafc 100644 --- a/.github/workflows/upload-doc.yml +++ b/.github/workflows/upload-doc.yml @@ -15,18 +15,12 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly-x86_64-unknown-linux-gnu - profile: minimal - override: true + - uses: dtolnay/rust-toolchain@nightly - name: Build Docs - uses: actions-rs/cargo@v1 - with: - command: doc - args: --workspace --all-features --no-deps + run: cargo +nightly doc --no-deps --workspace --all-features + env: + RUSTDOCFLAGS: --cfg=docsrs - name: Tweak HTML run: echo '' > target/doc/index.html From fcd06c9896c3ec53032be52ed94e187b30c99bc7 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 25 Nov 2022 17:28:06 +0000 Subject: [PATCH 05/24] workaround zstd msrv issue --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b45f5469f..f1c839e8e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,6 +59,7 @@ jobs: cargo install cargo-edit --version=0.8.0 cargo add const-str@0.3 --dev -p=actix-web cargo add const-str@0.3 --dev -p=awc + cargo update -p=zstd-sys --precise=2.0.1+zstd.1.5.2 - name: Generate Cargo.lock uses: actions-rs/cargo@v1 From d97bd7ec17d0dfd67d43f96abf8d8cfd5239e895 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 25 Nov 2022 17:37:23 +0000 Subject: [PATCH 06/24] fix msrv CI --- .github/workflows/ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f1c839e8e..421becc63 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,7 +59,6 @@ jobs: cargo install cargo-edit --version=0.8.0 cargo add const-str@0.3 --dev -p=actix-web cargo add const-str@0.3 --dev -p=awc - cargo update -p=zstd-sys --precise=2.0.1+zstd.1.5.2 - name: Generate Cargo.lock uses: actions-rs/cargo@v1 @@ -67,6 +66,11 @@ jobs: - name: Cache Dependencies uses: Swatinem/rust-cache@v1.2.0 + - name: workaround MSRV issues + if: matrix.version != 'stable' + run: | + cargo update -p=zstd-sys --precise=2.0.1+zstd.1.5.2 + - name: check minimal uses: actions-rs/cargo@v1 with: { command: ci-check-min } From d708a4de6d0c7efb54520f48d0a3efaa28db19da Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 25 Nov 2022 21:04:24 +0000 Subject: [PATCH 07/24] add acceptable guard (#2265) --- actix-web/CHANGES.md | 2 + actix-web/src/guard/acceptable.rs | 99 ++++++++++++++++++++++++ actix-web/src/{guard.rs => guard/mod.rs} | 3 + 3 files changed, 104 insertions(+) create mode 100644 actix-web/src/guard/acceptable.rs rename actix-web/src/{guard.rs => guard/mod.rs} (99%) diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index a018bc248..06a2cccc9 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -5,7 +5,9 @@ - Add `ContentDisposition::attachment` constructor. [#2867] - Add `ErrorHandlers::default_handler()` (as well as `default_handler_{server, client}()`) to make registering handlers for groups of response statuses easier. [#2784] - Add `Logger::custom_response_replace()`. [#2631] +- Add `guard::Acceptable` for matching against `Accept` header mime types. [#2265] +[#2265]: https://github.com/actix/actix-web/pull/2265 [#2631]: https://github.com/actix/actix-web/pull/2631 [#2784]: https://github.com/actix/actix-web/pull/2784 [#2867]: https://github.com/actix/actix-web/pull/2867 diff --git a/actix-web/src/guard/acceptable.rs b/actix-web/src/guard/acceptable.rs new file mode 100644 index 000000000..a31494a18 --- /dev/null +++ b/actix-web/src/guard/acceptable.rs @@ -0,0 +1,99 @@ +use super::{Guard, GuardContext}; +use crate::http::header::Accept; + +/// A guard that verifies that an `Accept` header is present and it contains a compatible MIME type. +/// +/// An exception is that matching `*/*` must be explicitly enabled because most browsers send this +/// as part of their `Accept` header for almost every request. +/// +/// # Examples +/// ``` +/// use actix_web::{guard::Acceptable, web, HttpResponse}; +/// +/// web::resource("/images") +/// .guard(Acceptable::new(mime::IMAGE_STAR)) +/// .default_service(web::to(|| async { +/// HttpResponse::Ok().body("only called when images responses are acceptable") +/// })); +/// ``` +#[derive(Debug, Clone)] +pub struct Acceptable { + mime: mime::Mime, + + /// Wether to match `*/*` mime type. + /// + /// Defaults to false because it's not very useful otherwise. + match_star_star: bool, +} + +impl Acceptable { + /// Constructs new `Acceptable` guard with the given `mime` type/pattern. + pub fn new(mime: mime::Mime) -> Self { + Self { + mime, + match_star_star: false, + } + } + + /// Allows `*/*` in the `Accept` header to pass the guard check. + pub fn match_star_star(mut self) -> Self { + self.match_star_star = true; + self + } +} + +impl Guard for Acceptable { + fn check(&self, ctx: &GuardContext<'_>) -> bool { + let accept = match ctx.header::() { + Some(hdr) => hdr, + None => return false, + }; + + let target_type = self.mime.type_(); + let target_subtype = self.mime.subtype(); + + for mime in accept.0.into_iter().map(|q| q.item) { + return match (mime.type_(), mime.subtype()) { + (typ, subtype) if typ == target_type && subtype == target_subtype => true, + (typ, mime::STAR) if typ == target_type => true, + (mime::STAR, mime::STAR) if self.match_star_star => true, + _ => continue, + }; + } + + false + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{http::header, test::TestRequest}; + + #[test] + fn test_acceptable() { + let req = TestRequest::default().to_srv_request(); + assert!(!Acceptable::new(mime::APPLICATION_JSON).check(&req.guard_ctx())); + + let req = TestRequest::default() + .insert_header((header::ACCEPT, "application/json")) + .to_srv_request(); + assert!(Acceptable::new(mime::APPLICATION_JSON).check(&req.guard_ctx())); + + let req = TestRequest::default() + .insert_header((header::ACCEPT, "text/html, application/json")) + .to_srv_request(); + assert!(Acceptable::new(mime::APPLICATION_JSON).check(&req.guard_ctx())); + } + + #[test] + fn test_acceptable_star() { + let req = TestRequest::default() + .insert_header((header::ACCEPT, "text/html, */*;q=0.8")) + .to_srv_request(); + + assert!(Acceptable::new(mime::APPLICATION_JSON) + .match_star_star() + .check(&req.guard_ctx())); + } +} diff --git a/actix-web/src/guard.rs b/actix-web/src/guard/mod.rs similarity index 99% rename from actix-web/src/guard.rs rename to actix-web/src/guard/mod.rs index ef1301075..5fcaec0de 100644 --- a/actix-web/src/guard.rs +++ b/actix-web/src/guard/mod.rs @@ -56,6 +56,9 @@ use actix_http::{header, uri::Uri, Extensions, Method as HttpMethod, RequestHead use crate::{http::header::Header, service::ServiceRequest, HttpMessage as _}; +mod acceptable; +pub use self::acceptable::Acceptable; + /// Provides access to request parts that are useful during routing. #[derive(Debug)] pub struct GuardContext<'a> { From e7c34f2e45125d00c899994473212703731fd605 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 25 Nov 2022 21:38:36 +0000 Subject: [PATCH 08/24] tweak form docs --- actix-web/src/types/form.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/actix-web/src/types/form.rs b/actix-web/src/types/form.rs index 9c09c6b73..d73f8ba74 100644 --- a/actix-web/src/types/form.rs +++ b/actix-web/src/types/form.rs @@ -35,6 +35,7 @@ use crate::{ /// /// Use [`FormConfig`] to configure extraction options. /// +/// ## Examples /// ``` /// use actix_web::{post, web}; /// use serde::Deserialize; @@ -46,20 +47,18 @@ use crate::{ /// /// // This handler is only called if: /// // - request headers declare the content type as `application/x-www-form-urlencoded` -/// // - request payload is deserialized into a `Info` struct from the URL encoded format +/// // - request payload deserializes into an `Info` struct from the URL encoded format /// #[post("/")] -/// async fn index(form: web::Form) -> String { +/// async fn index(web::Form(form): web::Form) -> String { /// format!("Welcome {}!", form.name) /// } /// ``` /// /// # Responder -/// The `Form` type also allows you to create URL encoded responses: -/// simply return a value of type Form where T is the type to be URL encoded. -/// The type must implement [`serde::Serialize`]. -/// -/// Responses use +/// The `Form` type also allows you to create URL encoded responses by returning a value of type +/// `Form` where `T` is the type to be URL encoded, as long as `T` implements [`Serialize`]. /// +/// ## Examples /// ``` /// use actix_web::{get, web}; /// use serde::Serialize; @@ -77,7 +76,7 @@ use crate::{ /// #[get("/")] /// async fn index() -> web::Form { /// web::Form(SomeForm { -/// name: "actix".into(), +/// name: "actix".to_owned(), /// age: 123 /// }) /// } From 3c69d078b2ad35672394f00d6d8acf1985a63bb8 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 25 Nov 2022 21:44:52 +0000 Subject: [PATCH 09/24] add redirect service (#1961) --- .github/workflows/clippy-fmt.yml | 2 +- actix-web/CHANGES.md | 2 + actix-web/src/lib.rs | 1 + actix-web/src/redirect.rs | 238 +++++++++++++++++++++++++++++++ actix-web/src/web.rs | 30 +++- 5 files changed, 269 insertions(+), 4 deletions(-) create mode 100644 actix-web/src/redirect.rs diff --git a/.github/workflows/clippy-fmt.yml b/.github/workflows/clippy-fmt.yml index 1587a0b1b..e94c4d1af 100644 --- a/.github/workflows/clippy-fmt.yml +++ b/.github/workflows/clippy-fmt.yml @@ -10,6 +10,7 @@ jobs: steps: - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@nightly + with: { components: rustfmt } - run: cargo fmt --all -- --check clippy: @@ -21,7 +22,6 @@ jobs: with: { components: clippy } - name: Generate Cargo.lock - uses: actions-rs/cargo@v1 run: cargo generate-lockfile - name: Cache Dependencies uses: Swatinem/rust-cache@v1.2.0 diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 06a2cccc9..6440ad693 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -5,8 +5,10 @@ - Add `ContentDisposition::attachment` constructor. [#2867] - Add `ErrorHandlers::default_handler()` (as well as `default_handler_{server, client}()`) to make registering handlers for groups of response statuses easier. [#2784] - Add `Logger::custom_response_replace()`. [#2631] +- Add rudimentary redirection service at `web::redirect()` / `web::Redirect`. [#1961] - Add `guard::Acceptable` for matching against `Accept` header mime types. [#2265] +[#1961]: https://github.com/actix/actix-web/pull/1961 [#2265]: https://github.com/actix/actix-web/pull/2265 [#2631]: https://github.com/actix/actix-web/pull/2631 [#2784]: https://github.com/actix/actix-web/pull/2784 diff --git a/actix-web/src/lib.rs b/actix-web/src/lib.rs index 8d9e2dbcd..338541208 100644 --- a/actix-web/src/lib.rs +++ b/actix-web/src/lib.rs @@ -86,6 +86,7 @@ mod helpers; pub mod http; mod info; pub mod middleware; +mod redirect; mod request; mod request_data; mod resource; diff --git a/actix-web/src/redirect.rs b/actix-web/src/redirect.rs new file mode 100644 index 000000000..ca9e23aa4 --- /dev/null +++ b/actix-web/src/redirect.rs @@ -0,0 +1,238 @@ +//! See [`Redirect`] for service/responder documentation. + +use std::borrow::Cow; + +use actix_utils::future::ready; + +use crate::{ + dev::{fn_service, AppService, HttpServiceFactory, ResourceDef, ServiceRequest}, + http::{header::LOCATION, StatusCode}, + HttpRequest, HttpResponse, Responder, +}; + +/// An HTTP service for redirecting one path to another path or URL. +/// +/// By default, the "307 Temporary Redirect" status is used when responding. See [this MDN +/// article][mdn-redirects] on why 307 is preferred over 302. +/// +/// # Examples +/// As service: +/// ``` +/// use actix_web::{web, App}; +/// +/// App::new() +/// // redirect "/duck" to DuckDuckGo +/// .service(web::redirect("/duck", "https://duck.com")) +/// .service( +/// // redirect "/api/old" to "/api/new" +/// web::scope("/api").service(web::redirect("/old", "/new")) +/// ); +/// ``` +/// +/// As responder: +/// ``` +/// use actix_web::web::Redirect; +/// +/// async fn handler() -> impl Responder { +/// // sends a permanent (308) redirect to duck.com +/// Redirect::to("https://duck.com").permanent() +/// } +/// # actix_web::web::to(handler); +/// ``` +/// +/// [mdn-redirects]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections#temporary_redirections +#[derive(Debug, Clone)] +pub struct Redirect { + from: Cow<'static, str>, + to: Cow<'static, str>, + status_code: StatusCode, +} + +impl Redirect { + /// Construct a new `Redirect` service that matches a path. + /// + /// This service will match exact paths equal to `from` within the current scope. I.e., when + /// registered on the root `App`, it will match exact, whole paths. But when registered on a + /// `Scope`, it will match paths under that scope, ignoring the defined scope prefix, just like + /// a normal `Resource` or `Route`. + /// + /// The `to` argument can be path or URL; whatever is provided shall be used verbatim when + /// setting the redirect location. This means that relative paths can be used to navigate + /// relatively to matched paths. + /// + /// Prefer [`Redirect::to()`](Self::to) when using `Redirect` as a responder since `from` has + /// no meaning in that context. + /// + /// # Examples + /// ``` + /// # use actix_web::{web::Redirect, App}; + /// App::new() + /// // redirects "/oh/hi/mark" to "/oh/bye/johnny" + /// .service(Redirect::new("/oh/hi/mark", "../../bye/johnny")); + /// ``` + pub fn new(from: impl Into>, to: impl Into>) -> Self { + Self { + from: from.into(), + to: to.into(), + status_code: StatusCode::TEMPORARY_REDIRECT, + } + } + + /// Construct a new `Redirect` to use as a responder. + /// + /// Only receives the `to` argument since responders do not need to do route matching. + /// + /// # Examples + /// ``` + /// use actix_web::web::Redirect; + /// + /// async fn admin_page() -> impl Responder { + /// // sends a temporary 307 redirect to the login path + /// Redirect::to("/login") + /// } + /// # actix_web::web::to(handler); + /// ``` + pub fn to(to: impl Into>) -> Self { + Self { + from: "/".into(), + to: to.into(), + status_code: StatusCode::TEMPORARY_REDIRECT, + } + } + + /// Use the "308 Permanent Redirect" status when responding. + /// + /// See [this MDN article][mdn-redirects] on why 308 is preferred over 301. + /// + /// [mdn-redirects]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections#permanent_redirections + pub fn permanent(self) -> Self { + self.using_status_code(StatusCode::PERMANENT_REDIRECT) + } + + /// Use the "307 Temporary Redirect" status when responding. + /// + /// See [this MDN article][mdn-redirects] on why 307 is preferred over 302. + /// + /// [mdn-redirects]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections#temporary_redirections + pub fn temporary(self) -> Self { + self.using_status_code(StatusCode::TEMPORARY_REDIRECT) + } + + /// Use the "303 See Other" status when responding. + /// + /// This status code is semantically correct as the response to a successful login, for example. + pub fn see_other(self) -> Self { + self.using_status_code(StatusCode::SEE_OTHER) + } + + /// Allows the use of custom status codes for less common redirect types. + /// + /// In most cases, the default status ("308 Permanent Redirect") or using the `temporary` + /// method, which uses the "307 Temporary Redirect" status have more consistent behavior than + /// 301 and 302 codes, respectively. + /// + /// ``` + /// # use actix_web::{http::StatusCode, web::Redirect}; + /// // redirects would use "301 Moved Permanently" status code + /// Redirect::new("/old", "/new") + /// .using_status_code(StatusCode::MOVED_PERMANENTLY); + /// + /// // redirects would use "302 Found" status code + /// Redirect::new("/old", "/new") + /// .using_status_code(StatusCode::FOUND); + /// ``` + pub fn using_status_code(mut self, status: StatusCode) -> Self { + self.status_code = status; + self + } +} + +impl HttpServiceFactory for Redirect { + fn register(self, config: &mut AppService) { + let redirect = self.clone(); + let rdef = ResourceDef::new(self.from.into_owned()); + let redirect_factory = fn_service(move |mut req: ServiceRequest| { + let res = redirect.clone().respond_to(req.parts_mut().0); + ready(Ok(req.into_response(res.map_into_boxed_body()))) + }); + + config.register_service(rdef, None, redirect_factory, None) + } +} + +impl Responder for Redirect { + type Body = (); + + fn respond_to(self, _req: &HttpRequest) -> HttpResponse { + let mut res = HttpResponse::with_body(self.status_code, ()); + + if let Ok(hdr_val) = self.to.parse() { + res.headers_mut().insert(LOCATION, hdr_val); + } else { + log::error!( + "redirect target location can not be converted to header value: {:?}", + self.to + ); + } + + res + } +} + +#[cfg(test)] +mod tests { + use crate::{dev::Service, http::StatusCode, test, App}; + + use super::*; + + #[actix_rt::test] + async fn absolute_redirects() { + let redirector = Redirect::new("/one", "/two").permanent(); + + let svc = test::init_service(App::new().service(redirector)).await; + + let req = test::TestRequest::default().uri("/one").to_request(); + let res = svc.call(req).await.unwrap(); + assert_eq!(res.status(), StatusCode::from_u16(308).unwrap()); + let hdr = res.headers().get(&LOCATION).unwrap(); + assert_eq!(hdr.to_str().unwrap(), "/two"); + } + + #[actix_rt::test] + async fn relative_redirects() { + let redirector = Redirect::new("/one", "two").permanent(); + + let svc = test::init_service(App::new().service(redirector)).await; + + let req = test::TestRequest::default().uri("/one").to_request(); + let res = svc.call(req).await.unwrap(); + assert_eq!(res.status(), StatusCode::from_u16(308).unwrap()); + let hdr = res.headers().get(&LOCATION).unwrap(); + assert_eq!(hdr.to_str().unwrap(), "two"); + } + + #[actix_rt::test] + async fn temporary_redirects() { + let external_service = Redirect::new("/external", "https://duck.com"); + + let svc = test::init_service(App::new().service(external_service)).await; + + let req = test::TestRequest::default().uri("/external").to_request(); + let res = svc.call(req).await.unwrap(); + assert_eq!(res.status(), StatusCode::from_u16(307).unwrap()); + let hdr = res.headers().get(&LOCATION).unwrap(); + assert_eq!(hdr.to_str().unwrap(), "https://duck.com"); + } + + #[actix_rt::test] + async fn as_responder() { + let responder = Redirect::to("https://duck.com"); + + let req = test::TestRequest::default().to_http_request(); + let res = responder.respond_to(&req); + + assert_eq!(res.status(), StatusCode::from_u16(307).unwrap()); + let hdr = res.headers().get(&LOCATION).unwrap(); + assert_eq!(hdr.to_str().unwrap(), "https://duck.com"); + } +} diff --git a/actix-web/src/web.rs b/actix-web/src/web.rs index f5845d7f6..0533f7f8f 100644 --- a/actix-web/src/web.rs +++ b/actix-web/src/web.rs @@ -11,10 +11,12 @@ //! - [`Bytes`]: Raw payload //! //! # Responders -//! - [`Json`]: JSON request payload -//! - [`Bytes`]: Raw request payload +//! - [`Json`]: JSON response +//! - [`Form`]: URL-encoded response +//! - [`Bytes`]: Raw bytes response +//! - [`Redirect`](Redirect::to): Convenient redirect responses -use std::future::Future; +use std::{borrow::Cow, future::Future}; use actix_router::IntoPatterns; pub use bytes::{Buf, BufMut, Bytes, BytesMut}; @@ -26,6 +28,7 @@ use crate::{ pub use crate::config::ServiceConfig; pub use crate::data::Data; +pub use crate::redirect::Redirect; pub use crate::request_data::ReqData; pub use crate::types::*; @@ -45,6 +48,7 @@ pub use crate::types::*; /// For instance, to route `GET`-requests on any route matching `/users/{userid}/{friend}` and store /// `userid` and `friend` in the exposed `Path` object: /// +/// # Examples /// ``` /// use actix_web::{web, App, HttpResponse}; /// @@ -74,6 +78,7 @@ pub fn resource(path: T) -> Resource { /// - `/{project_id}/path2` /// - `/{project_id}/path3` /// +/// # Examples /// ``` /// use actix_web::{web, App, HttpResponse}; /// @@ -183,6 +188,25 @@ pub fn service(path: T) -> WebService { WebService::new(path) } +/// Create a relative or absolute redirect. +/// +/// See [`Redirect`] docs for usage details. +/// +/// # Examples +/// ``` +/// use actix_web::{web, App}; +/// +/// let app = App::new() +/// // the client will resolve this redirect to /api/to-path +/// .service(web::redirect("/api/from-path", "to-path")); +/// ``` +pub fn redirect( + from: impl Into>, + to: impl Into>, +) -> Redirect { + Redirect::new(from, to) +} + /// Executes blocking function on a thread pool, returns future that resolves to result of the /// function execution. pub fn block(f: F) -> impl Future> From 6d48593a6016cd75f6e7f128ee2bb2e6a28fcc86 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 25 Nov 2022 23:28:31 +0000 Subject: [PATCH 10/24] fix doc tests --- actix-web/src/guard/mod.rs | 4 ++++ actix-web/src/redirect.rs | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/actix-web/src/guard/mod.rs b/actix-web/src/guard/mod.rs index 5fcaec0de..7cd846b66 100644 --- a/actix-web/src/guard/mod.rs +++ b/actix-web/src/guard/mod.rs @@ -196,6 +196,7 @@ impl AnyGuard { } impl Guard for AnyGuard { + #[inline] fn check(&self, ctx: &GuardContext<'_>) -> bool { for guard in &self.guards { if guard.check(ctx) { @@ -247,12 +248,14 @@ impl AllGuard { } impl Guard for AllGuard { + #[inline] fn check(&self, ctx: &GuardContext<'_>) -> bool { for guard in &self.guards { if !guard.check(ctx) { return false; } } + true } } @@ -271,6 +274,7 @@ impl Guard for AllGuard { pub struct Not(pub G); impl Guard for Not { + #[inline] fn check(&self, ctx: &GuardContext<'_>) -> bool { !self.0.check(ctx) } diff --git a/actix-web/src/redirect.rs b/actix-web/src/redirect.rs index ca9e23aa4..5611cc368 100644 --- a/actix-web/src/redirect.rs +++ b/actix-web/src/redirect.rs @@ -31,7 +31,7 @@ use crate::{ /// /// As responder: /// ``` -/// use actix_web::web::Redirect; +/// use actix_web::{web::Redirect, Responder}; /// /// async fn handler() -> impl Responder { /// // sends a permanent (308) redirect to duck.com @@ -84,13 +84,13 @@ impl Redirect { /// /// # Examples /// ``` - /// use actix_web::web::Redirect; + /// use actix_web::{web::Redirect, Responder}; /// /// async fn admin_page() -> impl Responder { /// // sends a temporary 307 redirect to the login path /// Redirect::to("/login") /// } - /// # actix_web::web::to(handler); + /// # actix_web::web::to(admin_page); /// ``` pub fn to(to: impl Into>) -> Self { Self { From ede645ee4e65ee6ce930dd4a796c10d37fc1c4ce Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 18 Dec 2022 01:11:04 +0000 Subject: [PATCH 11/24] bump criterion to 0.4 --- actix-http/Cargo.toml | 2 +- actix-router/Cargo.toml | 2 +- actix-web/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index a8b888ef4..313764d35 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -105,7 +105,7 @@ actix-tls = { version = "3", features = ["openssl"] } actix-web = "4" async-stream = "0.3" -criterion = { version = "0.3", features = ["html_reports"] } +criterion = { version = "0.4", features = ["html_reports"] } env_logger = "0.9" futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } memchr = "2.4" diff --git a/actix-router/Cargo.toml b/actix-router/Cargo.toml index 19d6abc62..f3a5f15e4 100644 --- a/actix-router/Cargo.toml +++ b/actix-router/Cargo.toml @@ -27,7 +27,7 @@ serde = "1" tracing = { version = "0.1.30", default-features = false, features = ["log"] } [dev-dependencies] -criterion = { version = "0.3", features = ["html_reports"] } +criterion = { version = "0.4", features = ["html_reports"] } http = "0.2.5" serde = { version = "1", features = ["derive"] } percent-encoding = "2.1" diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index c7b54bd46..f60a35978 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -107,7 +107,7 @@ awc = { version = "3", features = ["openssl"] } brotli = "3.3.3" const-str = "0.4" -criterion = { version = "0.3", features = ["html_reports"] } +criterion = { version = "0.4", features = ["html_reports"] } env_logger = "0.9" flate2 = "1.0.13" futures-util = { version = "0.3.7", default-features = false, features = ["std"] } From 17f7cd2aae1cf6cd5b21e3225ab3b3b95b128885 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 18 Dec 2022 01:31:06 +0000 Subject: [PATCH 12/24] bump zstd to 0.12 --- actix-http/Cargo.toml | 2 +- actix-web/Cargo.toml | 7 ++----- awc/Cargo.toml | 4 ++-- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 313764d35..3ee285c81 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -96,7 +96,7 @@ actix-tls = { version = "3", default-features = false, optional = true } # compress-* brotli = { version = "3.3.3", optional = true } flate2 = { version = "1.0.13", optional = true } -zstd = { version = "0.11", optional = true } +zstd = { version = "0.12", optional = true } [dev-dependencies] actix-http-test = { version = "3", features = ["openssl"] } diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index f60a35978..9ace68811 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -38,10 +38,7 @@ compress-gzip = ["actix-http/compress-gzip", "__compress"] compress-zstd = ["actix-http/compress-zstd", "__compress"] # Routing and runtime proc macros -macros = [ - "actix-macros", - "actix-web-codegen", -] +macros = ["actix-macros", "actix-web-codegen"] # Cookies support cookies = ["cookie"] @@ -119,7 +116,7 @@ static_assertions = "1" tls-openssl = { package = "openssl", version = "0.10.9" } tls-rustls = { package = "rustls", version = "0.20.0" } tokio = { version = "1.13.1", features = ["rt-multi-thread", "macros"] } -zstd = "0.11" +zstd = "0.12" [[test]] name = "test_server" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index e7ac43d22..b40becfec 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -87,7 +87,7 @@ cookie = { version = "0.16", features = ["percent-encode"], optional = true } tls-openssl = { package = "openssl", version = "0.10.9", optional = true } tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features = ["dangerous_configuration"] } -trust-dns-resolver = { version = "0.21", optional = true } +trust-dns-resolver = { version = "0.22", optional = true } [dev-dependencies] actix-http = { version = "3", features = ["openssl"] } @@ -107,7 +107,7 @@ static_assertions = "1.1" rcgen = "0.9" rustls-pemfile = "1" tokio = { version = "1.13.1", features = ["rt-multi-thread", "macros"] } -zstd = "0.11" +zstd = "0.12" [[example]] name = "client" From 29bd6a1dd57705fc44a40d63327086643de1d2ad Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 18 Dec 2022 01:34:48 +0000 Subject: [PATCH 13/24] fix version requirement for futures_util --- actix-files/Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http/Cargo.toml | 4 ++-- actix-http/src/h1/dispatcher_tests.rs | 2 +- actix-http/tests/test_openssl.rs | 2 +- actix-http/tests/test_server.rs | 5 +---- actix-multipart/Cargo.toml | 4 ++-- actix-multipart/src/extractor.rs | 2 +- actix-multipart/src/server.rs | 2 +- actix-test/Cargo.toml | 4 ++-- actix-web-actors/Cargo.toml | 4 ++-- actix-web-actors/tests/test_ws.rs | 2 +- actix-web-codegen/Cargo.toml | 2 +- actix-web/Cargo.toml | 6 +++--- actix-web/src/app.rs | 2 +- actix-web/src/middleware/condition.rs | 2 +- actix-web/src/middleware/err_handlers.rs | 2 +- actix-web/src/types/payload.rs | 2 +- actix-web/src/types/readlines.rs | 2 +- awc/Cargo.toml | 6 +++--- awc/src/client/pool.rs | 2 +- awc/src/lib.rs | 2 +- awc/src/ws.rs | 2 +- awc/tests/test_client.rs | 2 +- 24 files changed, 32 insertions(+), 35 deletions(-) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 018acdfb1..01dc2928a 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -29,7 +29,7 @@ actix-web = { version = "4", default-features = false } bitflags = "1" bytes = "1" derive_more = "0.99.5" -futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } +futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] } http-range = "0.1.4" log = "0.4" mime = "0.3" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 0a9ddf947..86338fb06 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -39,7 +39,7 @@ awc = { version = "3", default-features = false } base64 = "0.13" bytes = "1" -futures-core = { version = "0.3.7", default-features = false } +futures-core = { version = "0.3.17", default-features = false } http = "0.2.5" log = "0.4" socket2 = "0.4" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 3ee285c81..9f3977f0f 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -67,7 +67,7 @@ bytes = "1" bytestring = "1" derive_more = "0.99.5" encoding_rs = "0.8" -futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } +futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] } http = "0.2.5" httparse = "1.5.1" httpdate = "1.0.1" @@ -107,7 +107,7 @@ actix-web = "4" async-stream = "0.3" criterion = { version = "0.4", features = ["html_reports"] } env_logger = "0.9" -futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } +futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] } memchr = "2.4" once_cell = "1.9" rcgen = "0.9" diff --git a/actix-http/src/h1/dispatcher_tests.rs b/actix-http/src/h1/dispatcher_tests.rs index 3eea859bf..d39c5bd69 100644 --- a/actix-http/src/h1/dispatcher_tests.rs +++ b/actix-http/src/h1/dispatcher_tests.rs @@ -64,7 +64,7 @@ fn drop_payload_service( fn echo_payload_service() -> impl Service, Error = Error> { fn_service(|mut req: Request| { Box::pin(async move { - use futures_util::stream::StreamExt as _; + use futures_util::StreamExt as _; let mut pl = req.take_payload(); let mut body = BytesMut::new(); diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index b97b2e45b..40dbb6ba4 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -16,7 +16,7 @@ use actix_utils::future::{err, ok, ready}; use bytes::{Bytes, BytesMut}; use derive_more::{Display, Error}; use futures_core::Stream; -use futures_util::stream::{once, StreamExt as _}; +use futures_util::{stream::once, StreamExt as _}; use openssl::{ pkey::PKey, ssl::{SslAcceptor, SslMethod}, diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index e8d103c96..e70089b1d 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -15,10 +15,7 @@ use actix_service::fn_service; use actix_utils::future::{err, ok, ready}; use bytes::Bytes; use derive_more::{Display, Error}; -use futures_util::{ - stream::{once, StreamExt as _}, - FutureExt as _, -}; +use futures_util::{stream::once, FutureExt as _, StreamExt as _}; use regex::Regex; #[actix_rt::test] diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 6f631fcf1..3226850db 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -19,7 +19,7 @@ actix-web = { version = "4", default-features = false } bytes = "1" derive_more = "0.99.5" -futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } +futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] } httparse = "1.3" local-waker = "0.1" log = "0.4" @@ -29,6 +29,6 @@ memchr = "2.5" [dev-dependencies] actix-rt = "2.2" actix-http = "3" -futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } +futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] } tokio = { version = "1.8.4", features = ["sync"] } tokio-stream = "0.1" diff --git a/actix-multipart/src/extractor.rs b/actix-multipart/src/extractor.rs index 1ad1f203d..d45c4869c 100644 --- a/actix-multipart/src/extractor.rs +++ b/actix-multipart/src/extractor.rs @@ -14,7 +14,7 @@ use crate::server::Multipart; /// ``` /// use actix_web::{web, HttpResponse, Error}; /// use actix_multipart::Multipart; -/// use futures_util::stream::StreamExt as _; +/// use futures_util::StreamExt as _; /// /// async fn index(mut payload: Multipart) -> Result { /// // iterate over multipart stream diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index 1d0510039..9e0becd5c 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -868,7 +868,7 @@ mod tests { use actix_web::test::TestRequest; use actix_web::FromRequest; use bytes::Bytes; - use futures_util::{future::lazy, StreamExt}; + use futures_util::{future::lazy, StreamExt as _}; use std::time::Duration; use tokio::sync::mpsc; use tokio_stream::wrappers::UnboundedReceiverStream; diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index eaea15d47..05d12c25a 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -37,8 +37,8 @@ actix-utils = "3" actix-web = { version = "4", default-features = false, features = ["cookies"] } awc = { version = "3", default-features = false, features = ["cookies"] } -futures-core = { version = "0.3.7", default-features = false, features = ["std"] } -futures-util = { version = "0.3.7", default-features = false, features = [] } +futures-core = { version = "0.3.17", default-features = false, features = ["std"] } +futures-util = { version = "0.3.17", default-features = false, features = [] } log = "0.4" serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 26b1c09de..633c3c373 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -21,7 +21,7 @@ actix-web = { version = "4", default-features = false } bytes = "1" bytestring = "1" -futures-core = { version = "0.3.7", default-features = false } +futures-core = { version = "0.3.17", default-features = false } pin-project-lite = "0.2" tokio = { version = "1.13.1", features = ["sync"] } tokio-util = { version = "0.7", features = ["codec"] } @@ -35,4 +35,4 @@ actix-web = { version = "4", features = ["macros"] } mime = "0.3" env_logger = "0.9" -futures-util = { version = "0.3.7", default-features = false } +futures-util = { version = "0.3.17", default-features = false } diff --git a/actix-web-actors/tests/test_ws.rs b/actix-web-actors/tests/test_ws.rs index a9eb37699..cf12a0052 100644 --- a/actix-web-actors/tests/test_ws.rs +++ b/actix-web-actors/tests/test_ws.rs @@ -3,7 +3,7 @@ use actix_http::ws::Codec; use actix_web::{web, App, HttpRequest}; use actix_web_actors::ws; use bytes::Bytes; -use futures_util::{SinkExt, StreamExt}; +use futures_util::{SinkExt as _, StreamExt as _}; struct Ws; diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 2477364a6..da5577445 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -27,6 +27,6 @@ actix-test = "0.1" actix-utils = "3" actix-web = "4" -futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } +futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] } trybuild = "1" rustversion = "1" diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index 9ace68811..6078d5739 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -79,8 +79,8 @@ cfg-if = "1" cookie = { version = "0.16", features = ["percent-encode"], optional = true } derive_more = "0.99.8" encoding_rs = "0.8" -futures-core = { version = "0.3.7", default-features = false } -futures-util = { version = "0.3.7", default-features = false } +futures-core = { version = "0.3.17", default-features = false } +futures-util = { version = "0.3.17", default-features = false } http = "0.2.8" itoa = "1" language-tags = "0.3" @@ -107,7 +107,7 @@ const-str = "0.4" criterion = { version = "0.4", features = ["html_reports"] } env_logger = "0.9" flate2 = "1.0.13" -futures-util = { version = "0.3.7", default-features = false, features = ["std"] } +futures-util = { version = "0.3.17", default-features = false, features = ["std"] } rand = "0.8" rcgen = "0.9" rustls-pemfile = "1" diff --git a/actix-web/src/app.rs b/actix-web/src/app.rs index ec37ff8a4..e53ab8080 100644 --- a/actix-web/src/app.rs +++ b/actix-web/src/app.rs @@ -5,7 +5,7 @@ use actix_service::{ apply, apply_fn_factory, boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt, Transform, }; -use futures_util::future::FutureExt as _; +use futures_util::FutureExt as _; use crate::{ app_service::{AppEntry, AppInit, AppRoutingFactory}, diff --git a/actix-web/src/middleware/condition.rs b/actix-web/src/middleware/condition.rs index 65f25a67c..5e106c11f 100644 --- a/actix-web/src/middleware/condition.rs +++ b/actix-web/src/middleware/condition.rs @@ -7,7 +7,7 @@ use std::{ }; use futures_core::{future::LocalBoxFuture, ready}; -use futures_util::future::FutureExt as _; +use futures_util::FutureExt as _; use pin_project_lite::pin_project; use crate::{ diff --git a/actix-web/src/middleware/err_handlers.rs b/actix-web/src/middleware/err_handlers.rs index 3a4e44a2c..4ddbc6318 100644 --- a/actix-web/src/middleware/err_handlers.rs +++ b/actix-web/src/middleware/err_handlers.rs @@ -351,7 +351,7 @@ mod tests { use actix_service::IntoService; use actix_utils::future::ok; use bytes::Bytes; - use futures_util::future::FutureExt as _; + use futures_util::FutureExt as _; use super::*; use crate::{ diff --git a/actix-web/src/types/payload.rs b/actix-web/src/types/payload.rs index f17a4ed6d..4045cedb4 100644 --- a/actix-web/src/types/payload.rs +++ b/actix-web/src/types/payload.rs @@ -27,7 +27,7 @@ use crate::{ /// # Examples /// ``` /// use std::future::Future; -/// use futures_util::stream::StreamExt as _; +/// use futures_util::StreamExt as _; /// use actix_web::{post, web}; /// /// // `body: web::Payload` parameter extracts raw payload stream from request diff --git a/actix-web/src/types/readlines.rs b/actix-web/src/types/readlines.rs index 8a775a073..e75239968 100644 --- a/actix-web/src/types/readlines.rs +++ b/actix-web/src/types/readlines.rs @@ -177,7 +177,7 @@ where #[cfg(test)] mod tests { - use futures_util::stream::StreamExt as _; + use futures_util::StreamExt as _; use super::*; use crate::test::TestRequest; diff --git a/awc/Cargo.toml b/awc/Cargo.toml index b40becfec..cf64eed49 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -67,8 +67,8 @@ base64 = "0.13" bytes = "1" cfg-if = "1" derive_more = "0.99.5" -futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } -futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] } +futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] } +futures-util = { version = "0.3.17", default-features = false, features = ["alloc", "sink"] } h2 = "0.3.9" http = "0.2.5" itoa = "1" @@ -102,7 +102,7 @@ brotli = "3.3.3" const-str = "0.4" env_logger = "0.9" flate2 = "1.0.13" -futures-util = { version = "0.3.7", default-features = false } +futures-util = { version = "0.3.17", default-features = false } static_assertions = "1.1" rcgen = "0.9" rustls-pemfile = "1" diff --git a/awc/src/client/pool.rs b/awc/src/client/pool.rs index 5655b5845..47c1fdd67 100644 --- a/awc/src/client/pool.rs +++ b/awc/src/client/pool.rs @@ -19,7 +19,7 @@ use actix_rt::time::{sleep, Sleep}; use actix_service::Service; use ahash::AHashMap; use futures_core::future::LocalBoxFuture; -use futures_util::FutureExt; +use futures_util::FutureExt as _; use http::uri::Authority; use pin_project_lite::pin_project; use tokio::sync::{OwnedSemaphorePermit, Semaphore}; diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 8d6ea759a..412ccbe61 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -83,7 +83,7 @@ //! ```no_run //! # #[actix_rt::main] //! # async fn main() -> Result<(), Box> { -//! use futures_util::{sink::SinkExt as _, stream::StreamExt as _}; +//! use futures_util::{SinkExt as _, StreamExt as _}; //! //! let (_resp, mut connection) = awc::Client::new() //! .ws("ws://echo.websocket.org") diff --git a/awc/src/ws.rs b/awc/src/ws.rs index b316f68b4..4ef2e2b36 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -6,7 +6,7 @@ //! //! ```no_run //! use awc::{Client, ws}; -//! use futures_util::{sink::SinkExt as _, stream::StreamExt as _}; +//! use futures_util::{SinkExt as _, StreamExt as _}; //! //! #[actix_rt::main] //! async fn main() { diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index c4b468eeb..db987fdfa 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -139,7 +139,7 @@ async fn timeout_override() { #[actix_rt::test] async fn response_timeout() { - use futures_util::stream::{once, StreamExt as _}; + use futures_util::{stream::once, StreamExt as _}; let srv = actix_test::start(|| { App::new().service(web::resource("/").route(web::to(|| async { From 06c3513bc0da212d87c6fae5332e845146991027 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 21 Dec 2022 20:28:45 +0000 Subject: [PATCH 14/24] add Allow header to resource's default 405 handler (#2949) --- actix-web/CHANGES.md | 4 +++ actix-web/src/guard/mod.rs | 16 ++++++++++- actix-web/src/resource.rs | 59 +++++++++++++++++++++++++++++++------- 3 files changed, 68 insertions(+), 11 deletions(-) diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 6440ad693..8ea60266e 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -8,11 +8,15 @@ - Add rudimentary redirection service at `web::redirect()` / `web::Redirect`. [#1961] - Add `guard::Acceptable` for matching against `Accept` header mime types. [#2265] +### Fixed +- Add `Allow` header to `Resource`'s default responses when no routes are matched. [#2949] + [#1961]: https://github.com/actix/actix-web/pull/1961 [#2265]: https://github.com/actix/actix-web/pull/2265 [#2631]: https://github.com/actix/actix-web/pull/2631 [#2784]: https://github.com/actix/actix-web/pull/2784 [#2867]: https://github.com/actix/actix-web/pull/2867 +[#2949]: https://github.com/actix/actix-web/pull/2949 ## 4.2.1 - 2022-09-12 diff --git a/actix-web/src/guard/mod.rs b/actix-web/src/guard/mod.rs index 7cd846b66..e086f8648 100644 --- a/actix-web/src/guard/mod.rs +++ b/actix-web/src/guard/mod.rs @@ -286,11 +286,25 @@ pub fn Method(method: HttpMethod) -> impl Guard { MethodGuard(method) } +#[derive(Debug, Clone)] +pub(crate) struct RegisteredMethods(pub(crate) Vec); + /// HTTP method guard. -struct MethodGuard(HttpMethod); +#[derive(Debug)] +pub(crate) struct MethodGuard(HttpMethod); impl Guard for MethodGuard { fn check(&self, ctx: &GuardContext<'_>) -> bool { + let registered = ctx.req_data_mut().remove::(); + + if let Some(mut methods) = registered { + methods.0.push(self.0.clone()); + ctx.req_data_mut().insert(methods); + } else { + ctx.req_data_mut() + .insert(RegisteredMethods(vec![self.0.clone()])); + } + ctx.head().method == self.0 } } diff --git a/actix-web/src/resource.rs b/actix-web/src/resource.rs index c5c6701e6..997036751 100644 --- a/actix-web/src/resource.rs +++ b/actix-web/src/resource.rs @@ -13,8 +13,9 @@ use crate::{ body::MessageBody, data::Data, dev::{ensure_leading_slash, AppService, ResourceDef}, - guard::Guard, + guard::{self, Guard}, handler::Handler, + http::header, route::{Route, RouteService}, service::{ BoxedHttpService, BoxedHttpServiceFactory, HttpServiceFactory, ServiceRequest, @@ -40,8 +41,11 @@ use crate::{ /// .route(web::get().to(|| HttpResponse::Ok()))); /// ``` /// -/// If no matching route could be found, *405* response code get returned. Default behavior could be -/// overridden with `default_resource()` method. +/// If no matching route is found, [a 405 response is returned with an appropriate Allow header][RFC +/// 9110 §15.5.6]. This default behavior can be overridden using +/// [`default_service()`](Self::default_service). +/// +/// [RFC 9110 §15.5.6]: https://www.rfc-editor.org/rfc/rfc9110.html#section-15.5.6 pub struct Resource { endpoint: T, rdef: Patterns, @@ -66,7 +70,19 @@ impl Resource { guards: Vec::new(), app_data: None, default: boxed::factory(fn_service(|req: ServiceRequest| async { - Ok(req.into_response(HttpResponse::MethodNotAllowed())) + use crate::HttpMessage as _; + + let allowed = req.extensions().get::().cloned(); + + if let Some(methods) = allowed { + Ok(req.into_response( + HttpResponse::MethodNotAllowed() + .insert_header(header::Allow(methods.0)) + .finish(), + )) + } else { + Ok(req.into_response(HttpResponse::MethodNotAllowed())) + } })), } } @@ -309,13 +325,28 @@ where } } - /// Default service to be used if no matching route could be found. + /// Sets the default service to be used if no matching route is found. /// - /// You can use a [`Route`] as default service. + /// Unlike [`Scope`]s, a `Resource` does _not_ inherit its parent's default service. You can + /// use a [`Route`] as default service. /// - /// If a default service is not registered, an empty `405 Method Not Allowed` response will be - /// sent to the client instead. Unlike [`Scope`](crate::Scope)s, a [`Resource`] does **not** - /// inherit its parent's default service. + /// If a custom default service is not registered, an empty `405 Method Not Allowed` response + /// with an appropriate Allow header will be sent instead. + /// + /// # Examples + /// ``` + /// use actix_web::{App, HttpResponse, web}; + /// + /// let resource = web::resource("/test") + /// .route(web::get().to(HttpResponse::Ok)) + /// .default_service(web::to(|| { + /// HttpResponse::BadRequest() + /// })); + /// + /// App::new().service(resource); + /// ``` + /// + /// [`Scope`]: crate::Scope pub fn default_service(mut self, f: F) -> Self where F: IntoServiceFactory, @@ -606,7 +637,11 @@ mod tests { async fn test_default_resource() { let srv = init_service( App::new() - .service(web::resource("/test").route(web::get().to(HttpResponse::Ok))) + .service( + web::resource("/test") + .route(web::get().to(HttpResponse::Ok)) + .route(web::delete().to(HttpResponse::Ok)), + ) .default_service(|r: ServiceRequest| { ok(r.into_response(HttpResponse::BadRequest())) }), @@ -621,6 +656,10 @@ mod tests { .to_request(); let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + assert_eq!( + resp.headers().get(header::ALLOW).unwrap().as_bytes(), + b"GET, DELETE" + ); let srv = init_service( App::new().service( From 6f0a6bd1bb7ffdd98fa5ce825b24a73c4d71d9a7 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 1 Jan 2023 20:56:27 +0000 Subject: [PATCH 15/24] address clippy lints For intrepid commit message readers: The choice to add allows for the inlined format args lint instead of actually inlining them is not very clear because our actual real world MSRV is not clear. We currently claim 1.60 is our MSRV but this is mainly due to dependencies. I'm fairly sure that we could support < 1.58 if those deps are outdated in a users lockfile. We'll remove these allows again at some point soon. --- actix-files/src/lib.rs | 1 + actix-http-test/src/lib.rs | 3 +++ actix-http/benches/quality-value.rs | 2 ++ actix-http/src/body/sized_stream.rs | 2 +- actix-http/src/h1/chunked.rs | 2 +- actix-http/src/h1/encoder.rs | 2 +- actix-http/src/lib.rs | 3 ++- actix-http/tests/test_openssl.rs | 1 + actix-http/tests/test_rustls.rs | 1 + actix-http/tests/test_server.rs | 2 ++ actix-http/tests/test_ws.rs | 2 ++ actix-multipart/src/lib.rs | 2 +- actix-router/benches/quoter.rs | 2 ++ actix-router/src/lib.rs | 1 + actix-test/src/lib.rs | 2 ++ actix-web-actors/src/lib.rs | 1 + actix-web-codegen/src/route.rs | 2 +- actix-web/benches/server.rs | 2 ++ actix-web/examples/basic.rs | 2 ++ actix-web/examples/macroless.rs | 2 ++ actix-web/examples/on-connect.rs | 2 ++ actix-web/examples/uds.rs | 2 ++ actix-web/src/app.rs | 1 + actix-web/src/lib.rs | 1 + actix-web/tests/test_httpserver.rs | 2 ++ awc/examples/client.rs | 2 ++ awc/src/builder.rs | 2 +- awc/src/lib.rs | 3 ++- awc/src/request.rs | 4 +++- awc/src/ws.rs | 4 +++- awc/tests/test_client.rs | 2 ++ 31 files changed, 52 insertions(+), 10 deletions(-) diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 40327e5e8..0fbe39a8e 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -13,6 +13,7 @@ #![deny(rust_2018_idioms, nonstandard_style)] #![warn(future_incompatible, missing_docs, missing_debug_implementations)] +#![allow(clippy::uninlined_format_args)] use actix_service::boxed::{BoxService, BoxServiceFactory}; use actix_web::{ diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index 8636ef9c4..a66f7b486 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -2,6 +2,7 @@ #![deny(rust_2018_idioms, nonstandard_style)] #![warn(future_incompatible)] +#![allow(clippy::uninlined_format_args)] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] @@ -87,6 +88,7 @@ pub async fn test_server_with_addr>( // notify TestServer that server and system have shut down // all thread managed resources should be dropped at this point + #[allow(clippy::let_underscore_future)] let _ = thread_stop_tx.send(()); }); @@ -294,6 +296,7 @@ impl Drop for TestServer { // without needing to await anything // signal server to stop + #[allow(clippy::let_underscore_future)] let _ = self.server.stop(true); // signal system to stop diff --git a/actix-http/benches/quality-value.rs b/actix-http/benches/quality-value.rs index 33ba9c4c8..0ed274ded 100644 --- a/actix-http/benches/quality-value.rs +++ b/actix-http/benches/quality-value.rs @@ -1,3 +1,5 @@ +#![allow(clippy::uninlined_format_args)] + use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; const CODES: &[u16] = &[0, 1000, 201, 800, 550]; diff --git a/actix-http/src/body/sized_stream.rs b/actix-http/src/body/sized_stream.rs index e5e27b287..08cd81a0d 100644 --- a/actix-http/src/body/sized_stream.rs +++ b/actix-http/src/body/sized_stream.rs @@ -44,7 +44,7 @@ where #[inline] fn size(&self) -> BodySize { - BodySize::Sized(self.size as u64) + BodySize::Sized(self.size) } /// Attempts to pull out the next value of the underlying [`Stream`]. diff --git a/actix-http/src/h1/chunked.rs b/actix-http/src/h1/chunked.rs index 4005ed892..fc9081b81 100644 --- a/actix-http/src/h1/chunked.rs +++ b/actix-http/src/h1/chunked.rs @@ -71,7 +71,7 @@ impl ChunkedState { match size.checked_mul(radix) { Some(n) => { - *size = n as u64; + *size = n; *size += rem as u64; Poll::Ready(Ok(ChunkedState::Size)) diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 21cfd75c4..abe396ce2 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -450,7 +450,7 @@ impl TransferEncoding { buf.extend_from_slice(&msg[..len as usize]); - *remaining -= len as u64; + *remaining -= len; Ok(*remaining == 0) } else { Ok(true) diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 864db4986..05f80eba4 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -21,7 +21,8 @@ #![allow( clippy::type_complexity, clippy::too_many_arguments, - clippy::borrow_interior_mutable_const + clippy::borrow_interior_mutable_const, + clippy::uninlined_format_args )] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index 40dbb6ba4..7464bee4e 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -1,4 +1,5 @@ #![cfg(feature = "openssl")] +#![allow(clippy::uninlined_format_args)] extern crate tls_openssl as openssl; diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index d9ff42b7d..0b8197a69 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -1,4 +1,5 @@ #![cfg(feature = "rustls")] +#![allow(clippy::uninlined_format_args)] extern crate tls_rustls as rustls; diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index e70089b1d..0816ab221 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -1,3 +1,5 @@ +#![allow(clippy::uninlined_format_args)] + use std::{ convert::Infallible, io::{Read, Write}, diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs index 8b3ab8e1b..a9c1acd33 100644 --- a/actix-http/tests/test_ws.rs +++ b/actix-http/tests/test_ws.rs @@ -1,3 +1,5 @@ +#![allow(clippy::uninlined_format_args)] + use std::{ cell::Cell, convert::Infallible, diff --git a/actix-multipart/src/lib.rs b/actix-multipart/src/lib.rs index 3d536e08d..37d03db49 100644 --- a/actix-multipart/src/lib.rs +++ b/actix-multipart/src/lib.rs @@ -2,7 +2,7 @@ #![deny(rust_2018_idioms, nonstandard_style)] #![warn(future_incompatible)] -#![allow(clippy::borrow_interior_mutable_const)] +#![allow(clippy::borrow_interior_mutable_const, clippy::uninlined_format_args)] mod error; mod extractor; diff --git a/actix-router/benches/quoter.rs b/actix-router/benches/quoter.rs index c18f1620e..9ca06da39 100644 --- a/actix-router/benches/quoter.rs +++ b/actix-router/benches/quoter.rs @@ -1,3 +1,5 @@ +#![allow(clippy::uninlined_format_args)] + use criterion::{black_box, criterion_group, criterion_main, Criterion}; use std::borrow::Cow; diff --git a/actix-router/src/lib.rs b/actix-router/src/lib.rs index 0febcf1ac..418dd432b 100644 --- a/actix-router/src/lib.rs +++ b/actix-router/src/lib.rs @@ -2,6 +2,7 @@ #![deny(rust_2018_idioms, nonstandard_style)] #![warn(future_incompatible)] +#![allow(clippy::uninlined_format_args)] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] diff --git a/actix-test/src/lib.rs b/actix-test/src/lib.rs index 5efd9758e..1aff2dc83 100644 --- a/actix-test/src/lib.rs +++ b/actix-test/src/lib.rs @@ -321,6 +321,7 @@ where // all thread managed resources should be dropped at this point }); + #[allow(clippy::let_underscore_future)] let _ = thread_stop_tx.send(()); }); @@ -567,6 +568,7 @@ impl Drop for TestServer { // without needing to await anything // signal server to stop + #[allow(clippy::let_underscore_future)] let _ = self.server.stop(true); // signal system to stop diff --git a/actix-web-actors/src/lib.rs b/actix-web-actors/src/lib.rs index 106bc5202..7a34048da 100644 --- a/actix-web-actors/src/lib.rs +++ b/actix-web-actors/src/lib.rs @@ -57,6 +57,7 @@ #![deny(rust_2018_idioms, nonstandard_style)] #![warn(future_incompatible)] +#![allow(clippy::uninlined_format_args)] mod context; pub mod ws; diff --git a/actix-web-codegen/src/route.rs b/actix-web-codegen/src/route.rs index 7a0658468..e5493702d 100644 --- a/actix-web-codegen/src/route.rs +++ b/actix-web-codegen/src/route.rs @@ -155,7 +155,7 @@ impl Args { if !methods.insert(method) { return Err(syn::Error::new_spanned( &nv.lit, - &format!( + format!( "HTTP method defined more than once: `{}`", lit.value() ), diff --git a/actix-web/benches/server.rs b/actix-web/benches/server.rs index 0d45c9403..2c9f71dc5 100644 --- a/actix-web/benches/server.rs +++ b/actix-web/benches/server.rs @@ -1,3 +1,5 @@ +#![allow(clippy::uninlined_format_args)] + use actix_web::{web, App, HttpResponse}; use awc::Client; use criterion::{criterion_group, criterion_main, Criterion}; diff --git a/actix-web/examples/basic.rs b/actix-web/examples/basic.rs index 36b1cdd8f..60715f477 100644 --- a/actix-web/examples/basic.rs +++ b/actix-web/examples/basic.rs @@ -1,3 +1,5 @@ +#![allow(clippy::uninlined_format_args)] + use actix_web::{get, middleware, web, App, HttpRequest, HttpResponse, HttpServer}; #[get("/resource1/{name}/index.html")] diff --git a/actix-web/examples/macroless.rs b/actix-web/examples/macroless.rs index 78ffd45c1..d3589da21 100644 --- a/actix-web/examples/macroless.rs +++ b/actix-web/examples/macroless.rs @@ -1,3 +1,5 @@ +#![allow(clippy::uninlined_format_args)] + use actix_web::{middleware, rt, web, App, HttpRequest, HttpServer}; async fn index(req: HttpRequest) -> &'static str { diff --git a/actix-web/examples/on-connect.rs b/actix-web/examples/on-connect.rs index 24c6f8418..57017fcd6 100644 --- a/actix-web/examples/on-connect.rs +++ b/actix-web/examples/on-connect.rs @@ -4,6 +4,8 @@ //! For an example of extracting a client TLS certificate, see: //! +#![allow(clippy::uninlined_format_args)] + use std::{any::Any, io, net::SocketAddr}; use actix_web::{ diff --git a/actix-web/examples/uds.rs b/actix-web/examples/uds.rs index cf0ffebde..ba4b25a29 100644 --- a/actix-web/examples/uds.rs +++ b/actix-web/examples/uds.rs @@ -1,3 +1,5 @@ +#![allow(clippy::uninlined_format_args)] + use actix_web::{get, web, HttpRequest}; #[cfg(unix)] use actix_web::{middleware, App, Error, HttpResponse, HttpServer}; diff --git a/actix-web/src/app.rs b/actix-web/src/app.rs index e53ab8080..353b82b19 100644 --- a/actix-web/src/app.rs +++ b/actix-web/src/app.rs @@ -712,6 +712,7 @@ mod tests { .route("/", web::to(|| async { "hello" })) } + #[allow(clippy::let_underscore_future)] let _ = init_service(my_app()); } } diff --git a/actix-web/src/lib.rs b/actix-web/src/lib.rs index 338541208..6a94976c5 100644 --- a/actix-web/src/lib.rs +++ b/actix-web/src/lib.rs @@ -69,6 +69,7 @@ #![deny(rust_2018_idioms, nonstandard_style)] #![warn(future_incompatible)] +#![allow(clippy::uninlined_format_args)] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] #![cfg_attr(docsrs, feature(doc_cfg))] diff --git a/actix-web/tests/test_httpserver.rs b/actix-web/tests/test_httpserver.rs index 86e0575f3..861d76d93 100644 --- a/actix-web/tests/test_httpserver.rs +++ b/actix-web/tests/test_httpserver.rs @@ -1,3 +1,5 @@ +#![allow(clippy::uninlined_format_args)] + #[cfg(feature = "openssl")] extern crate tls_openssl as openssl; diff --git a/awc/examples/client.rs b/awc/examples/client.rs index 16ad330b8..26edcfd62 100644 --- a/awc/examples/client.rs +++ b/awc/examples/client.rs @@ -1,3 +1,5 @@ +#![allow(clippy::uninlined_format_args)] + use std::error::Error as StdError; #[tokio::main] diff --git a/awc/src/builder.rs b/awc/src/builder.rs index c101d18f0..34a5f8505 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -210,7 +210,7 @@ where }; self.add_default_header(( header::AUTHORIZATION, - format!("Basic {}", base64::encode(&auth)), + format!("Basic {}", base64::encode(auth)), )) } diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 412ccbe61..bb7f06c93 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -105,7 +105,8 @@ #![allow( clippy::type_complexity, clippy::borrow_interior_mutable_const, - clippy::needless_doctest_main + clippy::needless_doctest_main, + clippy::uninlined_format_args )] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] diff --git a/awc/src/request.rs b/awc/src/request.rs index 102db3c16..331c80af7 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -238,7 +238,7 @@ impl ClientRequest { self.insert_header(( header::AUTHORIZATION, - format!("Basic {}", base64::encode(&auth)), + format!("Basic {}", base64::encode(auth)), )) } @@ -565,6 +565,8 @@ mod tests { assert_eq!(req.head.version, Version::HTTP_2); let _ = req.headers_mut(); + + #[allow(clippy::let_underscore_future)] let _ = req.send_body(""); } diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 4ef2e2b36..f905b8ef2 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -236,7 +236,7 @@ impl WebsocketsRequest { Some(password) => format!("{}:{}", username, password), None => format!("{}:", username), }; - self.header(AUTHORIZATION, format!("Basic {}", base64::encode(&auth))) + self.header(AUTHORIZATION, format!("Basic {}", base64::encode(auth))) } /// Set HTTP bearer authentication header @@ -503,6 +503,8 @@ mod tests { .unwrap(), "Bearer someS3cr3tAutht0k3n" ); + + #[allow(clippy::let_underscore_future)] let _ = req.connect(); } diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index db987fdfa..0949595cb 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -1,3 +1,5 @@ +#![allow(clippy::uninlined_format_args)] + use std::{ collections::HashMap, convert::Infallible, From 77459ec415a44ab90ed5b11ec6165f0dd0fe5e37 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 2 Jan 2023 00:14:25 +0000 Subject: [PATCH 16/24] add h2c example --- actix-http/examples/h2c-detect.rs | 54 +++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 actix-http/examples/h2c-detect.rs diff --git a/actix-http/examples/h2c-detect.rs b/actix-http/examples/h2c-detect.rs new file mode 100644 index 000000000..e1491324b --- /dev/null +++ b/actix-http/examples/h2c-detect.rs @@ -0,0 +1,54 @@ +//! An example that supports automatic selection of plaintext h1/h2c connections. +//! +//! Notably, both the following commands will work. +//! ```console +//! $ curl --http1.1 'http://localhost:8080/' +//! $ curl --http2-prior-knowledge 'http://localhost:8080/' +//! ``` + +use std::{convert::Infallible, io}; + +use actix_http::{HttpService, Protocol, Request, Response, StatusCode}; +use actix_rt::net::TcpStream; +use actix_server::Server; +use actix_service::{fn_service, ServiceFactoryExt}; + +const H2_PREFACE: &[u8] = b"PRI * HTTP/2"; + +#[actix_rt::main] +async fn main() -> io::Result<()> { + env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); + + Server::build() + .bind("h2c-detect", ("127.0.0.1", 8080), || { + fn_service(move |io: TcpStream| async move { + let mut buf = [0; 12]; + + io.peek(&mut buf).await?; + + let proto = if buf == H2_PREFACE { + tracing::info!("selecting h2c"); + Protocol::Http2 + } else { + tracing::info!("selecting h1"); + Protocol::Http1 + }; + + let peer_addr = io.peer_addr().ok(); + Ok::<_, io::Error>((io, proto, peer_addr)) + }) + .and_then( + HttpService::build() + .finish(|_req: Request| async move { + Ok::<_, Infallible>(Response::build(StatusCode::OK).body("Hello!")) + }) + .map_err(|err| { + tracing::error!("{}", err); + io::Error::new(io::ErrorKind::Other, "http service dispatch error") + }), + ) + })? + .workers(2) + .run() + .await +} From d2364c80c4311d70f32a6a50e26a2a3ef43a9206 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 2 Jan 2023 00:16:59 +0000 Subject: [PATCH 17/24] improve error handling on new new example --- actix-http/examples/h2c-detect.rs | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/actix-http/examples/h2c-detect.rs b/actix-http/examples/h2c-detect.rs index e1491324b..550a03d2a 100644 --- a/actix-http/examples/h2c-detect.rs +++ b/actix-http/examples/h2c-detect.rs @@ -8,7 +8,7 @@ use std::{convert::Infallible, io}; -use actix_http::{HttpService, Protocol, Request, Response, StatusCode}; +use actix_http::{error::DispatchError, HttpService, Protocol, Request, Response, StatusCode}; use actix_rt::net::TcpStream; use actix_server::Server; use actix_service::{fn_service, ServiceFactoryExt}; @@ -24,7 +24,7 @@ async fn main() -> io::Result<()> { fn_service(move |io: TcpStream| async move { let mut buf = [0; 12]; - io.peek(&mut buf).await?; + io.peek(&mut buf).await.map_err(DispatchError::Io)?; let proto = if buf == H2_PREFACE { tracing::info!("selecting h2c"); @@ -35,18 +35,11 @@ async fn main() -> io::Result<()> { }; let peer_addr = io.peer_addr().ok(); - Ok::<_, io::Error>((io, proto, peer_addr)) + Ok((io, proto, peer_addr)) }) - .and_then( - HttpService::build() - .finish(|_req: Request| async move { - Ok::<_, Infallible>(Response::build(StatusCode::OK).body("Hello!")) - }) - .map_err(|err| { - tracing::error!("{}", err); - io::Error::new(io::ErrorKind::Other, "http service dispatch error") - }), - ) + .and_then(HttpService::build().finish(|_req: Request| async move { + Ok::<_, Infallible>(Response::build(StatusCode::OK).body("Hello!")) + })) })? .workers(2) .run() From 7b936bc443d22fca679a1fca72b488adfecee3e6 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 2 Jan 2023 13:33:31 +0000 Subject: [PATCH 18/24] add some useful header name constants (#2956) --- actix-http/CHANGES.md | 8 +++++++ actix-http/src/header/common.rs | 39 +++++++++++++++++++++++++++++++++ actix-http/src/header/mod.rs | 35 +++++++++++++++++++---------- 3 files changed, 70 insertions(+), 12 deletions(-) create mode 100644 actix-http/src/header/common.rs diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 045ae461f..3c820ccf8 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -4,12 +4,20 @@ ### Added - Implement `MessageBody` for `&mut B` where `B: MessageBody + Unpin`. [#2868] - Implement `MessageBody` for `Pin` where `B::Target: MessageBody`. [#2868] +- Header name constants in `header` module. [#2956] + - `CROSS_ORIGIN_EMBEDDER_POLICY` + - `CROSS_ORIGIN_OPENER_POLICY` + - `PERMISSIONS_POLICY` + - `X_FORWARDED_FOR` + - `X_FORWARDED_HOST` + - `X_FORWARDED_PROTO` ### Performance - Improve overall performance of operations on `Extensions`. [#2890] [#2868]: https://github.com/actix/actix-web/pull/2868 [#2890]: https://github.com/actix/actix-web/pull/2890 +[#2956]: https://github.com/actix/actix-web/pull/2956 ## 3.2.2 - 2022-09-11 diff --git a/actix-http/src/header/common.rs b/actix-http/src/header/common.rs new file mode 100644 index 000000000..52909099a --- /dev/null +++ b/actix-http/src/header/common.rs @@ -0,0 +1,39 @@ +//! Common header names not defined in [`http`]. +//! +//! Any headers added to this file will need to be re-exported from the list at `crate::headers`. + +use http::header::HeaderName; + +/// Response header that prevents a document from loading any cross-origin resources that don't +/// explicitly grant the document permission (using [CORP] or [CORS]). +/// +/// [CORP]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Cross-Origin_Resource_Policy_(CORP) +/// [CORS]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS +pub const CROSS_ORIGIN_EMBEDDER_POLICY: HeaderName = + HeaderName::from_static("cross-origin-embedder-policy"); + +/// Response header that allows you to ensure a top-level document does not share a browsing context +/// group with cross-origin documents. +pub const CROSS_ORIGIN_OPENER_POLICY: HeaderName = + HeaderName::from_static("cross-origin-opener-policy"); + +/// Response header that conveys a desire that the browser blocks no-cors cross-origin/cross-site +/// requests to the given resource. +pub const CROSS_ORIGIN_RESOURCE_POLICY: HeaderName = + HeaderName::from_static("cross-origin-resource-policy"); + +/// Response header that provides a mechanism to allow and deny the use of browser features in a +/// document or within any `