mirror of https://github.com/fafhrd91/actix-web
Merge branch 'master' into multipart-forms
This commit is contained in:
commit
7344a0f4fa
|
@ -5,6 +5,9 @@ on:
|
|||
branches:
|
||||
- master
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
check_benchmark:
|
||||
runs-on: ubuntu-latest
|
||||
|
|
|
@ -4,6 +4,9 @@ on:
|
|||
push:
|
||||
branches: [master]
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
build_and_test_nightly:
|
||||
strategy:
|
||||
|
@ -92,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
|
||||
|
@ -127,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
|
||||
|
|
|
@ -6,6 +6,9 @@ on:
|
|||
push:
|
||||
branches: [master]
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
strategy:
|
||||
|
@ -63,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 }
|
||||
|
@ -96,16 +104,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
|
||||
|
||||
|
@ -123,20 +125,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 }
|
||||
|
|
|
@ -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
|
||||
with: { components: rustfmt }
|
||||
- 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:
|
||||
|
|
|
@ -4,31 +4,29 @@ 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:
|
||||
- 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 '<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.4.0
|
||||
uses: JamesIves/github-pages-deploy-action@v4.4.1
|
||||
with:
|
||||
folder: target/doc
|
||||
single-commit: true
|
||||
|
|
|
@ -3,7 +3,6 @@ name = "actix-files"
|
|||
version = "0.6.2"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"fakeshadow <24548779@qq.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
]
|
||||
description = "Static file serving for Actix Web"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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::{
|
||||
|
|
|
@ -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};
|
||||
|
||||
|
|
|
@ -30,8 +30,8 @@ futures-util = { version = "0.3.7", default-features = false }
|
|||
httparse = "1.3"
|
||||
local-waker = "0.1"
|
||||
log = "0.4"
|
||||
memchr = "2.5"
|
||||
mime = "0.3"
|
||||
twoway = "0.2"
|
||||
serde = "1.0"
|
||||
serde_plain = "1.0"
|
||||
serde_json = "1.0"
|
||||
|
|
|
@ -606,7 +606,7 @@ impl InnerField {
|
|||
}
|
||||
|
||||
loop {
|
||||
return if let Some(idx) = twoway::find_bytes(&payload.buf[pos..], b"\r") {
|
||||
return if let Some(idx) = memchr::memmem::find(&payload.buf[pos..], b"\r") {
|
||||
let cur = pos + idx;
|
||||
|
||||
// check if we have enough data for boundary detection
|
||||
|
@ -827,7 +827,7 @@ impl PayloadBuffer {
|
|||
|
||||
/// Read until specified ending
|
||||
fn read_until(&mut self, line: &[u8]) -> Result<Option<Bytes>, MultipartError> {
|
||||
let res = twoway::find_bytes(&self.buf, line)
|
||||
let res = memchr::memmem::find(&self.buf, line)
|
||||
.map(|idx| self.buf.split_to(idx + line.len()).freeze());
|
||||
|
||||
if res.is_none() && self.eof {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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.
|
||||
///
|
||||
|
|
|
@ -5,7 +5,11 @@
|
|||
- 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
|
||||
[#2867]: https://github.com/actix/actix-web/pull/2867
|
||||
|
|
|
@ -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::<Accept>() {
|
||||
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()));
|
||||
}
|
||||
}
|
|
@ -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> {
|
|
@ -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<Mime>)*
|
||||
|
||||
test_parse_and_format {
|
||||
|
@ -106,8 +107,7 @@ common_header! {
|
|||
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]
|
||||
|
|
|
@ -86,6 +86,7 @@ mod helpers;
|
|||
pub mod http;
|
||||
mod info;
|
||||
pub mod middleware;
|
||||
mod redirect;
|
||||
mod request;
|
||||
mod request_data;
|
||||
mod resource;
|
||||
|
|
|
@ -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<Cow<'static, str>>, to: impl Into<Cow<'static, str>>) -> 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<Cow<'static, str>>) -> 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<Self::Body> {
|
||||
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");
|
||||
}
|
||||
}
|
|
@ -327,9 +327,7 @@ impl ServiceRequest {
|
|||
.push(extensions);
|
||||
}
|
||||
|
||||
/// Creates a context object for use with a [guard](crate::guard).
|
||||
///
|
||||
/// Useful if you are implementing
|
||||
/// Creates a context object for use with a routing [guard](crate::guard).
|
||||
#[inline]
|
||||
pub fn guard_ctx(&self) -> GuardContext<'_> {
|
||||
GuardContext { req: self }
|
||||
|
|
|
@ -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<Info>) -> String {
|
||||
/// async fn index(web::Form(form): web::Form<Info>) -> String {
|
||||
/// format!("Welcome {}!", form.name)
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Responder
|
||||
/// The `Form` type also allows you to create URL encoded responses:
|
||||
/// simply return a value of type Form<T> 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<T>` 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<SomeForm> {
|
||||
/// web::Form(SomeForm {
|
||||
/// name: "actix".into(),
|
||||
/// name: "actix".to_owned(),
|
||||
/// age: 123
|
||||
/// })
|
||||
/// }
|
||||
|
|
|
@ -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<T: IntoPatterns>(path: T) -> Resource {
|
|||
/// - `/{project_id}/path2`
|
||||
/// - `/{project_id}/path3`
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_web::{web, App, HttpResponse};
|
||||
///
|
||||
|
@ -183,6 +188,25 @@ pub fn service<T: IntoPatterns>(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<Cow<'static, str>>,
|
||||
to: impl Into<Cow<'static, str>>,
|
||||
) -> 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, R>(f: F) -> impl Future<Output = Result<R, BlockingError>>
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
[package]
|
||||
name = "awc"
|
||||
version = "3.0.1"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"fakeshadow <24548779@qq.com>",
|
||||
]
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Async HTTP and WebSocket client library"
|
||||
keywords = ["actix", "http", "framework", "async", "web"]
|
||||
categories = [
|
||||
|
|
Loading…
Reference in New Issue