mirror of https://github.com/fafhrd91/actix-web
Merge branch 'master' into hide-authorization-header-in-httprequest-debug-output
This commit is contained in:
commit
bc78ceb420
|
@ -26,39 +26,26 @@ jobs:
|
||||||
name: ${{ matrix.target.name }} / ${{ matrix.version }}
|
name: ${{ matrix.target.name }} / ${{ matrix.version }}
|
||||||
runs-on: ${{ matrix.target.os }}
|
runs-on: ${{ matrix.target.os }}
|
||||||
|
|
||||||
env:
|
|
||||||
CI: 1
|
|
||||||
CARGO_INCREMENTAL: 0
|
|
||||||
VCPKGRS_DYNAMIC: 1
|
|
||||||
CARGO_UNSTABLE_SPARSE_REGISTRY: true
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
# install OpenSSL on Windows
|
|
||||||
# TODO: GitHub actions docs state that OpenSSL is
|
|
||||||
# already installed on these Windows machines somewhere
|
|
||||||
- name: Set vcpkg root
|
|
||||||
if: matrix.target.triple == 'x86_64-pc-windows-msvc'
|
|
||||||
run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append
|
|
||||||
- name: Install OpenSSL
|
- name: Install OpenSSL
|
||||||
if: matrix.target.triple == 'x86_64-pc-windows-msvc'
|
if: matrix.target.os == 'windows-latest'
|
||||||
run: vcpkg install openssl:x64-windows
|
run: choco install openssl -y --forcex64 --no-progress
|
||||||
|
- name: Set OpenSSL dir in env
|
||||||
- name: Install ${{ matrix.version }}
|
if: matrix.target.os == 'windows-latest'
|
||||||
run: |
|
run: |
|
||||||
rustup set profile minimal
|
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL-Win64' | Out-File -FilePath $env:GITHUB_ENV -Append
|
||||||
rustup install ${{ matrix.version }}
|
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' | Out-File -FilePath $env:GITHUB_ENV -Append
|
||||||
rustup override set ${{ matrix.version }}
|
|
||||||
|
- name: Install Rust (${{ matrix.version }})
|
||||||
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: ${{ matrix.version }}
|
||||||
|
|
||||||
- name: Install cargo-hack
|
- name: Install cargo-hack
|
||||||
uses: taiki-e/install-action@cargo-hack
|
uses: taiki-e/install-action@cargo-hack
|
||||||
|
|
||||||
- name: Generate Cargo.lock
|
|
||||||
run: cargo generate-lockfile
|
|
||||||
- name: Cache Dependencies
|
|
||||||
uses: Swatinem/rust-cache@v2.2.1
|
|
||||||
|
|
||||||
- name: check minimal
|
- name: check minimal
|
||||||
run: cargo ci-check-min
|
run: cargo ci-check-min
|
||||||
|
|
||||||
|
@ -88,23 +75,15 @@ jobs:
|
||||||
name: Verify Feature Combinations
|
name: Verify Feature Combinations
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
env:
|
|
||||||
CI: 1
|
|
||||||
CARGO_INCREMENTAL: 0
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- name: Install Rust
|
||||||
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
|
||||||
- name: Install cargo-hack
|
- name: Install cargo-hack
|
||||||
uses: taiki-e/install-action@cargo-hack
|
uses: taiki-e/install-action@cargo-hack
|
||||||
|
|
||||||
- name: Generate Cargo.lock
|
|
||||||
run: cargo generate-lockfile
|
|
||||||
- name: Cache Dependencies
|
|
||||||
uses: Swatinem/rust-cache@v2.2.1
|
|
||||||
|
|
||||||
- name: check feature combinations
|
- name: check feature combinations
|
||||||
run: cargo ci-check-all-feature-powerset
|
run: cargo ci-check-all-feature-powerset
|
||||||
|
|
||||||
|
@ -115,22 +94,14 @@ jobs:
|
||||||
name: nextest
|
name: nextest
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
env:
|
|
||||||
CI: 1
|
|
||||||
CARGO_INCREMENTAL: 0
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- name: Install Rust
|
||||||
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
|
||||||
- name: Install nextest
|
- name: Install nextest
|
||||||
uses: taiki-e/install-action@nextest
|
uses: taiki-e/install-action@nextest
|
||||||
|
|
||||||
- name: Generate Cargo.lock
|
|
||||||
run: cargo generate-lockfile
|
|
||||||
- name: Cache Dependencies
|
|
||||||
uses: Swatinem/rust-cache@v2.2.1
|
|
||||||
|
|
||||||
- name: Test with cargo-nextest
|
- name: Test with cargo-nextest
|
||||||
run: cargo nextest run
|
run: cargo nextest run
|
||||||
|
|
|
@ -23,23 +23,23 @@ jobs:
|
||||||
- { name: macOS, os: macos-latest, triple: x86_64-apple-darwin }
|
- { name: macOS, os: macos-latest, triple: x86_64-apple-darwin }
|
||||||
- { name: Windows, os: windows-latest, triple: x86_64-pc-windows-msvc }
|
- { name: Windows, os: windows-latest, triple: x86_64-pc-windows-msvc }
|
||||||
version:
|
version:
|
||||||
- 1.59.0 # MSRV
|
- 1.65.0 # MSRV
|
||||||
- stable
|
- stable
|
||||||
|
|
||||||
name: ${{ matrix.target.name }} / ${{ matrix.version }}
|
name: ${{ matrix.target.name }} / ${{ matrix.version }}
|
||||||
runs-on: ${{ matrix.target.os }}
|
runs-on: ${{ matrix.target.os }}
|
||||||
|
|
||||||
env: {}
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Install OpenSSL
|
- name: Install OpenSSL
|
||||||
if: matrix.target.os == 'windows-latest'
|
if: matrix.target.os == 'windows-latest'
|
||||||
run: choco install openssl
|
run: choco install openssl -y --forcex64 --no-progress
|
||||||
- name: Set OpenSSL dir in env
|
- name: Set OpenSSL dir in env
|
||||||
if: matrix.target.os == 'windows-latest'
|
if: matrix.target.os == 'windows-latest'
|
||||||
run: echo 'OPENSSL_DIR=C:\Program Files\OpenSSL-Win64' | Out-File -FilePath $env:GITHUB_ENV -Append
|
run: |
|
||||||
|
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL-Win64' | Out-File -FilePath $env:GITHUB_ENV -Append
|
||||||
|
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' | Out-File -FilePath $env:GITHUB_ENV -Append
|
||||||
|
|
||||||
- name: Install Rust (${{ matrix.version }})
|
- name: Install Rust (${{ matrix.version }})
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
@ -49,17 +49,9 @@ jobs:
|
||||||
- name: Install cargo-hack
|
- name: Install cargo-hack
|
||||||
uses: taiki-e/install-action@cargo-hack
|
uses: taiki-e/install-action@cargo-hack
|
||||||
|
|
||||||
- name: workaround MSRV issues
|
# - name: workaround MSRV issues
|
||||||
if: matrix.version != 'stable'
|
# if: matrix.version != 'stable'
|
||||||
run: |
|
# run: |
|
||||||
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
|
|
||||||
|
|
||||||
- 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
|
- name: check minimal
|
||||||
run: cargo ci-check-min
|
run: cargo ci-check-min
|
||||||
|
|
|
@ -63,7 +63,8 @@ jobs:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
with: { toolchain: nightly }
|
# temp: unpin once https://github.com/rust-lang/rust/issues/113152 is fixed
|
||||||
|
with: { toolchain: nightly-2023-06-28 }
|
||||||
|
|
||||||
- uses: taiki-e/cache-cargo-install-action@v1
|
- uses: taiki-e/cache-cargo-install-action@v1
|
||||||
with: { tool: cargo-public-api }
|
with: { tool: cargo-public-api }
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
group_imports = "StdExternalCrate"
|
||||||
|
imports_granularity = "Crate"
|
||||||
|
use_field_init_shorthand = true
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
## Unreleased - 2023-xx-xx
|
## Unreleased - 2023-xx-xx
|
||||||
|
|
||||||
|
- Minimum supported Rust version (MSRV) is now 1.65 due to transitive `time` dependency.
|
||||||
|
|
||||||
## 0.6.3 - 2023-01-21
|
## 0.6.3 - 2023-01-21
|
||||||
|
|
||||||
- XHTML files now use `Content-Disposition: inline` instead of `attachment`. [#2903]
|
- XHTML files now use `Content-Disposition: inline` instead of `attachment`. [#2903]
|
||||||
|
|
|
@ -11,7 +11,7 @@ homepage = "https://actix.rs"
|
||||||
repository = "https://github.com/actix/actix-web"
|
repository = "https://github.com/actix/actix-web"
|
||||||
categories = ["asynchronous", "web-programming::http-server"]
|
categories = ["asynchronous", "web-programming::http-server"]
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "actix_files"
|
name = "actix_files"
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-files)
|
[](https://crates.io/crates/actix-files)
|
||||||
[](https://docs.rs/actix-files/0.6.3)
|
[](https://docs.rs/actix-files/0.6.3)
|
||||||

|

|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-files/0.6.3)
|
[](https://deps.rs/crate/actix-files/0.6.3)
|
||||||
|
@ -15,4 +15,4 @@
|
||||||
|
|
||||||
- [API Documentation](https://docs.rs/actix-files)
|
- [API Documentation](https://docs.rs/actix-files)
|
||||||
- [Example Project](https://github.com/actix/examples/tree/master/basics/static-files)
|
- [Example Project](https://github.com/actix/examples/tree/master/basics/static-files)
|
||||||
- Minimum Supported Rust Version (MSRV): 1.59
|
- Minimum Supported Rust Version (MSRV): 1.65
|
||||||
|
|
|
@ -7,11 +7,10 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use actix_web::{error::Error, web::Bytes};
|
use actix_web::{error::Error, web::Bytes};
|
||||||
use futures_core::{ready, Stream};
|
|
||||||
use pin_project_lite::pin_project;
|
|
||||||
|
|
||||||
#[cfg(feature = "experimental-io-uring")]
|
#[cfg(feature = "experimental-io-uring")]
|
||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
|
use futures_core::{ready, Stream};
|
||||||
|
use pin_project_lite::pin_project;
|
||||||
|
|
||||||
use super::named::File;
|
use super::named::File;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
use std::{fmt::Write, fs::DirEntry, io, path::Path, path::PathBuf};
|
use std::{
|
||||||
|
fmt::Write,
|
||||||
|
fs::DirEntry,
|
||||||
|
io,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
use actix_web::{dev::ServiceResponse, HttpRequest, HttpResponse};
|
use actix_web::{dev::ServiceResponse, HttpRequest, HttpResponse};
|
||||||
use percent_encoding::{utf8_percent_encode, CONTROLS};
|
use percent_encoding::{utf8_percent_encode, CONTROLS};
|
||||||
|
|
|
@ -8,8 +8,7 @@ use std::{
|
||||||
use actix_service::{boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt};
|
use actix_service::{boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt};
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
dev::{
|
dev::{
|
||||||
AppService, HttpServiceFactory, RequestHead, ResourceDef, ServiceRequest,
|
AppService, HttpServiceFactory, RequestHead, ResourceDef, ServiceRequest, ServiceResponse,
|
||||||
ServiceResponse,
|
|
||||||
},
|
},
|
||||||
error::Error,
|
error::Error,
|
||||||
guard::Guard,
|
guard::Guard,
|
||||||
|
@ -301,12 +300,8 @@ impl Files {
|
||||||
pub fn default_handler<F, U>(mut self, f: F) -> Self
|
pub fn default_handler<F, U>(mut self, f: F) -> Self
|
||||||
where
|
where
|
||||||
F: IntoServiceFactory<U, ServiceRequest>,
|
F: IntoServiceFactory<U, ServiceRequest>,
|
||||||
U: ServiceFactory<
|
U: ServiceFactory<ServiceRequest, Config = (), Response = ServiceResponse, Error = Error>
|
||||||
ServiceRequest,
|
+ 'static,
|
||||||
Config = (),
|
|
||||||
Response = ServiceResponse,
|
|
||||||
Error = Error,
|
|
||||||
> + 'static,
|
|
||||||
{
|
{
|
||||||
// create and configure default resource
|
// create and configure default resource
|
||||||
self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::factory(
|
self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::factory(
|
||||||
|
@ -422,10 +417,14 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(res.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
let body = test::read_body(res).await;
|
let body = test::read_body(res).await;
|
||||||
|
let body_str = std::str::from_utf8(&body).unwrap();
|
||||||
|
let actual_path = Path::new(&body_str);
|
||||||
|
let expected_path = Path::new("actix-files/tests");
|
||||||
assert!(
|
assert!(
|
||||||
body.ends_with(b"actix-files/tests/"),
|
actual_path.ends_with(expected_path),
|
||||||
"body {:?} does not end with `actix-files/tests/`",
|
"body {:?} does not end with {:?}",
|
||||||
body
|
actual_path,
|
||||||
|
expected_path
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,8 @@
|
||||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use actix_service::boxed::{BoxService, BoxServiceFactory};
|
use actix_service::boxed::{BoxService, BoxServiceFactory};
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
dev::{RequestHead, ServiceRequest, ServiceResponse},
|
dev::{RequestHead, ServiceRequest, ServiceResponse},
|
||||||
|
@ -25,7 +27,6 @@ use actix_web::{
|
||||||
http::header::DispositionType,
|
http::header::DispositionType,
|
||||||
};
|
};
|
||||||
use mime_guess::from_ext;
|
use mime_guess::from_ext;
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
mod chunked;
|
mod chunked;
|
||||||
mod directory;
|
mod directory;
|
||||||
|
@ -37,16 +38,15 @@ mod path_buf;
|
||||||
mod range;
|
mod range;
|
||||||
mod service;
|
mod service;
|
||||||
|
|
||||||
pub use self::chunked::ChunkedReadFile;
|
pub use self::{
|
||||||
pub use self::directory::Directory;
|
chunked::ChunkedReadFile, directory::Directory, files::Files, named::NamedFile,
|
||||||
pub use self::files::Files;
|
range::HttpRange, service::FilesService,
|
||||||
pub use self::named::NamedFile;
|
};
|
||||||
pub use self::range::HttpRange;
|
use self::{
|
||||||
pub use self::service::FilesService;
|
directory::{directory_listing, DirectoryRenderer},
|
||||||
|
error::FilesError,
|
||||||
use self::directory::{directory_listing, DirectoryRenderer};
|
path_buf::PathBufWrap,
|
||||||
use self::error::FilesError;
|
};
|
||||||
use self::path_buf::PathBufWrap;
|
|
||||||
|
|
||||||
type HttpService = BoxService<ServiceRequest, ServiceResponse, Error>;
|
type HttpService = BoxService<ServiceRequest, ServiceResponse, Error>;
|
||||||
type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>;
|
type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>;
|
||||||
|
@ -554,10 +554,9 @@ mod tests {
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_static_files_with_spaces() {
|
async fn test_static_files_with_spaces() {
|
||||||
let srv = test::init_service(
|
let srv =
|
||||||
App::new().service(Files::new("/", ".").index_file("Cargo.toml")),
|
test::init_service(App::new().service(Files::new("/", ".").index_file("Cargo.toml")))
|
||||||
)
|
.await;
|
||||||
.await;
|
|
||||||
let request = TestRequest::get()
|
let request = TestRequest::get()
|
||||||
.uri("/tests/test%20space.binary")
|
.uri("/tests/test%20space.binary")
|
||||||
.to_request();
|
.to_request();
|
||||||
|
@ -667,8 +666,7 @@ mod tests {
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_static_files() {
|
async fn test_static_files() {
|
||||||
let srv =
|
let srv =
|
||||||
test::init_service(App::new().service(Files::new("/", ".").show_files_listing()))
|
test::init_service(App::new().service(Files::new("/", ".").show_files_listing())).await;
|
||||||
.await;
|
|
||||||
let req = TestRequest::with_uri("/missing").to_request();
|
let req = TestRequest::with_uri("/missing").to_request();
|
||||||
|
|
||||||
let resp = test::call_service(&srv, req).await;
|
let resp = test::call_service(&srv, req).await;
|
||||||
|
@ -681,8 +679,7 @@ mod tests {
|
||||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
let srv =
|
let srv =
|
||||||
test::init_service(App::new().service(Files::new("/", ".").show_files_listing()))
|
test::init_service(App::new().service(Files::new("/", ".").show_files_listing())).await;
|
||||||
.await;
|
|
||||||
let req = TestRequest::with_uri("/tests").to_request();
|
let req = TestRequest::with_uri("/tests").to_request();
|
||||||
let resp = test::call_service(&srv, req).await;
|
let resp = test::call_service(&srv, req).await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
|
@ -8,13 +8,13 @@ use std::{
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
body::{self, BoxBody, SizedStream},
|
body::{self, BoxBody, SizedStream},
|
||||||
dev::{
|
dev::{
|
||||||
self, AppService, HttpServiceFactory, ResourceDef, Service, ServiceFactory,
|
self, AppService, HttpServiceFactory, ResourceDef, Service, ServiceFactory, ServiceRequest,
|
||||||
ServiceRequest, ServiceResponse,
|
ServiceResponse,
|
||||||
},
|
},
|
||||||
http::{
|
http::{
|
||||||
header::{
|
header::{
|
||||||
self, Charset, ContentDisposition, ContentEncoding, DispositionParam,
|
self, Charset, ContentDisposition, ContentEncoding, DispositionParam, DispositionType,
|
||||||
DispositionType, ExtendedValue, HeaderValue,
|
ExtendedValue, HeaderValue,
|
||||||
},
|
},
|
||||||
StatusCode,
|
StatusCode,
|
||||||
},
|
},
|
||||||
|
@ -85,6 +85,7 @@ pub struct NamedFile {
|
||||||
|
|
||||||
#[cfg(not(feature = "experimental-io-uring"))]
|
#[cfg(not(feature = "experimental-io-uring"))]
|
||||||
pub(crate) use std::fs::File;
|
pub(crate) use std::fs::File;
|
||||||
|
|
||||||
#[cfg(feature = "experimental-io-uring")]
|
#[cfg(feature = "experimental-io-uring")]
|
||||||
pub(crate) use tokio_uring::fs::File;
|
pub(crate) use tokio_uring::fs::File;
|
||||||
|
|
||||||
|
@ -139,8 +140,7 @@ impl NamedFile {
|
||||||
_ => DispositionType::Attachment,
|
_ => DispositionType::Attachment,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut parameters =
|
let mut parameters = vec![DispositionParam::Filename(String::from(filename.as_ref()))];
|
||||||
vec![DispositionParam::Filename(String::from(filename.as_ref()))];
|
|
||||||
|
|
||||||
if !filename.is_ascii() {
|
if !filename.is_ascii() {
|
||||||
parameters.push(DispositionParam::FilenameExt(ExtendedValue {
|
parameters.push(DispositionParam::FilenameExt(ExtendedValue {
|
||||||
|
|
|
@ -97,8 +97,6 @@ impl FromRequest for PathBufWrap {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::iter::FromIterator;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -48,8 +48,8 @@ impl HttpRange {
|
||||||
/// `header` is HTTP Range header (e.g. `bytes=bytes=0-9`).
|
/// `header` is HTTP Range header (e.g. `bytes=bytes=0-9`).
|
||||||
/// `size` is full size of response (file).
|
/// `size` is full size of response (file).
|
||||||
pub fn parse(header: &str, size: u64) -> Result<Vec<HttpRange>, ParseRangeErr> {
|
pub fn parse(header: &str, size: u64) -> Result<Vec<HttpRange>, ParseRangeErr> {
|
||||||
let ranges = http_range::HttpRange::parse(header, size)
|
let ranges =
|
||||||
.map_err(|err| ParseRangeErr(err.into()))?;
|
http_range::HttpRange::parse(header, size).map_err(|err| ParseRangeErr(err.into()))?;
|
||||||
|
|
||||||
Ok(ranges
|
Ok(ranges
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -62,11 +62,7 @@ impl FilesService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serve_named_file(
|
fn serve_named_file(&self, req: ServiceRequest, mut named_file: NamedFile) -> ServiceResponse {
|
||||||
&self,
|
|
||||||
req: ServiceRequest,
|
|
||||||
mut named_file: NamedFile,
|
|
||||||
) -> ServiceResponse {
|
|
||||||
if let Some(ref mime_override) = self.mime_override {
|
if let Some(ref mime_override) = self.mime_override {
|
||||||
let new_disposition = mime_override(&named_file.content_type.type_());
|
let new_disposition = mime_override(&named_file.content_type.type_());
|
||||||
named_file.content_disposition.disposition = new_disposition;
|
named_file.content_disposition.disposition = new_disposition;
|
||||||
|
@ -120,13 +116,11 @@ impl Service<ServiceRequest> for FilesService {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let path_on_disk = match PathBufWrap::parse_path(
|
let path_on_disk =
|
||||||
req.match_info().unprocessed(),
|
match PathBufWrap::parse_path(req.match_info().unprocessed(), this.hidden_files) {
|
||||||
this.hidden_files,
|
Ok(item) => item,
|
||||||
) {
|
Err(err) => return Ok(req.error_response(err)),
|
||||||
Ok(item) => item,
|
};
|
||||||
Err(err) => return Ok(req.error_response(err)),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(filter) = &this.path_filter {
|
if let Some(filter) = &this.path_filter {
|
||||||
if !filter(path_on_disk.as_ref(), req.head()) {
|
if !filter(path_on_disk.as_ref(), req.head()) {
|
||||||
|
@ -177,8 +171,7 @@ impl Service<ServiceRequest> for FilesService {
|
||||||
match NamedFile::open_async(&path).await {
|
match NamedFile::open_async(&path).await {
|
||||||
Ok(mut named_file) => {
|
Ok(mut named_file) => {
|
||||||
if let Some(ref mime_override) = this.mime_override {
|
if let Some(ref mime_override) = this.mime_override {
|
||||||
let new_disposition =
|
let new_disposition = mime_override(&named_file.content_type.type_());
|
||||||
mime_override(&named_file.content_type.type_());
|
|
||||||
named_file.content_disposition.disposition = new_disposition;
|
named_file.content_disposition.disposition = new_disposition;
|
||||||
}
|
}
|
||||||
named_file.flags = this.file_flags;
|
named_file.flags = this.file_flags;
|
||||||
|
|
|
@ -24,8 +24,7 @@ async fn test_utf8_file_contents() {
|
||||||
|
|
||||||
// disable UTF-8 attribute
|
// disable UTF-8 attribute
|
||||||
let srv =
|
let srv =
|
||||||
test::init_service(App::new().service(Files::new("/", "./tests").prefer_utf8(false)))
|
test::init_service(App::new().service(Files::new("/", "./tests").prefer_utf8(false))).await;
|
||||||
.await;
|
|
||||||
|
|
||||||
let req = TestRequest::with_uri("/utf8.txt").to_request();
|
let req = TestRequest::with_uri("/utf8.txt").to_request();
|
||||||
let res = test::call_service(&srv, req).await;
|
let res = test::call_service(&srv, req).await;
|
||||||
|
|
|
@ -12,9 +12,7 @@ async fn test_guard_filter() {
|
||||||
let srv = test::init_service(
|
let srv = test::init_service(
|
||||||
App::new()
|
App::new()
|
||||||
.service(Files::new("/", "./tests/fixtures/guards/first").guard(Host("first.com")))
|
.service(Files::new("/", "./tests/fixtures/guards/first").guard(Host("first.com")))
|
||||||
.service(
|
.service(Files::new("/", "./tests/fixtures/guards/second").guard(Host("second.com"))),
|
||||||
Files::new("/", "./tests/fixtures/guards/second").guard(Host("second.com")),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,7 @@ use actix_web::{
|
||||||
async fn test_directory_traversal_prevention() {
|
async fn test_directory_traversal_prevention() {
|
||||||
let srv = test::init_service(App::new().service(Files::new("/", "./tests"))).await;
|
let srv = test::init_service(App::new().service(Files::new("/", "./tests"))).await;
|
||||||
|
|
||||||
let req =
|
let req = TestRequest::with_uri("/../../../../../../../../../../../etc/passwd").to_request();
|
||||||
TestRequest::with_uri("/../../../../../../../../../../../etc/passwd").to_request();
|
|
||||||
let res = test::call_service(&srv, req).await;
|
let res = test::call_service(&srv, req).await;
|
||||||
assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
## Unreleased - 2023-xx-xx
|
## Unreleased - 2023-xx-xx
|
||||||
|
|
||||||
|
- Minimum supported Rust version (MSRV) is now 1.65 due to transitive `time` dependency.
|
||||||
|
|
||||||
## 3.1.0 - 2023-01-21
|
## 3.1.0 - 2023-01-21
|
||||||
|
|
||||||
- Minimum supported Rust version (MSRV) is now 1.59.
|
- Minimum supported Rust version (MSRV) is now 1.59.
|
||||||
|
|
|
@ -13,7 +13,7 @@ categories = [
|
||||||
"web-programming::websocket",
|
"web-programming::websocket",
|
||||||
]
|
]
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = []
|
features = []
|
||||||
|
@ -41,12 +41,12 @@ bytes = "1"
|
||||||
futures-core = { version = "0.3.17", default-features = false }
|
futures-core = { version = "0.3.17", default-features = false }
|
||||||
http = "0.2.7"
|
http = "0.2.7"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
socket2 = "0.4"
|
socket2 = "0.5"
|
||||||
serde = "1"
|
serde = "1"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
slab = "0.4"
|
slab = "0.4"
|
||||||
serde_urlencoded = "0.7"
|
serde_urlencoded = "0.7"
|
||||||
tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
|
tls-openssl = { version = "0.10.55", package = "openssl", optional = true }
|
||||||
tokio = { version = "1.24.2", features = ["sync"] }
|
tokio = { version = "1.24.2", features = ["sync"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-http-test)
|
[](https://crates.io/crates/actix-http-test)
|
||||||
[](https://docs.rs/actix-http-test/3.1.0)
|
[](https://docs.rs/actix-http-test/3.1.0)
|
||||||

|

|
||||||

|

|
||||||
<br>
|
<br>
|
||||||
[](https://deps.rs/crate/actix-http-test/3.1.0)
|
[](https://deps.rs/crate/actix-http-test/3.1.0)
|
||||||
|
@ -14,4 +14,4 @@
|
||||||
## Documentation & Resources
|
## Documentation & Resources
|
||||||
|
|
||||||
- [API Documentation](https://docs.rs/actix-http-test)
|
- [API Documentation](https://docs.rs/actix-http-test)
|
||||||
- Minimum Supported Rust Version (MSRV): 1.59
|
- Minimum Supported Rust Version (MSRV): 1.65
|
||||||
|
|
|
@ -7,6 +7,10 @@
|
||||||
- Add `body::to_body_limit()` function.
|
- Add `body::to_body_limit()` function.
|
||||||
- Add `body::BodyLimitExceeded` error type.
|
- Add `body::BodyLimitExceeded` error type.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Minimum supported Rust version (MSRV) is now 1.65 due to transitive `time` dependency.
|
||||||
|
|
||||||
## 3.3.1 - 2023-03-02
|
## 3.3.1 - 2023-03-02
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
|
@ -16,7 +16,7 @@ categories = [
|
||||||
"web-programming::websocket",
|
"web-programming::websocket",
|
||||||
]
|
]
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
# features that docs.rs will build with
|
# features that docs.rs will build with
|
||||||
|
@ -105,20 +105,20 @@ actix-tls = { version = "3", features = ["openssl"] }
|
||||||
actix-web = "4"
|
actix-web = "4"
|
||||||
|
|
||||||
async-stream = "0.3"
|
async-stream = "0.3"
|
||||||
criterion = { version = "0.4", features = ["html_reports"] }
|
criterion = { version = "0.5", features = ["html_reports"] }
|
||||||
env_logger = "0.9"
|
env_logger = "0.10"
|
||||||
futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] }
|
futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] }
|
||||||
memchr = "2.4"
|
memchr = "2.4"
|
||||||
once_cell = "1.9"
|
once_cell = "1.9"
|
||||||
rcgen = "0.9"
|
rcgen = "0.11"
|
||||||
regex = "1.3"
|
regex = "1.3"
|
||||||
rustversion = "1"
|
rustversion = "1"
|
||||||
rustls-pemfile = "1"
|
rustls-pemfile = "1"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
static_assertions = "1"
|
static_assertions = "1"
|
||||||
tls-openssl = { package = "openssl", version = "0.10.9" }
|
tls-openssl = { package = "openssl", version = "0.10.55" }
|
||||||
tls-rustls = { package = "rustls", version = "0.20.0" }
|
tls-rustls = { package = "rustls", version = "0.20" }
|
||||||
tokio = { version = "1.24.2", features = ["net", "rt", "macros"] }
|
tokio = { version = "1.24.2", features = ["net", "rt", "macros"] }
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-http)
|
[](https://crates.io/crates/actix-http)
|
||||||
[](https://docs.rs/actix-http/3.3.1)
|
[](https://docs.rs/actix-http/3.3.1)
|
||||||

|

|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-http/3.3.1)
|
[](https://deps.rs/crate/actix-http/3.3.1)
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
## Documentation & Resources
|
## Documentation & Resources
|
||||||
|
|
||||||
- [API Documentation](https://docs.rs/actix-http)
|
- [API Documentation](https://docs.rs/actix-http)
|
||||||
- Minimum Supported Rust Version (MSRV): 1.59
|
- Minimum Supported Rust Version (MSRV): 1.65
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
|
|
|
@ -23,10 +23,7 @@ async fn main() -> io::Result<()> {
|
||||||
res.insert_header(("x-head", HeaderValue::from_static("dummy value!")));
|
res.insert_header(("x-head", HeaderValue::from_static("dummy value!")));
|
||||||
|
|
||||||
let forty_two = req.conn_data::<u32>().unwrap().to_string();
|
let forty_two = req.conn_data::<u32>().unwrap().to_string();
|
||||||
res.insert_header((
|
res.insert_header(("x-forty-two", HeaderValue::from_str(&forty_two).unwrap()));
|
||||||
"x-forty-two",
|
|
||||||
HeaderValue::from_str(&forty_two).unwrap(),
|
|
||||||
));
|
|
||||||
|
|
||||||
Ok::<_, Infallible>(res.body("Hello world!"))
|
Ok::<_, Infallible>(res.body("Hello world!"))
|
||||||
})
|
})
|
||||||
|
|
|
@ -77,12 +77,8 @@ impl MessageBody for BoxBody {
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||||
match &mut self.0 {
|
match &mut self.0 {
|
||||||
BoxBodyInner::None(body) => {
|
BoxBodyInner::None(body) => Pin::new(body).poll_next(cx).map_err(|err| match err {}),
|
||||||
Pin::new(body).poll_next(cx).map_err(|err| match err {})
|
BoxBodyInner::Bytes(body) => Pin::new(body).poll_next(cx).map_err(|err| match err {}),
|
||||||
}
|
|
||||||
BoxBodyInner::Bytes(body) => {
|
|
||||||
Pin::new(body).poll_next(cx).map_err(|err| match err {})
|
|
||||||
}
|
|
||||||
BoxBodyInner::Stream(body) => Pin::new(body).poll_next(cx),
|
BoxBodyInner::Stream(body) => Pin::new(body).poll_next(cx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,7 +100,6 @@ impl MessageBody for BoxBody {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
use static_assertions::{assert_impl_all, assert_not_impl_any};
|
use static_assertions::{assert_impl_all, assert_not_impl_any};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -14,12 +14,14 @@ mod size;
|
||||||
mod sized_stream;
|
mod sized_stream;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
pub use self::body_stream::BodyStream;
|
|
||||||
pub use self::boxed::BoxBody;
|
|
||||||
pub use self::either::EitherBody;
|
|
||||||
pub use self::message_body::MessageBody;
|
|
||||||
pub(crate) use self::message_body::MessageBodyMapErr;
|
pub(crate) use self::message_body::MessageBodyMapErr;
|
||||||
pub use self::none::None;
|
pub use self::{
|
||||||
pub use self::size::BodySize;
|
body_stream::BodyStream,
|
||||||
pub use self::sized_stream::SizedStream;
|
boxed::BoxBody,
|
||||||
pub use self::utils::{to_bytes, to_bytes_limited, BodyLimitExceeded};
|
either::EitherBody,
|
||||||
|
message_body::MessageBody,
|
||||||
|
none::None,
|
||||||
|
size::BodySize,
|
||||||
|
sized_stream::SizedStream,
|
||||||
|
utils::{to_bytes, to_bytes_limited, BodyLimitExceeded},
|
||||||
|
};
|
||||||
|
|
|
@ -132,15 +132,15 @@ impl ServiceConfig {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
|
||||||
use crate::{date::DATE_VALUE_LENGTH, notify_on_drop};
|
|
||||||
|
|
||||||
use actix_rt::{
|
use actix_rt::{
|
||||||
task::yield_now,
|
task::yield_now,
|
||||||
time::{sleep, sleep_until},
|
time::{sleep, sleep_until},
|
||||||
};
|
};
|
||||||
use memchr::memmem;
|
use memchr::memmem;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::{date::DATE_VALUE_LENGTH, notify_on_drop};
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_date_service_update() {
|
async fn test_date_service_update() {
|
||||||
let settings =
|
let settings =
|
||||||
|
|
|
@ -9,11 +9,9 @@ use std::{
|
||||||
|
|
||||||
use actix_rt::task::{spawn_blocking, JoinHandle};
|
use actix_rt::task::{spawn_blocking, JoinHandle};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_core::{ready, Stream};
|
|
||||||
|
|
||||||
#[cfg(feature = "compress-gzip")]
|
#[cfg(feature = "compress-gzip")]
|
||||||
use flate2::write::{GzDecoder, ZlibDecoder};
|
use flate2::write::{GzDecoder, ZlibDecoder};
|
||||||
|
use futures_core::{ready, Stream};
|
||||||
#[cfg(feature = "compress-zstd")]
|
#[cfg(feature = "compress-zstd")]
|
||||||
use zstd::stream::write::Decoder as ZstdDecoder;
|
use zstd::stream::write::Decoder as ZstdDecoder;
|
||||||
|
|
||||||
|
@ -49,9 +47,9 @@ where
|
||||||
))),
|
))),
|
||||||
|
|
||||||
#[cfg(feature = "compress-gzip")]
|
#[cfg(feature = "compress-gzip")]
|
||||||
ContentEncoding::Deflate => Some(ContentDecoder::Deflate(Box::new(
|
ContentEncoding::Deflate => Some(ContentDecoder::Deflate(Box::new(ZlibDecoder::new(
|
||||||
ZlibDecoder::new(Writer::new()),
|
Writer::new(),
|
||||||
))),
|
)))),
|
||||||
|
|
||||||
#[cfg(feature = "compress-gzip")]
|
#[cfg(feature = "compress-gzip")]
|
||||||
ContentEncoding::Gzip => Some(ContentDecoder::Gzip(Box::new(GzDecoder::new(
|
ContentEncoding::Gzip => Some(ContentDecoder::Gzip(Box::new(GzDecoder::new(
|
||||||
|
|
|
@ -11,12 +11,10 @@ use std::{
|
||||||
use actix_rt::task::{spawn_blocking, JoinHandle};
|
use actix_rt::task::{spawn_blocking, JoinHandle};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use derive_more::Display;
|
use derive_more::Display;
|
||||||
use futures_core::ready;
|
|
||||||
use pin_project_lite::pin_project;
|
|
||||||
|
|
||||||
#[cfg(feature = "compress-gzip")]
|
#[cfg(feature = "compress-gzip")]
|
||||||
use flate2::write::{GzEncoder, ZlibEncoder};
|
use flate2::write::{GzEncoder, ZlibEncoder};
|
||||||
|
use futures_core::ready;
|
||||||
|
use pin_project_lite::pin_project;
|
||||||
use tracing::trace;
|
use tracing::trace;
|
||||||
#[cfg(feature = "compress-zstd")]
|
#[cfg(feature = "compress-zstd")]
|
||||||
use zstd::stream::write::Encoder as ZstdEncoder;
|
use zstd::stream::write::Encoder as ZstdEncoder;
|
||||||
|
|
|
@ -7,8 +7,7 @@ use bytes::{Bytes, BytesMut};
|
||||||
mod decoder;
|
mod decoder;
|
||||||
mod encoder;
|
mod encoder;
|
||||||
|
|
||||||
pub use self::decoder::Decoder;
|
pub use self::{decoder::Decoder, encoder::Encoder};
|
||||||
pub use self::encoder::Encoder;
|
|
||||||
|
|
||||||
/// Special-purpose writer for streaming (de-)compression.
|
/// Special-purpose writer for streaming (de-)compression.
|
||||||
///
|
///
|
||||||
|
|
|
@ -3,12 +3,11 @@
|
||||||
use std::{error::Error as StdError, fmt, io, str::Utf8Error, string::FromUtf8Error};
|
use std::{error::Error as StdError, fmt, io, str::Utf8Error, string::FromUtf8Error};
|
||||||
|
|
||||||
use derive_more::{Display, Error, From};
|
use derive_more::{Display, Error, From};
|
||||||
|
pub use http::Error as HttpError;
|
||||||
use http::{uri::InvalidUri, StatusCode};
|
use http::{uri::InvalidUri, StatusCode};
|
||||||
|
|
||||||
use crate::{body::BoxBody, Response};
|
use crate::{body::BoxBody, Response};
|
||||||
|
|
||||||
pub use http::Error as HttpError;
|
|
||||||
|
|
||||||
pub struct Error {
|
pub struct Error {
|
||||||
inner: Box<ErrorInner>,
|
inner: Box<ErrorInner>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,7 @@ use super::{
|
||||||
decoder::{self, PayloadDecoder, PayloadItem, PayloadType},
|
decoder::{self, PayloadDecoder, PayloadItem, PayloadType},
|
||||||
encoder, Message, MessageType,
|
encoder, Message, MessageType,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{body::BodySize, error::ParseError, ConnectionType, Request, Response, ServiceConfig};
|
||||||
body::BodySize, error::ParseError, ConnectionType, Request, Response, ServiceConfig,
|
|
||||||
};
|
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{convert::TryFrom, io, marker::PhantomData, mem::MaybeUninit, task::Poll};
|
use std::{io, marker::PhantomData, mem::MaybeUninit, task::Poll};
|
||||||
|
|
||||||
use actix_codec::Decoder;
|
use actix_codec::Decoder;
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
|
@ -94,9 +94,7 @@ pub(crate) trait MessageType: Sized {
|
||||||
// SAFETY: httparse already checks header value is only visible ASCII bytes
|
// SAFETY: httparse already checks header value is only visible ASCII bytes
|
||||||
// from_maybe_shared_unchecked contains debug assertions so they are omitted here
|
// from_maybe_shared_unchecked contains debug assertions so they are omitted here
|
||||||
let value = unsafe {
|
let value = unsafe {
|
||||||
HeaderValue::from_maybe_shared_unchecked(
|
HeaderValue::from_maybe_shared_unchecked(slice.slice(idx.value.0..idx.value.1))
|
||||||
slice.slice(idx.value.0..idx.value.1),
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
match name {
|
match name {
|
||||||
|
@ -275,8 +273,7 @@ impl MessageType for Request {
|
||||||
let mut msg = Request::new();
|
let mut msg = Request::new();
|
||||||
|
|
||||||
// convert headers
|
// convert headers
|
||||||
let mut length =
|
let mut length = msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len], ver)?;
|
||||||
msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len], ver)?;
|
|
||||||
|
|
||||||
// disallow HTTP/1.0 POST requests that do not contain a Content-Length headers
|
// disallow HTTP/1.0 POST requests that do not contain a Content-Length headers
|
||||||
// see https://datatracker.ietf.org/doc/html/rfc1945#section-7.2.2
|
// see https://datatracker.ietf.org/doc/html/rfc1945#section-7.2.2
|
||||||
|
@ -356,8 +353,8 @@ impl MessageType for ResponseHead {
|
||||||
Version::HTTP_10
|
Version::HTTP_10
|
||||||
};
|
};
|
||||||
|
|
||||||
let status = StatusCode::from_u16(res.code.unwrap())
|
let status =
|
||||||
.map_err(|_| ParseError::Status)?;
|
StatusCode::from_u16(res.code.unwrap()).map_err(|_| ParseError::Status)?;
|
||||||
HeaderIndex::record(src, res.headers, &mut headers);
|
HeaderIndex::record(src, res.headers, &mut headers);
|
||||||
|
|
||||||
(len, version, status, res.headers.len())
|
(len, version, status, res.headers.len())
|
||||||
|
@ -378,8 +375,7 @@ impl MessageType for ResponseHead {
|
||||||
msg.version = ver;
|
msg.version = ver;
|
||||||
|
|
||||||
// convert headers
|
// convert headers
|
||||||
let mut length =
|
let mut length = msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len], ver)?;
|
||||||
msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len], ver)?;
|
|
||||||
|
|
||||||
// Remove CL value if 0 now that all headers and HTTP/1.0 special cases are processed.
|
// Remove CL value if 0 now that all headers and HTTP/1.0 special cases are processed.
|
||||||
// Protects against some request smuggling attacks.
|
// Protects against some request smuggling attacks.
|
||||||
|
|
|
@ -19,14 +19,6 @@ use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
use tokio_util::codec::{Decoder as _, Encoder as _};
|
use tokio_util::codec::{Decoder as _, Encoder as _};
|
||||||
use tracing::{error, trace};
|
use tracing::{error, trace};
|
||||||
|
|
||||||
use crate::{
|
|
||||||
body::{BodySize, BoxBody, MessageBody},
|
|
||||||
config::ServiceConfig,
|
|
||||||
error::{DispatchError, ParseError, PayloadError},
|
|
||||||
service::HttpFlow,
|
|
||||||
Error, Extensions, OnConnectData, Request, Response, StatusCode,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
codec::Codec,
|
codec::Codec,
|
||||||
decoder::MAX_BUFFER_SIZE,
|
decoder::MAX_BUFFER_SIZE,
|
||||||
|
@ -34,6 +26,13 @@ use super::{
|
||||||
timer::TimerState,
|
timer::TimerState,
|
||||||
Message, MessageType,
|
Message, MessageType,
|
||||||
};
|
};
|
||||||
|
use crate::{
|
||||||
|
body::{BodySize, BoxBody, MessageBody},
|
||||||
|
config::ServiceConfig,
|
||||||
|
error::{DispatchError, ParseError, PayloadError},
|
||||||
|
service::HttpFlow,
|
||||||
|
Error, Extensions, OnConnectData, Request, Response, StatusCode,
|
||||||
|
};
|
||||||
|
|
||||||
const LW_BUFFER_SIZE: usize = 1024;
|
const LW_BUFFER_SIZE: usize = 1024;
|
||||||
const HW_BUFFER_SIZE: usize = 1024 * 8;
|
const HW_BUFFER_SIZE: usize = 1024 * 8;
|
||||||
|
@ -213,9 +212,7 @@ where
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::None => write!(f, "State::None"),
|
Self::None => write!(f, "State::None"),
|
||||||
Self::ExpectCall { .. } => {
|
Self::ExpectCall { .. } => f.debug_struct("State::ExpectCall").finish_non_exhaustive(),
|
||||||
f.debug_struct("State::ExpectCall").finish_non_exhaustive()
|
|
||||||
}
|
|
||||||
Self::ServiceCall { .. } => {
|
Self::ServiceCall { .. } => {
|
||||||
f.debug_struct("State::ServiceCall").finish_non_exhaustive()
|
f.debug_struct("State::ServiceCall").finish_non_exhaustive()
|
||||||
}
|
}
|
||||||
|
@ -276,9 +273,7 @@ where
|
||||||
|
|
||||||
head_timer: TimerState::new(config.client_request_deadline().is_some()),
|
head_timer: TimerState::new(config.client_request_deadline().is_some()),
|
||||||
ka_timer: TimerState::new(config.keep_alive().enabled()),
|
ka_timer: TimerState::new(config.keep_alive().enabled()),
|
||||||
shutdown_timer: TimerState::new(
|
shutdown_timer: TimerState::new(config.client_disconnect_deadline().is_some()),
|
||||||
config.client_disconnect_deadline().is_some(),
|
|
||||||
),
|
|
||||||
|
|
||||||
io: Some(io),
|
io: Some(io),
|
||||||
read_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
|
read_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
|
||||||
|
@ -456,9 +451,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
// return with upgrade request and poll it exclusively
|
// return with upgrade request and poll it exclusively
|
||||||
Some(DispatcherMessage::Upgrade(req)) => {
|
Some(DispatcherMessage::Upgrade(req)) => return Ok(PollResponse::Upgrade(req)),
|
||||||
return Ok(PollResponse::Upgrade(req))
|
|
||||||
}
|
|
||||||
|
|
||||||
// all messages are dealt with
|
// all messages are dealt with
|
||||||
None => {
|
None => {
|
||||||
|
@ -675,9 +668,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
unreachable!(
|
unreachable!("State must be set to ServiceCall or ExceptCall in handle_request")
|
||||||
"State must be set to ServiceCall or ExceptCall in handle_request"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -686,10 +677,7 @@ where
|
||||||
/// Process one incoming request.
|
/// Process one incoming request.
|
||||||
///
|
///
|
||||||
/// Returns true if any meaningful work was done.
|
/// Returns true if any meaningful work was done.
|
||||||
fn poll_request(
|
fn poll_request(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Result<bool, DispatchError> {
|
||||||
mut self: Pin<&mut Self>,
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
) -> Result<bool, DispatchError> {
|
|
||||||
let pipeline_queue_full = self.messages.len() >= MAX_PIPELINED_MESSAGES;
|
let pipeline_queue_full = self.messages.len() >= MAX_PIPELINED_MESSAGES;
|
||||||
let can_not_read = !self.can_read(cx);
|
let can_not_read = !self.can_read(cx);
|
||||||
|
|
||||||
|
@ -859,10 +847,7 @@ where
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll_ka_timer(
|
fn poll_ka_timer(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Result<(), DispatchError> {
|
||||||
mut self: Pin<&mut Self>,
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
) -> Result<(), DispatchError> {
|
|
||||||
let this = self.as_mut().project();
|
let this = self.as_mut().project();
|
||||||
if let TimerState::Active { timer } = this.ka_timer {
|
if let TimerState::Active { timer } = this.ka_timer {
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
|
@ -927,10 +912,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Poll head, keep-alive, and disconnect timer.
|
/// Poll head, keep-alive, and disconnect timer.
|
||||||
fn poll_timers(
|
fn poll_timers(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Result<(), DispatchError> {
|
||||||
mut self: Pin<&mut Self>,
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
) -> Result<(), DispatchError> {
|
|
||||||
self.as_mut().poll_head_timer(cx)?;
|
self.as_mut().poll_head_timer(cx)?;
|
||||||
self.as_mut().poll_ka_timer(cx)?;
|
self.as_mut().poll_ka_timer(cx)?;
|
||||||
self.as_mut().poll_shutdown_timer(cx)?;
|
self.as_mut().poll_shutdown_timer(cx)?;
|
||||||
|
@ -944,10 +926,7 @@ where
|
||||||
/// - `std::io::ErrorKind::ConnectionReset` after partial read;
|
/// - `std::io::ErrorKind::ConnectionReset` after partial read;
|
||||||
/// - all data read done.
|
/// - all data read done.
|
||||||
#[inline(always)] // TODO: bench this inline
|
#[inline(always)] // TODO: bench this inline
|
||||||
fn read_available(
|
fn read_available(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Result<bool, DispatchError> {
|
||||||
self: Pin<&mut Self>,
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
) -> Result<bool, DispatchError> {
|
|
||||||
let this = self.project();
|
let this = self.project();
|
||||||
|
|
||||||
if this.flags.contains(Flags::READ_DISCONNECT) {
|
if this.flags.contains(Flags::READ_DISCONNECT) {
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
use std::{future::Future, str, task::Poll, time::Duration};
|
use std::{future::Future, str, task::Poll, time::Duration};
|
||||||
|
|
||||||
use actix_rt::{pin, time::sleep};
|
|
||||||
use actix_service::fn_service;
|
|
||||||
use actix_utils::future::{ready, Ready};
|
|
||||||
use bytes::Bytes;
|
|
||||||
use futures_util::future::lazy;
|
|
||||||
|
|
||||||
use actix_codec::Framed;
|
use actix_codec::Framed;
|
||||||
use actix_service::Service;
|
use actix_rt::{pin, time::sleep};
|
||||||
use bytes::{Buf, BytesMut};
|
use actix_service::{fn_service, Service};
|
||||||
|
use actix_utils::future::{ready, Ready};
|
||||||
|
use bytes::{Buf, Bytes, BytesMut};
|
||||||
|
use futures_util::future::lazy;
|
||||||
|
|
||||||
use super::dispatcher::{Dispatcher, DispatcherState, DispatcherStateProj, Flags};
|
use super::dispatcher::{Dispatcher, DispatcherState, DispatcherStateProj, Flags};
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -43,8 +40,8 @@ fn status_service(
|
||||||
fn_service(move |_req: Request| ready(Ok::<_, Error>(Response::new(status))))
|
fn_service(move |_req: Request| ready(Ok::<_, Error>(Response::new(status))))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn echo_path_service(
|
fn echo_path_service() -> impl Service<Request, Response = Response<impl MessageBody>, Error = Error>
|
||||||
) -> impl Service<Request, Response = Response<impl MessageBody>, Error = Error> {
|
{
|
||||||
fn_service(|req: Request| {
|
fn_service(|req: Request| {
|
||||||
let path = req.path().as_bytes();
|
let path = req.path().as_bytes();
|
||||||
ready(Ok::<_, Error>(
|
ready(Ok::<_, Error>(
|
||||||
|
@ -53,8 +50,8 @@ fn echo_path_service(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn drop_payload_service(
|
fn drop_payload_service() -> impl Service<Request, Response = Response<&'static str>, Error = Error>
|
||||||
) -> impl Service<Request, Response = Response<&'static str>, Error = Error> {
|
{
|
||||||
fn_service(|mut req: Request| async move {
|
fn_service(|mut req: Request| async move {
|
||||||
let _ = req.take_payload();
|
let _ = req.take_payload();
|
||||||
Ok::<_, Error>(Response::with_body(StatusCode::OK, "payload dropped"))
|
Ok::<_, Error>(Response::with_body(StatusCode::OK, "payload dropped"))
|
||||||
|
|
|
@ -17,14 +17,16 @@ mod timer;
|
||||||
mod upgrade;
|
mod upgrade;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
pub use self::client::{ClientCodec, ClientPayloadCodec};
|
pub use self::{
|
||||||
pub use self::codec::Codec;
|
client::{ClientCodec, ClientPayloadCodec},
|
||||||
pub use self::dispatcher::Dispatcher;
|
codec::Codec,
|
||||||
pub use self::expect::ExpectHandler;
|
dispatcher::Dispatcher,
|
||||||
pub use self::payload::Payload;
|
expect::ExpectHandler,
|
||||||
pub use self::service::{H1Service, H1ServiceHandler};
|
payload::Payload,
|
||||||
pub use self::upgrade::UpgradeHandler;
|
service::{H1Service, H1ServiceHandler},
|
||||||
pub use self::utils::SendResponse;
|
upgrade::UpgradeHandler,
|
||||||
|
utils::SendResponse,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
/// Codec message
|
/// Codec message
|
||||||
|
|
|
@ -15,6 +15,7 @@ use actix_utils::future::ready;
|
||||||
use futures_core::future::LocalBoxFuture;
|
use futures_core::future::LocalBoxFuture;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
|
use super::{codec::Codec, dispatcher::Dispatcher, ExpectHandler, UpgradeHandler};
|
||||||
use crate::{
|
use crate::{
|
||||||
body::{BoxBody, MessageBody},
|
body::{BoxBody, MessageBody},
|
||||||
config::ServiceConfig,
|
config::ServiceConfig,
|
||||||
|
@ -23,8 +24,6 @@ use crate::{
|
||||||
ConnectCallback, OnConnectData, Request, Response,
|
ConnectCallback, OnConnectData, Request, Response,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{codec::Codec, dispatcher::Dispatcher, ExpectHandler, UpgradeHandler};
|
|
||||||
|
|
||||||
/// `ServiceFactory` implementation for HTTP1 transport
|
/// `ServiceFactory` implementation for HTTP1 transport
|
||||||
pub struct H1Service<T, S, B, X = ExpectHandler, U = UpgradeHandler> {
|
pub struct H1Service<T, S, B, X = ExpectHandler, U = UpgradeHandler> {
|
||||||
srv: S,
|
srv: S,
|
||||||
|
@ -82,13 +81,8 @@ where
|
||||||
/// Create simple tcp stream service
|
/// Create simple tcp stream service
|
||||||
pub fn tcp(
|
pub fn tcp(
|
||||||
self,
|
self,
|
||||||
) -> impl ServiceFactory<
|
) -> impl ServiceFactory<TcpStream, Config = (), Response = (), Error = DispatchError, InitError = ()>
|
||||||
TcpStream,
|
{
|
||||||
Config = (),
|
|
||||||
Response = (),
|
|
||||||
Error = DispatchError,
|
|
||||||
InitError = (),
|
|
||||||
> {
|
|
||||||
fn_service(|io: TcpStream| {
|
fn_service(|io: TcpStream| {
|
||||||
let peer_addr = io.peer_addr().ok();
|
let peer_addr = io.peer_addr().ok();
|
||||||
ready(Ok((io, peer_addr)))
|
ready(Ok((io, peer_addr)))
|
||||||
|
@ -99,8 +93,6 @@ where
|
||||||
|
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
mod openssl {
|
mod openssl {
|
||||||
use super::*;
|
|
||||||
|
|
||||||
use actix_tls::accept::{
|
use actix_tls::accept::{
|
||||||
openssl::{
|
openssl::{
|
||||||
reexports::{Error as SslError, SslAcceptor},
|
reexports::{Error as SslError, SslAcceptor},
|
||||||
|
@ -109,6 +101,8 @@ mod openssl {
|
||||||
TlsError,
|
TlsError,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
impl<S, B, X, U> H1Service<TlsStream<TcpStream>, S, B, X, U>
|
impl<S, B, X, U> H1Service<TlsStream<TcpStream>, S, B, X, U>
|
||||||
where
|
where
|
||||||
S: ServiceFactory<Request, Config = ()>,
|
S: ServiceFactory<Request, Config = ()>,
|
||||||
|
@ -160,7 +154,6 @@ mod openssl {
|
||||||
|
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
mod rustls {
|
mod rustls {
|
||||||
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use actix_service::ServiceFactoryExt as _;
|
use actix_service::ServiceFactoryExt as _;
|
||||||
|
|
|
@ -23,8 +23,7 @@ use crate::{
|
||||||
mod dispatcher;
|
mod dispatcher;
|
||||||
mod service;
|
mod service;
|
||||||
|
|
||||||
pub use self::dispatcher::Dispatcher;
|
pub use self::{dispatcher::Dispatcher, service::H2Service};
|
||||||
pub use self::service::H2Service;
|
|
||||||
|
|
||||||
/// HTTP/2 peer stream.
|
/// HTTP/2 peer stream.
|
||||||
pub struct Payload {
|
pub struct Payload {
|
||||||
|
@ -58,10 +57,7 @@ impl Stream for Payload {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn handshake_with_timeout<T>(
|
pub(crate) fn handshake_with_timeout<T>(io: T, config: &ServiceConfig) -> HandshakeWithTimeout<T>
|
||||||
io: T,
|
|
||||||
config: &ServiceConfig,
|
|
||||||
) -> HandshakeWithTimeout<T>
|
|
||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
{
|
{
|
||||||
|
|
|
@ -16,6 +16,7 @@ use actix_utils::future::ready;
|
||||||
use futures_core::{future::LocalBoxFuture, ready};
|
use futures_core::{future::LocalBoxFuture, ready};
|
||||||
use tracing::{error, trace};
|
use tracing::{error, trace};
|
||||||
|
|
||||||
|
use super::{dispatcher::Dispatcher, handshake_with_timeout, HandshakeWithTimeout};
|
||||||
use crate::{
|
use crate::{
|
||||||
body::{BoxBody, MessageBody},
|
body::{BoxBody, MessageBody},
|
||||||
config::ServiceConfig,
|
config::ServiceConfig,
|
||||||
|
@ -24,8 +25,6 @@ use crate::{
|
||||||
ConnectCallback, OnConnectData, Request, Response,
|
ConnectCallback, OnConnectData, Request, Response,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{dispatcher::Dispatcher, handshake_with_timeout, HandshakeWithTimeout};
|
|
||||||
|
|
||||||
/// `ServiceFactory` implementation for HTTP/2 transport
|
/// `ServiceFactory` implementation for HTTP/2 transport
|
||||||
pub struct H2Service<T, S, B> {
|
pub struct H2Service<T, S, B> {
|
||||||
srv: S,
|
srv: S,
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
//! [`TryIntoHeaderPair`] trait and implementations.
|
//! [`TryIntoHeaderPair`] trait and implementations.
|
||||||
|
|
||||||
use std::convert::TryFrom as _;
|
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
Header, HeaderName, HeaderValue, InvalidHeaderName, InvalidHeaderValue, TryIntoHeaderValue,
|
Header, HeaderName, HeaderValue, InvalidHeaderName, InvalidHeaderValue, TryIntoHeaderValue,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
//! [`TryIntoHeaderValue`] trait and implementations.
|
//! [`TryIntoHeaderValue`] trait and implementations.
|
||||||
|
|
||||||
use std::convert::TryFrom as _;
|
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use http::{header::InvalidHeaderValue, Error as HttpError, HeaderValue};
|
use http::{header::InvalidHeaderValue, Error as HttpError, HeaderValue};
|
||||||
use mime::Mime;
|
use mime::Mime;
|
||||||
|
|
|
@ -1120,9 +1120,7 @@ mod tests {
|
||||||
assert!(vals.next().is_none());
|
assert!(vals.next().is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn owned_pair<'a>(
|
fn owned_pair<'a>((name, val): (&'a HeaderName, &'a HeaderValue)) -> (HeaderName, HeaderValue) {
|
||||||
(name, val): (&'a HeaderName, &'a HeaderValue),
|
|
||||||
) -> (HeaderName, HeaderValue) {
|
|
||||||
(name.clone(), val.clone())
|
(name.clone(), val.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,33 +3,30 @@
|
||||||
// declaring new header consts will yield this error
|
// declaring new header consts will yield this error
|
||||||
#![allow(clippy::declare_interior_mutable_const)]
|
#![allow(clippy::declare_interior_mutable_const)]
|
||||||
|
|
||||||
use percent_encoding::{AsciiSet, CONTROLS};
|
|
||||||
|
|
||||||
// re-export from http except header map related items
|
// re-export from http except header map related items
|
||||||
pub use ::http::header::{
|
pub use ::http::header::{
|
||||||
HeaderName, HeaderValue, InvalidHeaderName, InvalidHeaderValue, ToStrError,
|
HeaderName, HeaderValue, InvalidHeaderName, InvalidHeaderValue, ToStrError,
|
||||||
};
|
};
|
||||||
|
|
||||||
// re-export const header names, list is explicit so that any updates to `common` module do not
|
// re-export const header names, list is explicit so that any updates to `common` module do not
|
||||||
// conflict with this set
|
// conflict with this set
|
||||||
pub use ::http::header::{
|
pub use ::http::header::{
|
||||||
ACCEPT, ACCEPT_CHARSET, ACCEPT_ENCODING, ACCEPT_LANGUAGE, ACCEPT_RANGES,
|
ACCEPT, ACCEPT_CHARSET, ACCEPT_ENCODING, ACCEPT_LANGUAGE, ACCEPT_RANGES,
|
||||||
ACCESS_CONTROL_ALLOW_CREDENTIALS, ACCESS_CONTROL_ALLOW_HEADERS,
|
ACCESS_CONTROL_ALLOW_CREDENTIALS, ACCESS_CONTROL_ALLOW_HEADERS, ACCESS_CONTROL_ALLOW_METHODS,
|
||||||
ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_EXPOSE_HEADERS,
|
ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_EXPOSE_HEADERS, ACCESS_CONTROL_MAX_AGE,
|
||||||
ACCESS_CONTROL_MAX_AGE, ACCESS_CONTROL_REQUEST_HEADERS, ACCESS_CONTROL_REQUEST_METHOD, AGE,
|
ACCESS_CONTROL_REQUEST_HEADERS, ACCESS_CONTROL_REQUEST_METHOD, AGE, ALLOW, ALT_SVC,
|
||||||
ALLOW, ALT_SVC, AUTHORIZATION, CACHE_CONTROL, CONNECTION, CONTENT_DISPOSITION,
|
AUTHORIZATION, CACHE_CONTROL, CONNECTION, CONTENT_DISPOSITION, CONTENT_ENCODING,
|
||||||
CONTENT_ENCODING, CONTENT_LANGUAGE, CONTENT_LENGTH, CONTENT_LOCATION, CONTENT_RANGE,
|
CONTENT_LANGUAGE, CONTENT_LENGTH, CONTENT_LOCATION, CONTENT_RANGE, CONTENT_SECURITY_POLICY,
|
||||||
CONTENT_SECURITY_POLICY, CONTENT_SECURITY_POLICY_REPORT_ONLY, CONTENT_TYPE, COOKIE, DATE,
|
CONTENT_SECURITY_POLICY_REPORT_ONLY, CONTENT_TYPE, COOKIE, DATE, DNT, ETAG, EXPECT, EXPIRES,
|
||||||
DNT, ETAG, EXPECT, EXPIRES, FORWARDED, FROM, HOST, IF_MATCH, IF_MODIFIED_SINCE,
|
FORWARDED, FROM, HOST, IF_MATCH, IF_MODIFIED_SINCE, IF_NONE_MATCH, IF_RANGE,
|
||||||
IF_NONE_MATCH, IF_RANGE, IF_UNMODIFIED_SINCE, LAST_MODIFIED, LINK, LOCATION, MAX_FORWARDS,
|
IF_UNMODIFIED_SINCE, LAST_MODIFIED, LINK, LOCATION, MAX_FORWARDS, ORIGIN, PRAGMA,
|
||||||
ORIGIN, PRAGMA, PROXY_AUTHENTICATE, PROXY_AUTHORIZATION, PUBLIC_KEY_PINS,
|
PROXY_AUTHENTICATE, PROXY_AUTHORIZATION, PUBLIC_KEY_PINS, PUBLIC_KEY_PINS_REPORT_ONLY, RANGE,
|
||||||
PUBLIC_KEY_PINS_REPORT_ONLY, RANGE, REFERER, REFERRER_POLICY, REFRESH, RETRY_AFTER,
|
REFERER, REFERRER_POLICY, REFRESH, RETRY_AFTER, SEC_WEBSOCKET_ACCEPT, SEC_WEBSOCKET_EXTENSIONS,
|
||||||
SEC_WEBSOCKET_ACCEPT, SEC_WEBSOCKET_EXTENSIONS, SEC_WEBSOCKET_KEY, SEC_WEBSOCKET_PROTOCOL,
|
SEC_WEBSOCKET_KEY, SEC_WEBSOCKET_PROTOCOL, SEC_WEBSOCKET_VERSION, SERVER, SET_COOKIE,
|
||||||
SEC_WEBSOCKET_VERSION, SERVER, SET_COOKIE, STRICT_TRANSPORT_SECURITY, TE, TRAILER,
|
STRICT_TRANSPORT_SECURITY, TE, TRAILER, TRANSFER_ENCODING, UPGRADE, UPGRADE_INSECURE_REQUESTS,
|
||||||
TRANSFER_ENCODING, UPGRADE, UPGRADE_INSECURE_REQUESTS, USER_AGENT, VARY, VIA, WARNING,
|
USER_AGENT, VARY, VIA, WARNING, WWW_AUTHENTICATE, X_CONTENT_TYPE_OPTIONS,
|
||||||
WWW_AUTHENTICATE, X_CONTENT_TYPE_OPTIONS, X_DNS_PREFETCH_CONTROL, X_FRAME_OPTIONS,
|
X_DNS_PREFETCH_CONTROL, X_FRAME_OPTIONS, X_XSS_PROTECTION,
|
||||||
X_XSS_PROTECTION,
|
|
||||||
};
|
};
|
||||||
|
use percent_encoding::{AsciiSet, CONTROLS};
|
||||||
|
|
||||||
use crate::{error::ParseError, HttpMessage};
|
use crate::{error::ParseError, HttpMessage};
|
||||||
|
|
||||||
|
@ -43,23 +40,22 @@ mod utils;
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
as_name::AsHeaderName,
|
as_name::AsHeaderName,
|
||||||
|
// re-export list is explicit so that any updates to `http` do not conflict with this set
|
||||||
|
common::{
|
||||||
|
CACHE_STATUS, CDN_CACHE_CONTROL, CROSS_ORIGIN_EMBEDDER_POLICY, CROSS_ORIGIN_OPENER_POLICY,
|
||||||
|
CROSS_ORIGIN_RESOURCE_POLICY, PERMISSIONS_POLICY, X_FORWARDED_FOR, X_FORWARDED_HOST,
|
||||||
|
X_FORWARDED_PROTO,
|
||||||
|
},
|
||||||
into_pair::TryIntoHeaderPair,
|
into_pair::TryIntoHeaderPair,
|
||||||
into_value::TryIntoHeaderValue,
|
into_value::TryIntoHeaderValue,
|
||||||
map::HeaderMap,
|
map::HeaderMap,
|
||||||
shared::{
|
shared::{
|
||||||
parse_extended_value, q, Charset, ContentEncoding, ExtendedValue, HttpDate,
|
parse_extended_value, q, Charset, ContentEncoding, ExtendedValue, HttpDate, LanguageTag,
|
||||||
LanguageTag, Quality, QualityItem,
|
Quality, QualityItem,
|
||||||
},
|
},
|
||||||
utils::{fmt_comma_delimited, from_comma_delimited, from_one_raw_str, http_percent_encode},
|
utils::{fmt_comma_delimited, from_comma_delimited, from_one_raw_str, http_percent_encode},
|
||||||
};
|
};
|
||||||
|
|
||||||
// re-export list is explicit so that any updates to `http` do not conflict with this set
|
|
||||||
pub use self::common::{
|
|
||||||
CACHE_STATUS, CDN_CACHE_CONTROL, CROSS_ORIGIN_EMBEDDER_POLICY, CROSS_ORIGIN_OPENER_POLICY,
|
|
||||||
CROSS_ORIGIN_RESOURCE_POLICY, PERMISSIONS_POLICY, X_FORWARDED_FOR, X_FORWARDED_HOST,
|
|
||||||
X_FORWARDED_PROTO,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// An interface for types that already represent a valid header.
|
/// An interface for types that already represent a valid header.
|
||||||
pub trait Header: TryIntoHeaderValue {
|
pub trait Header: TryIntoHeaderValue {
|
||||||
/// Returns the name of the header field.
|
/// Returns the name of the header field.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{convert::TryFrom, str::FromStr};
|
use std::str::FromStr;
|
||||||
|
|
||||||
use derive_more::{Display, Error};
|
use derive_more::{Display, Error};
|
||||||
use http::header::InvalidHeaderValue;
|
use http::header::InvalidHeaderValue;
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
//! Originally taken from `hyper::header::shared`.
|
//! Originally taken from `hyper::header::shared`.
|
||||||
|
|
||||||
|
pub use language_tags::LanguageTag;
|
||||||
|
|
||||||
mod charset;
|
mod charset;
|
||||||
mod content_encoding;
|
mod content_encoding;
|
||||||
mod extended;
|
mod extended;
|
||||||
|
@ -7,10 +9,11 @@ mod http_date;
|
||||||
mod quality;
|
mod quality;
|
||||||
mod quality_item;
|
mod quality_item;
|
||||||
|
|
||||||
pub use self::charset::Charset;
|
pub use self::{
|
||||||
pub use self::content_encoding::ContentEncoding;
|
charset::Charset,
|
||||||
pub use self::extended::{parse_extended_value, ExtendedValue};
|
content_encoding::ContentEncoding,
|
||||||
pub use self::http_date::HttpDate;
|
extended::{parse_extended_value, ExtendedValue},
|
||||||
pub use self::quality::{q, Quality};
|
http_date::HttpDate,
|
||||||
pub use self::quality_item::QualityItem;
|
quality::{q, Quality},
|
||||||
pub use language_tags::LanguageTag;
|
quality_item::QualityItem,
|
||||||
|
};
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
use std::{
|
use std::fmt;
|
||||||
convert::{TryFrom, TryInto},
|
|
||||||
fmt,
|
|
||||||
};
|
|
||||||
|
|
||||||
use derive_more::{Display, Error};
|
use derive_more::{Display, Error};
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
use std::{cmp, convert::TryFrom as _, fmt, str};
|
use std::{cmp, fmt, str};
|
||||||
|
|
||||||
use crate::error::ParseError;
|
|
||||||
|
|
||||||
use super::Quality;
|
use super::Quality;
|
||||||
|
use crate::error::ParseError;
|
||||||
|
|
||||||
/// Represents an item with a quality value as defined
|
/// Represents an item with a quality value as defined
|
||||||
/// in [RFC 7231 §5.3.1](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.1).
|
/// in [RFC 7231 §5.3.1](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.1).
|
||||||
|
|
|
@ -61,9 +61,7 @@ pub trait HttpMessage: Sized {
|
||||||
fn encoding(&self) -> Result<&'static Encoding, ContentTypeError> {
|
fn encoding(&self) -> Result<&'static Encoding, ContentTypeError> {
|
||||||
if let Some(mime_type) = self.mime_type()? {
|
if let Some(mime_type) = self.mime_type()? {
|
||||||
if let Some(charset) = mime_type.get_param("charset") {
|
if let Some(charset) = mime_type.get_param("charset") {
|
||||||
if let Some(enc) =
|
if let Some(enc) = Encoding::for_label_no_replacement(charset.as_str().as_bytes()) {
|
||||||
Encoding::for_label_no_replacement(charset.as_str().as_bytes())
|
|
||||||
{
|
|
||||||
Ok(enc)
|
Ok(enc)
|
||||||
} else {
|
} else {
|
||||||
Err(ContentTypeError::UnknownEncoding)
|
Err(ContentTypeError::UnknownEncoding)
|
||||||
|
@ -146,7 +144,7 @@ mod tests {
|
||||||
.finish();
|
.finish();
|
||||||
assert_eq!(req.content_type(), "text/plain");
|
assert_eq!(req.content_type(), "text/plain");
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.insert_header(("content-type", "application/json; charset=utf=8"))
|
.insert_header(("content-type", "application/json; charset=utf-8"))
|
||||||
.finish();
|
.finish();
|
||||||
assert_eq!(req.content_type(), "application/json");
|
assert_eq!(req.content_type(), "application/json");
|
||||||
let req = TestRequest::default().finish();
|
let req = TestRequest::default().finish();
|
||||||
|
|
|
@ -28,8 +28,7 @@
|
||||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
|
|
||||||
pub use ::http::{uri, uri::Uri};
|
pub use ::http::{uri, uri::Uri, Method, StatusCode, Version};
|
||||||
pub use ::http::{Method, StatusCode, Version};
|
|
||||||
|
|
||||||
pub mod body;
|
pub mod body;
|
||||||
mod builder;
|
mod builder;
|
||||||
|
@ -57,22 +56,24 @@ pub mod test;
|
||||||
#[cfg(feature = "ws")]
|
#[cfg(feature = "ws")]
|
||||||
pub mod ws;
|
pub mod ws;
|
||||||
|
|
||||||
pub use self::builder::HttpServiceBuilder;
|
|
||||||
pub use self::config::ServiceConfig;
|
|
||||||
pub use self::error::Error;
|
|
||||||
pub use self::extensions::Extensions;
|
|
||||||
pub use self::header::ContentEncoding;
|
|
||||||
pub use self::http_message::HttpMessage;
|
|
||||||
pub use self::keep_alive::KeepAlive;
|
|
||||||
pub use self::message::ConnectionType;
|
|
||||||
pub use self::message::Message;
|
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
pub use self::payload::{BoxedPayloadStream, Payload, PayloadStream};
|
pub use self::payload::PayloadStream;
|
||||||
pub use self::requests::{Request, RequestHead, RequestHeadType};
|
|
||||||
pub use self::responses::{Response, ResponseBuilder, ResponseHead};
|
|
||||||
pub use self::service::HttpService;
|
|
||||||
#[cfg(any(feature = "openssl", feature = "rustls"))]
|
#[cfg(any(feature = "openssl", feature = "rustls"))]
|
||||||
pub use self::service::TlsAcceptorConfig;
|
pub use self::service::TlsAcceptorConfig;
|
||||||
|
pub use self::{
|
||||||
|
builder::HttpServiceBuilder,
|
||||||
|
config::ServiceConfig,
|
||||||
|
error::Error,
|
||||||
|
extensions::Extensions,
|
||||||
|
header::ContentEncoding,
|
||||||
|
http_message::HttpMessage,
|
||||||
|
keep_alive::KeepAlive,
|
||||||
|
message::{ConnectionType, Message},
|
||||||
|
payload::{BoxedPayloadStream, Payload},
|
||||||
|
requests::{Request, RequestHead, RequestHeadType},
|
||||||
|
responses::{Response, ResponseBuilder, ResponseHead},
|
||||||
|
service::HttpService,
|
||||||
|
};
|
||||||
|
|
||||||
/// A major HTTP protocol version.
|
/// A major HTTP protocol version.
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
|
|
@ -3,5 +3,7 @@
|
||||||
mod head;
|
mod head;
|
||||||
mod request;
|
mod request;
|
||||||
|
|
||||||
pub use self::head::{RequestHead, RequestHeadType};
|
pub use self::{
|
||||||
pub use self::request::Request;
|
head::{RequestHead, RequestHeadType},
|
||||||
|
request::Request,
|
||||||
|
};
|
||||||
|
|
|
@ -10,8 +10,7 @@ use std::{
|
||||||
use http::{header, Method, Uri, Version};
|
use http::{header, Method, Uri, Version};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
header::HeaderMap, BoxedPayloadStream, Extensions, HttpMessage, Message, Payload,
|
header::HeaderMap, BoxedPayloadStream, Extensions, HttpMessage, Message, Payload, RequestHead,
|
||||||
RequestHead,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// An HTTP request.
|
/// An HTTP request.
|
||||||
|
@ -234,7 +233,6 @@ impl<P> fmt::Debug for Request<P> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::convert::TryFrom;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_basics() {
|
fn test_basics() {
|
||||||
|
|
|
@ -5,7 +5,5 @@ mod head;
|
||||||
#[allow(clippy::module_inception)]
|
#[allow(clippy::module_inception)]
|
||||||
mod response;
|
mod response;
|
||||||
|
|
||||||
pub use self::builder::ResponseBuilder;
|
|
||||||
pub(crate) use self::head::BoxedResponseHead;
|
pub(crate) use self::head::BoxedResponseHead;
|
||||||
pub use self::head::ResponseHead;
|
pub use self::{builder::ResponseBuilder, head::ResponseHead, response::Response};
|
||||||
pub use self::response::Response;
|
|
||||||
|
|
|
@ -30,9 +30,9 @@ use crate::{
|
||||||
///
|
///
|
||||||
/// # Automatic HTTP Version Selection
|
/// # Automatic HTTP Version Selection
|
||||||
/// There are two ways to select the HTTP version of an incoming connection:
|
/// There are two ways to select the HTTP version of an incoming connection:
|
||||||
/// - One is to rely on the ALPN information that is provided when using a TLS (HTTPS); both
|
/// - One is to rely on the ALPN information that is provided when using TLS (HTTPS); both versions
|
||||||
/// versions are supported automatically when using either of the `.rustls()` or `.openssl()`
|
/// are supported automatically when using either of the `.rustls()` or `.openssl()` finalizing
|
||||||
/// finalizing methods.
|
/// methods.
|
||||||
/// - The other is to read the first few bytes of the TCP stream. This is the only viable approach
|
/// - The other is to read the first few bytes of the TCP stream. This is the only viable approach
|
||||||
/// for supporting H2C, which allows the HTTP/2 protocol to work over plaintext connections. Use
|
/// for supporting H2C, which allows the HTTP/2 protocol to work over plaintext connections. Use
|
||||||
/// the `.tcp_auto_h2c()` finalizing method to enable this behavior.
|
/// the `.tcp_auto_h2c()` finalizing method to enable this behavior.
|
||||||
|
@ -200,13 +200,8 @@ where
|
||||||
/// The resulting service only supports HTTP/1.x.
|
/// The resulting service only supports HTTP/1.x.
|
||||||
pub fn tcp(
|
pub fn tcp(
|
||||||
self,
|
self,
|
||||||
) -> impl ServiceFactory<
|
) -> impl ServiceFactory<TcpStream, Config = (), Response = (), Error = DispatchError, InitError = ()>
|
||||||
TcpStream,
|
{
|
||||||
Config = (),
|
|
||||||
Response = (),
|
|
||||||
Error = DispatchError,
|
|
||||||
InitError = (),
|
|
||||||
> {
|
|
||||||
fn_service(|io: TcpStream| async {
|
fn_service(|io: TcpStream| async {
|
||||||
let peer_addr = io.peer_addr().ok();
|
let peer_addr = io.peer_addr().ok();
|
||||||
Ok((io, Protocol::Http1, peer_addr))
|
Ok((io, Protocol::Http1, peer_addr))
|
||||||
|
@ -219,13 +214,8 @@ where
|
||||||
#[cfg(feature = "http2")]
|
#[cfg(feature = "http2")]
|
||||||
pub fn tcp_auto_h2c(
|
pub fn tcp_auto_h2c(
|
||||||
self,
|
self,
|
||||||
) -> impl ServiceFactory<
|
) -> impl ServiceFactory<TcpStream, Config = (), Response = (), Error = DispatchError, InitError = ()>
|
||||||
TcpStream,
|
{
|
||||||
Config = (),
|
|
||||||
Response = (),
|
|
||||||
Error = DispatchError,
|
|
||||||
InitError = (),
|
|
||||||
> {
|
|
||||||
fn_service(move |io: TcpStream| async move {
|
fn_service(move |io: TcpStream| async move {
|
||||||
// subset of HTTP/2 preface defined by RFC 9113 §3.4
|
// subset of HTTP/2 preface defined by RFC 9113 §3.4
|
||||||
// this subset was chosen to maximize likelihood that peeking only once will allow us to
|
// this subset was chosen to maximize likelihood that peeking only once will allow us to
|
||||||
|
@ -563,10 +553,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn _poll_ready(
|
pub(super) fn _poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Response<BoxBody>>> {
|
||||||
&self,
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
) -> Poll<Result<(), Response<BoxBody>>> {
|
|
||||||
ready!(self.flow.expect.poll_ready(cx).map_err(Into::into))?;
|
ready!(self.flow.expect.poll_ready(cx).map_err(Into::into))?;
|
||||||
|
|
||||||
ready!(self.flow.service.poll_ready(cx).map_err(Into::into))?;
|
ready!(self.flow.service.poll_ready(cx).map_err(Into::into))?;
|
||||||
|
@ -625,10 +612,7 @@ where
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(
|
fn call(&self, (io, proto, peer_addr): (T, Protocol, Option<net::SocketAddr>)) -> Self::Future {
|
||||||
&self,
|
|
||||||
(io, proto, peer_addr): (T, Protocol, Option<net::SocketAddr>),
|
|
||||||
) -> Self::Future {
|
|
||||||
let conn_data = OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
|
let conn_data = OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
|
||||||
|
|
||||||
match proto {
|
match proto {
|
||||||
|
|
|
@ -70,15 +70,14 @@ mod inner {
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use actix_codec::Framed;
|
||||||
use actix_service::{IntoService, Service};
|
use actix_service::{IntoService, Service};
|
||||||
use futures_core::stream::Stream;
|
use futures_core::stream::Stream;
|
||||||
use local_channel::mpsc;
|
use local_channel::mpsc;
|
||||||
use pin_project_lite::pin_project;
|
use pin_project_lite::pin_project;
|
||||||
use tracing::debug;
|
|
||||||
|
|
||||||
use actix_codec::Framed;
|
|
||||||
use tokio::io::{AsyncRead, AsyncWrite};
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
use tokio_util::codec::{Decoder, Encoder};
|
use tokio_util::codec::{Decoder, Encoder};
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
use crate::{body::BoxBody, Response};
|
use crate::{body::BoxBody, Response};
|
||||||
|
|
||||||
|
@ -413,9 +412,7 @@ mod inner {
|
||||||
}
|
}
|
||||||
State::Error(_) => {
|
State::Error(_) => {
|
||||||
// flush write buffer
|
// flush write buffer
|
||||||
if !this.framed.is_write_buf_empty()
|
if !this.framed.is_write_buf_empty() && this.framed.flush(cx).is_pending() {
|
||||||
&& this.framed.flush(cx).is_pending()
|
|
||||||
{
|
|
||||||
return Poll::Pending;
|
return Poll::Pending;
|
||||||
}
|
}
|
||||||
Poll::Ready(Err(this.state.take_error()))
|
Poll::Ready(Err(this.state.take_error()))
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use std::convert::TryFrom;
|
|
||||||
|
|
||||||
use bytes::{Buf, BufMut, BytesMut};
|
use bytes::{Buf, BufMut, BytesMut};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
@ -222,9 +221,10 @@ impl Parser {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
struct F {
|
struct F {
|
||||||
finished: bool,
|
finished: bool,
|
||||||
opcode: OpCode,
|
opcode: OpCode,
|
||||||
|
|
|
@ -8,8 +8,7 @@ use std::io;
|
||||||
use derive_more::{Display, Error, From};
|
use derive_more::{Display, Error, From};
|
||||||
use http::{header, Method, StatusCode};
|
use http::{header, Method, StatusCode};
|
||||||
|
|
||||||
use crate::body::BoxBody;
|
use crate::{body::BoxBody, header::HeaderValue, RequestHead, Response, ResponseBuilder};
|
||||||
use crate::{header::HeaderValue, RequestHead, Response, ResponseBuilder};
|
|
||||||
|
|
||||||
mod codec;
|
mod codec;
|
||||||
mod dispatcher;
|
mod dispatcher;
|
||||||
|
@ -17,10 +16,12 @@ mod frame;
|
||||||
mod mask;
|
mod mask;
|
||||||
mod proto;
|
mod proto;
|
||||||
|
|
||||||
pub use self::codec::{Codec, Frame, Item, Message};
|
pub use self::{
|
||||||
pub use self::dispatcher::Dispatcher;
|
codec::{Codec, Frame, Item, Message},
|
||||||
pub use self::frame::Parser;
|
dispatcher::Dispatcher,
|
||||||
pub use self::proto::{hash_key, CloseCode, CloseReason, OpCode};
|
frame::Parser,
|
||||||
|
proto::{hash_key, CloseCode, CloseReason, OpCode},
|
||||||
|
};
|
||||||
|
|
||||||
/// WebSocket protocol errors.
|
/// WebSocket protocol errors.
|
||||||
#[derive(Debug, Display, Error, From)]
|
#[derive(Debug, Display, Error, From)]
|
||||||
|
@ -219,10 +220,8 @@ pub fn handshake_response(req: &RequestHead) -> ResponseBuilder {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{header, Method};
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::test::TestRequest;
|
use crate::{header, test::TestRequest, Method};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_handshake() {
|
fn test_handshake() {
|
||||||
|
|
|
@ -321,8 +321,7 @@ async fn h2_body_length() {
|
||||||
let mut srv = test_server(move || {
|
let mut srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.h2(|_| async {
|
.h2(|_| async {
|
||||||
let body =
|
let body = once(async { Ok::<_, Infallible>(Bytes::from_static(STR.as_ref())) });
|
||||||
once(async { Ok::<_, Infallible>(Bytes::from_static(STR.as_ref())) });
|
|
||||||
|
|
||||||
Ok::<_, Infallible>(
|
Ok::<_, Infallible>(
|
||||||
Response::ok().set_body(SizedStream::new(STR.len() as u64, body)),
|
Response::ok().set_body(SizedStream::new(STR.len() as u64, body)),
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
extern crate tls_rustls as rustls;
|
extern crate tls_rustls as rustls;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
convert::{Infallible, TryFrom},
|
convert::Infallible,
|
||||||
io::{self, BufReader, Write},
|
io::{self, BufReader, Write},
|
||||||
net::{SocketAddr, TcpStream as StdTcpStream},
|
net::{SocketAddr, TcpStream as StdTcpStream},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
@ -90,11 +90,9 @@ pub fn get_negotiated_alpn_protocol(
|
||||||
|
|
||||||
config.alpn_protocols.push(client_alpn_protocol.to_vec());
|
config.alpn_protocols.push(client_alpn_protocol.to_vec());
|
||||||
|
|
||||||
let mut sess = rustls::ClientConnection::new(
|
let mut sess =
|
||||||
Arc::new(config),
|
rustls::ClientConnection::new(Arc::new(config), ServerName::try_from("localhost").unwrap())
|
||||||
ServerName::try_from("localhost").unwrap(),
|
.unwrap();
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut sock = StdTcpStream::connect(addr).unwrap();
|
let mut sock = StdTcpStream::connect(addr).unwrap();
|
||||||
let mut stream = rustls::Stream::new(&mut sess, &mut sock);
|
let mut stream = rustls::Stream::new(&mut sess, &mut sock);
|
||||||
|
|
|
@ -166,8 +166,7 @@ async fn chunked_payload() {
|
||||||
|
|
||||||
for chunk_size in chunk_sizes.iter() {
|
for chunk_size in chunk_sizes.iter() {
|
||||||
let mut bytes = Vec::new();
|
let mut bytes = Vec::new();
|
||||||
let random_bytes: Vec<u8> =
|
let random_bytes: Vec<u8> = (0..*chunk_size).map(|_| rand::random::<u8>()).collect();
|
||||||
(0..*chunk_size).map(|_| rand::random::<u8>()).collect();
|
|
||||||
|
|
||||||
bytes.extend(format!("{:X}\r\n", chunk_size).as_bytes());
|
bytes.extend(format!("{:X}\r\n", chunk_size).as_bytes());
|
||||||
bytes.extend(&random_bytes[..]);
|
bytes.extend(&random_bytes[..]);
|
||||||
|
@ -352,8 +351,7 @@ async fn http10_keepalive() {
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
|
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
|
||||||
let _ =
|
let _ = stream.write_all(b"GET /test/tests/test HTTP/1.0\r\nconnection: keep-alive\r\n\r\n");
|
||||||
stream.write_all(b"GET /test/tests/test HTTP/1.0\r\nconnection: keep-alive\r\n\r\n");
|
|
||||||
let mut data = vec![0; 1024];
|
let mut data = vec![0; 1024];
|
||||||
let _ = stream.read(&mut data);
|
let _ = stream.read(&mut data);
|
||||||
assert_eq!(&data[..17], b"HTTP/1.0 200 OK\r\n");
|
assert_eq!(&data[..17], b"HTTP/1.0 200 OK\r\n");
|
||||||
|
@ -795,8 +793,9 @@ async fn not_modified_spec_h1() {
|
||||||
.map_into_boxed_body(),
|
.map_into_boxed_body(),
|
||||||
|
|
||||||
// with no content-length
|
// with no content-length
|
||||||
"/body" => Response::with_body(StatusCode::NOT_MODIFIED, "1234")
|
"/body" => {
|
||||||
.map_into_boxed_body(),
|
Response::with_body(StatusCode::NOT_MODIFIED, "1234").map_into_boxed_body()
|
||||||
|
}
|
||||||
|
|
||||||
// with manual content-length header and specific None body
|
// with manual content-length header and specific None body
|
||||||
"/cl-none" => {
|
"/cl-none" => {
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
- Minimum supported Rust version (MSRV) is now 1.65 due to transitive `time` dependency.
|
||||||
|
|
||||||
## 0.6.0 - 2023-02-26
|
## 0.6.0 - 2023-02-26
|
||||||
|
|
||||||
- Add `MultipartForm` derive macro.
|
- Add `MultipartForm` derive macro.
|
||||||
|
|
|
@ -7,7 +7,7 @@ keywords = ["http", "web", "framework", "async", "futures"]
|
||||||
homepage = "https://actix.rs"
|
homepage = "https://actix.rs"
|
||||||
repository = "https://github.com/actix/actix-web.git"
|
repository = "https://github.com/actix/actix-web.git"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-multipart-derive)
|
[](https://crates.io/crates/actix-multipart-derive)
|
||||||
[](https://docs.rs/actix-multipart-derive/0.5.0)
|
[](https://docs.rs/actix-multipart-derive/0.5.0)
|
||||||

|

|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-multipart-derive/0.5.0)
|
[](https://deps.rs/crate/actix-multipart-derive/0.5.0)
|
||||||
|
@ -14,4 +14,4 @@
|
||||||
## Documentation & Resources
|
## Documentation & Resources
|
||||||
|
|
||||||
- [API Documentation](https://docs.rs/actix-multipart-derive)
|
- [API Documentation](https://docs.rs/actix-multipart-derive)
|
||||||
- Minimum Supported Rust Version (MSRV): 1.59
|
- Minimum Supported Rust Version (MSRV): 1.65
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
|
|
||||||
use std::{collections::HashSet, convert::TryFrom as _};
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use darling::{FromDeriveInput, FromField, FromMeta};
|
use darling::{FromDeriveInput, FromField, FromMeta};
|
||||||
use parse_size::parse_size;
|
use parse_size::parse_size;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#[rustversion::stable(1.59)] // MSRV
|
#[rustversion::stable(1.65)] // MSRV
|
||||||
#[test]
|
#[test]
|
||||||
fn compile_macros() {
|
fn compile_macros() {
|
||||||
let t = trybuild::TestCases::new();
|
let t = trybuild::TestCases::new();
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
## Unreleased - 2023-xx-xx
|
## Unreleased - 2023-xx-xx
|
||||||
|
|
||||||
|
- Minimum supported Rust version (MSRV) is now 1.65 due to transitive `time` dependency.
|
||||||
|
|
||||||
## 0.6.0 - 2023-02-26
|
## 0.6.0 - 2023-02-26
|
||||||
|
|
||||||
- Added `MultipartForm` typed data extractor. [#2883]
|
- Added `MultipartForm` typed data extractor. [#2883]
|
||||||
|
|
|
@ -10,7 +10,7 @@ keywords = ["http", "web", "framework", "async", "futures"]
|
||||||
homepage = "https://actix.rs"
|
homepage = "https://actix.rs"
|
||||||
repository = "https://github.com/actix/actix-web.git"
|
repository = "https://github.com/actix/actix-web.git"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
@ -19,7 +19,7 @@ all-features = true
|
||||||
[features]
|
[features]
|
||||||
default = ["tempfile", "derive"]
|
default = ["tempfile", "derive"]
|
||||||
derive = ["actix-multipart-derive"]
|
derive = ["actix-multipart-derive"]
|
||||||
tempfile = ["tempfile-dep", "tokio/fs"]
|
tempfile = ["dep:tempfile", "tokio/fs"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-multipart-derive = { version = "=0.6.0", optional = true }
|
actix-multipart-derive = { version = "=0.6.0", optional = true }
|
||||||
|
@ -38,9 +38,8 @@ mime = "0.3"
|
||||||
serde = "1"
|
serde = "1"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
serde_plain = "1"
|
serde_plain = "1"
|
||||||
# TODO(MSRV 1.60): replace with dep: prefix
|
tempfile = { version = "3.4", optional = true }
|
||||||
tempfile-dep = { package = "tempfile", version = "3.4", optional = true }
|
tokio = { version = "1.24.2", features = ["sync", "io-util"] }
|
||||||
tokio = { version = "1.24.2", features = ["sync"] }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-http = "3"
|
actix-http = "3"
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-multipart)
|
[](https://crates.io/crates/actix-multipart)
|
||||||
[](https://docs.rs/actix-multipart/0.6.0)
|
[](https://docs.rs/actix-multipart/0.6.0)
|
||||||

|

|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-multipart/0.6.0)
|
[](https://deps.rs/crate/actix-multipart/0.6.0)
|
||||||
|
@ -14,4 +14,4 @@
|
||||||
## Documentation & Resources
|
## Documentation & Resources
|
||||||
|
|
||||||
- [API Documentation](https://docs.rs/actix-multipart)
|
- [API Documentation](https://docs.rs/actix-multipart)
|
||||||
- Minimum Supported Rust Version (MSRV): 1.59
|
- Minimum Supported Rust Version (MSRV): 1.65
|
||||||
|
|
|
@ -27,11 +27,7 @@ pub struct Bytes {
|
||||||
impl<'t> FieldReader<'t> for Bytes {
|
impl<'t> FieldReader<'t> for Bytes {
|
||||||
type Future = LocalBoxFuture<'t, Result<Self, MultipartError>>;
|
type Future = LocalBoxFuture<'t, Result<Self, MultipartError>>;
|
||||||
|
|
||||||
fn read_field(
|
fn read_field(_: &'t HttpRequest, mut field: Field, limits: &'t mut Limits) -> Self::Future {
|
||||||
_: &'t HttpRequest,
|
|
||||||
mut field: Field,
|
|
||||||
limits: &'t mut Limits,
|
|
||||||
) -> Self::Future {
|
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let mut buf = BytesMut::with_capacity(131_072);
|
let mut buf = BytesMut::with_capacity(131_072);
|
||||||
|
|
||||||
|
|
|
@ -7,13 +7,12 @@ use derive_more::{Deref, DerefMut, Display, Error};
|
||||||
use futures_core::future::LocalBoxFuture;
|
use futures_core::future::LocalBoxFuture;
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
|
|
||||||
|
use super::FieldErrorHandler;
|
||||||
use crate::{
|
use crate::{
|
||||||
form::{bytes::Bytes, FieldReader, Limits},
|
form::{bytes::Bytes, FieldReader, Limits},
|
||||||
Field, MultipartError,
|
Field, MultipartError,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::FieldErrorHandler;
|
|
||||||
|
|
||||||
/// Deserialize from JSON.
|
/// Deserialize from JSON.
|
||||||
#[derive(Debug, Deref, DerefMut)]
|
#[derive(Debug, Deref, DerefMut)]
|
||||||
pub struct Json<T: DeserializeOwned>(pub T);
|
pub struct Json<T: DeserializeOwned>(pub T);
|
||||||
|
|
|
@ -429,8 +429,7 @@ mod tests {
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_options() {
|
async fn test_options() {
|
||||||
let srv =
|
let srv = actix_test::start(|| App::new().route("/", web::post().to(test_options_route)));
|
||||||
actix_test::start(|| App::new().route("/", web::post().to(test_options_route)));
|
|
||||||
|
|
||||||
let mut form = multipart::Form::default();
|
let mut form = multipart::Form::default();
|
||||||
form.add_text("field1", "value");
|
form.add_text("field1", "value");
|
||||||
|
@ -481,9 +480,7 @@ mod tests {
|
||||||
field3: Text<String>,
|
field3: Text<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn test_field_renaming_route(
|
async fn test_field_renaming_route(form: MultipartForm<TestFieldRenaming>) -> impl Responder {
|
||||||
form: MultipartForm<TestFieldRenaming>,
|
|
||||||
) -> impl Responder {
|
|
||||||
assert_eq!(&*form.field1, "renamed");
|
assert_eq!(&*form.field1, "renamed");
|
||||||
assert_eq!(&*form.field2, "field1");
|
assert_eq!(&*form.field2, "field1");
|
||||||
assert_eq!(&*form.field3, "field3");
|
assert_eq!(&*form.field3, "field3");
|
||||||
|
@ -492,9 +489,8 @@ mod tests {
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_field_renaming() {
|
async fn test_field_renaming() {
|
||||||
let srv = actix_test::start(|| {
|
let srv =
|
||||||
App::new().route("/", web::post().to(test_field_renaming_route))
|
actix_test::start(|| App::new().route("/", web::post().to(test_field_renaming_route)));
|
||||||
});
|
|
||||||
|
|
||||||
let mut form = multipart::Form::default();
|
let mut form = multipart::Form::default();
|
||||||
form.add_text("renamed", "renamed");
|
form.add_text("renamed", "renamed");
|
||||||
|
@ -623,9 +619,7 @@ mod tests {
|
||||||
HttpResponse::Ok().finish()
|
HttpResponse::Ok().finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn test_upload_limits_file(
|
async fn test_upload_limits_file(form: MultipartForm<TestFileUploadLimits>) -> impl Responder {
|
||||||
form: MultipartForm<TestFileUploadLimits>,
|
|
||||||
) -> impl Responder {
|
|
||||||
assert!(form.field.size > 0);
|
assert!(form.field.size > 0);
|
||||||
HttpResponse::Ok().finish()
|
HttpResponse::Ok().finish()
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ use derive_more::{Display, Error};
|
||||||
use futures_core::future::LocalBoxFuture;
|
use futures_core::future::LocalBoxFuture;
|
||||||
use futures_util::TryStreamExt as _;
|
use futures_util::TryStreamExt as _;
|
||||||
use mime::Mime;
|
use mime::Mime;
|
||||||
use tempfile_dep::NamedTempFile;
|
use tempfile::NamedTempFile;
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
|
|
||||||
use super::FieldErrorHandler;
|
use super::FieldErrorHandler;
|
||||||
|
@ -39,23 +39,20 @@ pub struct TempFile {
|
||||||
impl<'t> FieldReader<'t> for TempFile {
|
impl<'t> FieldReader<'t> for TempFile {
|
||||||
type Future = LocalBoxFuture<'t, Result<Self, MultipartError>>;
|
type Future = LocalBoxFuture<'t, Result<Self, MultipartError>>;
|
||||||
|
|
||||||
fn read_field(
|
fn read_field(req: &'t HttpRequest, mut field: Field, limits: &'t mut Limits) -> Self::Future {
|
||||||
req: &'t HttpRequest,
|
|
||||||
mut field: Field,
|
|
||||||
limits: &'t mut Limits,
|
|
||||||
) -> Self::Future {
|
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let config = TempFileConfig::from_req(req);
|
let config = TempFileConfig::from_req(req);
|
||||||
let field_name = field.name().to_owned();
|
let field_name = field.name().to_owned();
|
||||||
let mut size = 0;
|
let mut size = 0;
|
||||||
|
|
||||||
let file = config.create_tempfile().map_err(|err| {
|
let file = config
|
||||||
config.map_error(req, &field_name, TempFileError::FileIo(err))
|
.create_tempfile()
|
||||||
})?;
|
.map_err(|err| config.map_error(req, &field_name, TempFileError::FileIo(err)))?;
|
||||||
|
|
||||||
let mut file_async = tokio::fs::File::from_std(file.reopen().map_err(|err| {
|
let mut file_async =
|
||||||
config.map_error(req, &field_name, TempFileError::FileIo(err))
|
tokio::fs::File::from_std(file.reopen().map_err(|err| {
|
||||||
})?);
|
config.map_error(req, &field_name, TempFileError::FileIo(err))
|
||||||
|
})?);
|
||||||
|
|
||||||
while let Some(chunk) = field.try_next().await? {
|
while let Some(chunk) = field.try_next().await? {
|
||||||
limits.try_consume_limits(chunk.len(), false)?;
|
limits.try_consume_limits(chunk.len(), false)?;
|
||||||
|
@ -65,9 +62,10 @@ impl<'t> FieldReader<'t> for TempFile {
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
file_async.flush().await.map_err(|err| {
|
file_async
|
||||||
config.map_error(req, &field_name, TempFileError::FileIo(err))
|
.flush()
|
||||||
})?;
|
.await
|
||||||
|
.map_err(|err| config.map_error(req, &field_name, TempFileError::FileIo(err)))?;
|
||||||
|
|
||||||
Ok(TempFile {
|
Ok(TempFile {
|
||||||
file,
|
file,
|
||||||
|
@ -131,12 +129,7 @@ impl TempFileConfig {
|
||||||
.unwrap_or(&DEFAULT_CONFIG)
|
.unwrap_or(&DEFAULT_CONFIG)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_error(
|
fn map_error(&self, req: &HttpRequest, field_name: &str, err: TempFileError) -> MultipartError {
|
||||||
&self,
|
|
||||||
req: &HttpRequest,
|
|
||||||
field_name: &str,
|
|
||||||
err: TempFileError,
|
|
||||||
) -> MultipartError {
|
|
||||||
let source = if let Some(ref err_handler) = self.err_handler {
|
let source = if let Some(ref err_handler) = self.err_handler {
|
||||||
(err_handler)(err, req)
|
(err_handler)(err, req)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -17,5 +17,7 @@ mod server;
|
||||||
|
|
||||||
pub mod form;
|
pub mod form;
|
||||||
|
|
||||||
pub use self::error::MultipartError;
|
pub use self::{
|
||||||
pub use self::server::{Field, Multipart};
|
error::MultipartError,
|
||||||
|
server::{Field, Multipart},
|
||||||
|
};
|
||||||
|
|
|
@ -2,9 +2,7 @@
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
cell::{Cell, RefCell, RefMut},
|
cell::{Cell, RefCell, RefMut},
|
||||||
cmp,
|
cmp, fmt,
|
||||||
convert::TryFrom,
|
|
||||||
fmt,
|
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
|
@ -163,8 +161,8 @@ impl InnerMultipart {
|
||||||
for h in hdrs {
|
for h in hdrs {
|
||||||
let name =
|
let name =
|
||||||
HeaderName::try_from(h.name).map_err(|_| ParseError::Header)?;
|
HeaderName::try_from(h.name).map_err(|_| ParseError::Header)?;
|
||||||
let value = HeaderValue::try_from(h.value)
|
let value =
|
||||||
.map_err(|_| ParseError::Header)?;
|
HeaderValue::try_from(h.value).map_err(|_| ParseError::Header)?;
|
||||||
headers.append(name, value);
|
headers.append(name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,8 +222,7 @@ impl InnerMultipart {
|
||||||
if chunk.len() < boundary.len() {
|
if chunk.len() < boundary.len() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if &chunk[..2] == b"--" && &chunk[2..chunk.len() - 2] == boundary.as_bytes()
|
if &chunk[..2] == b"--" && &chunk[2..chunk.len() - 2] == boundary.as_bytes() {
|
||||||
{
|
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
if chunk.len() < boundary.len() + 2 {
|
if chunk.len() < boundary.len() + 2 {
|
||||||
|
@ -270,9 +267,7 @@ impl InnerMultipart {
|
||||||
match field.borrow_mut().poll(safety) {
|
match field.borrow_mut().poll(safety) {
|
||||||
Poll::Pending => return Poll::Pending,
|
Poll::Pending => return Poll::Pending,
|
||||||
Poll::Ready(Some(Ok(_))) => continue,
|
Poll::Ready(Some(Ok(_))) => continue,
|
||||||
Poll::Ready(Some(Err(err))) => {
|
Poll::Ready(Some(Err(err))) => return Poll::Ready(Some(Err(err))),
|
||||||
return Poll::Ready(Some(Err(err)))
|
|
||||||
}
|
|
||||||
Poll::Ready(None) => true,
|
Poll::Ready(None) => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -291,8 +286,7 @@ impl InnerMultipart {
|
||||||
match self.state {
|
match self.state {
|
||||||
// read until first boundary
|
// read until first boundary
|
||||||
InnerState::FirstBoundary => {
|
InnerState::FirstBoundary => {
|
||||||
match InnerMultipart::skip_until_boundary(&mut payload, &self.boundary)?
|
match InnerMultipart::skip_until_boundary(&mut payload, &self.boundary)? {
|
||||||
{
|
|
||||||
Some(eof) => {
|
Some(eof) => {
|
||||||
if eof {
|
if eof {
|
||||||
self.state = InnerState::Eof;
|
self.state = InnerState::Eof;
|
||||||
|
@ -669,9 +663,7 @@ impl InnerField {
|
||||||
Ok(None) => Poll::Pending,
|
Ok(None) => Poll::Pending,
|
||||||
Ok(Some(line)) => {
|
Ok(Some(line)) => {
|
||||||
if line.as_ref() != b"\r\n" {
|
if line.as_ref() != b"\r\n" {
|
||||||
log::warn!(
|
log::warn!("multipart field did not read all the data or it is malformed");
|
||||||
"multipart field did not read all the data or it is malformed"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
Poll::Ready(None)
|
Poll::Ready(None)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
## Unreleased - 2023-xx-xx
|
## Unreleased - 2023-xx-xx
|
||||||
|
|
||||||
|
- Minimum supported Rust version (MSRV) is now 1.65 due to transitive `time` dependency.
|
||||||
|
|
||||||
## 0.5.1 - 2022-09-19
|
## 0.5.1 - 2022-09-19
|
||||||
|
|
||||||
- Correct typo in error string for `i32` deserialization. [#2876]
|
- Correct typo in error string for `i32` deserialization. [#2876]
|
||||||
|
|
|
@ -10,7 +10,7 @@ description = "Resource path matching and router"
|
||||||
keywords = ["actix", "router", "routing"]
|
keywords = ["actix", "router", "routing"]
|
||||||
repository = "https://github.com/actix/actix-web.git"
|
repository = "https://github.com/actix/actix-web.git"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "actix_router"
|
name = "actix_router"
|
||||||
|
@ -27,7 +27,7 @@ serde = "1"
|
||||||
tracing = { version = "0.1.30", default-features = false, features = ["log"] }
|
tracing = { version = "0.1.30", default-features = false, features = ["log"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = { version = "0.4", features = ["html_reports"] }
|
criterion = { version = "0.5", features = ["html_reports"] }
|
||||||
http = "0.2.7"
|
http = "0.2.7"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
percent-encoding = "2.1"
|
percent-encoding = "2.1"
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
#![allow(clippy::uninlined_format_args)]
|
#![allow(clippy::uninlined_format_args)]
|
||||||
|
|
||||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||||
|
|
||||||
fn compare_quoters(c: &mut Criterion) {
|
fn compare_quoters(c: &mut Criterion) {
|
||||||
let mut group = c.benchmark_group("Compare Quoters");
|
let mut group = c.benchmark_group("Compare Quoters");
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use serde::de::{self, Deserializer, Error as DeError, Visitor};
|
use serde::{
|
||||||
use serde::forward_to_deserialize_any;
|
de::{self, Deserializer, Error as DeError, Visitor},
|
||||||
|
forward_to_deserialize_any,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::path::{Path, PathIter};
|
use crate::{
|
||||||
use crate::{Quoter, ResourcePath};
|
path::{Path, PathIter},
|
||||||
|
Quoter, ResourcePath,
|
||||||
|
};
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static FULL_QUOTER: Quoter = Quoter::new(b"", b"");
|
static FULL_QUOTER: Quoter = Quoter::new(b"", b"");
|
||||||
|
@ -486,11 +490,7 @@ impl<'de> de::VariantAccess<'de> for UnitVariant {
|
||||||
Err(de::value::Error::custom("not supported"))
|
Err(de::value::Error::custom("not supported"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn struct_variant<V>(
|
fn struct_variant<V>(self, _: &'static [&'static str], _: V) -> Result<V::Value, Self::Error>
|
||||||
self,
|
|
||||||
_: &'static [&'static str],
|
|
||||||
_: V,
|
|
||||||
) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
where
|
||||||
V: Visitor<'de>,
|
V: Visitor<'de>,
|
||||||
{
|
{
|
||||||
|
@ -503,9 +503,7 @@ mod tests {
|
||||||
use serde::{de, Deserialize};
|
use serde::{de, Deserialize};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::path::Path;
|
use crate::{path::Path, router::Router, ResourceDef};
|
||||||
use crate::router::Router;
|
|
||||||
use crate::ResourceDef;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct MyStruct {
|
struct MyStruct {
|
||||||
|
@ -572,13 +570,11 @@ mod tests {
|
||||||
assert_eq!(s.key, "name");
|
assert_eq!(s.key, "name");
|
||||||
assert_eq!(s.value, 32);
|
assert_eq!(s.value, 32);
|
||||||
|
|
||||||
let s: (String, u8) =
|
let s: (String, u8) = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
|
||||||
de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
|
|
||||||
assert_eq!(s.0, "name");
|
assert_eq!(s.0, "name");
|
||||||
assert_eq!(s.1, 32);
|
assert_eq!(s.1, 32);
|
||||||
|
|
||||||
let res: Vec<String> =
|
let res: Vec<String> = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
|
||||||
de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
|
|
||||||
assert_eq!(res[0], "name".to_owned());
|
assert_eq!(res[0], "name".to_owned());
|
||||||
assert_eq!(res[1], "32".to_owned());
|
assert_eq!(res[1], "32".to_owned());
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,13 +18,14 @@ mod router;
|
||||||
#[cfg(feature = "http")]
|
#[cfg(feature = "http")]
|
||||||
mod url;
|
mod url;
|
||||||
|
|
||||||
pub use self::de::PathDeserializer;
|
|
||||||
pub use self::path::Path;
|
|
||||||
pub use self::pattern::{IntoPatterns, Patterns};
|
|
||||||
pub use self::quoter::Quoter;
|
|
||||||
pub use self::resource::ResourceDef;
|
|
||||||
pub use self::resource_path::{Resource, ResourcePath};
|
|
||||||
pub use self::router::{ResourceId, Router, RouterBuilder};
|
|
||||||
|
|
||||||
#[cfg(feature = "http")]
|
#[cfg(feature = "http")]
|
||||||
pub use self::url::Url;
|
pub use self::url::Url;
|
||||||
|
pub use self::{
|
||||||
|
de::PathDeserializer,
|
||||||
|
path::Path,
|
||||||
|
pattern::{IntoPatterns, Patterns},
|
||||||
|
quoter::Quoter,
|
||||||
|
resource::ResourceDef,
|
||||||
|
resource_path::{Resource, ResourcePath},
|
||||||
|
router::{ResourceId, Router, RouterBuilder},
|
||||||
|
};
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
use std::borrow::Cow;
|
use std::{
|
||||||
use std::ops::{DerefMut, Index};
|
borrow::Cow,
|
||||||
|
ops::{DerefMut, Index},
|
||||||
|
};
|
||||||
|
|
||||||
use serde::de;
|
use serde::de;
|
||||||
|
|
||||||
|
|
|
@ -1389,8 +1389,6 @@ mod tests {
|
||||||
#[cfg(feature = "http")]
|
#[cfg(feature = "http")]
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_urlencoded_param() {
|
fn parse_urlencoded_param() {
|
||||||
use std::convert::TryFrom;
|
|
||||||
|
|
||||||
let re = ResourceDef::new("/user/{id}/test");
|
let re = ResourceDef::new("/user/{id}/test");
|
||||||
|
|
||||||
let mut path = Path::new("/user/2345/test");
|
let mut path = Path::new("/user/2345/test");
|
||||||
|
@ -1743,9 +1741,7 @@ mod tests {
|
||||||
ResourceDef::new("/{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}");
|
ResourceDef::new("/{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}");
|
||||||
|
|
||||||
// panics
|
// panics
|
||||||
ResourceDef::new(
|
ResourceDef::new("/{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}/{q}");
|
||||||
"/{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}/{q}",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -117,11 +117,7 @@ where
|
||||||
U: Default,
|
U: Default,
|
||||||
{
|
{
|
||||||
/// Registers resource for specified path.
|
/// Registers resource for specified path.
|
||||||
pub fn path(
|
pub fn path(&mut self, path: impl IntoPatterns, val: T) -> (&mut ResourceDef, &mut T, &mut U) {
|
||||||
&mut self,
|
|
||||||
path: impl IntoPatterns,
|
|
||||||
val: T,
|
|
||||||
) -> (&mut ResourceDef, &mut T, &mut U) {
|
|
||||||
self.push(ResourceDef::new(path), val, U::default())
|
self.push(ResourceDef::new(path), val, U::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,8 +138,10 @@ where
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::path::Path;
|
use crate::{
|
||||||
use crate::router::{ResourceId, Router};
|
path::Path,
|
||||||
|
router::{ResourceId, Router},
|
||||||
|
};
|
||||||
|
|
||||||
#[allow(clippy::cognitive_complexity)]
|
#[allow(clippy::cognitive_complexity)]
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
use crate::ResourcePath;
|
use crate::{Quoter, ResourcePath};
|
||||||
|
|
||||||
use crate::Quoter;
|
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static DEFAULT_QUOTER: Quoter = Quoter::new(b"", b"%/+");
|
static DEFAULT_QUOTER: Quoter = Quoter::new(b"", b"%/+");
|
||||||
|
@ -65,7 +63,6 @@ impl ResourcePath for Url {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use http::Uri;
|
use http::Uri;
|
||||||
use std::convert::TryFrom;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{Path, ResourceDef};
|
use crate::{Path, ResourceDef};
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
|
|
||||||
## Unreleased - 2023-xx-xx
|
## Unreleased - 2023-xx-xx
|
||||||
|
|
||||||
|
- Add `TestServerConfig::workers()` setter method.
|
||||||
|
- Minimum supported Rust version (MSRV) is now 1.65 due to transitive `time` dependency.
|
||||||
|
|
||||||
## 0.1.1 - 2023-02-26
|
## 0.1.1 - 2023-02-26
|
||||||
|
|
||||||
- Add `TestServerConfig::port()` setter method.
|
- Add `TestServerConfig::port()` setter method.
|
||||||
|
|
|
@ -16,7 +16,7 @@ categories = [
|
||||||
"web-programming::websocket",
|
"web-programming::websocket",
|
||||||
]
|
]
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
@ -43,6 +43,6 @@ log = "0.4"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
serde_urlencoded = "0.7"
|
serde_urlencoded = "0.7"
|
||||||
tls-openssl = { package = "openssl", version = "0.10.9", optional = true }
|
tls-openssl = { package = "openssl", version = "0.10.55", optional = true }
|
||||||
tls-rustls = { package = "rustls", version = "0.20.0", optional = true }
|
tls-rustls = { package = "rustls", version = "0.20", optional = true }
|
||||||
tokio = { version = "1.24.2", features = ["sync"] }
|
tokio = { version = "1.24.2", features = ["sync"] }
|
||||||
|
|
|
@ -45,8 +45,8 @@ use actix_http::{header::HeaderMap, ws, HttpService, Method, Request, Response};
|
||||||
pub use actix_http_test::unused_addr;
|
pub use actix_http_test::unused_addr;
|
||||||
use actix_service::{map_config, IntoServiceFactory, ServiceFactory, ServiceFactoryExt as _};
|
use actix_service::{map_config, IntoServiceFactory, ServiceFactory, ServiceFactoryExt as _};
|
||||||
pub use actix_web::test::{
|
pub use actix_web::test::{
|
||||||
call_and_read_body, call_and_read_body_json, call_service, init_service, ok_service,
|
call_and_read_body, call_and_read_body_json, call_service, init_service, ok_service, read_body,
|
||||||
read_body, read_body_json, status_service, TestRequest,
|
read_body_json, status_service, TestRequest,
|
||||||
};
|
};
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
body::MessageBody,
|
body::MessageBody,
|
||||||
|
@ -154,16 +154,16 @@ where
|
||||||
let srv_cfg = cfg.clone();
|
let srv_cfg = cfg.clone();
|
||||||
let timeout = cfg.client_request_timeout;
|
let timeout = cfg.client_request_timeout;
|
||||||
|
|
||||||
let builder = Server::build().workers(1).disable_signals().system_exit();
|
let builder = Server::build()
|
||||||
|
.workers(cfg.workers)
|
||||||
|
.disable_signals()
|
||||||
|
.system_exit();
|
||||||
|
|
||||||
let srv = match srv_cfg.stream {
|
let srv = match srv_cfg.stream {
|
||||||
StreamType::Tcp => match srv_cfg.tp {
|
StreamType::Tcp => match srv_cfg.tp {
|
||||||
HttpVer::Http1 => builder.listen("test", tcp, move || {
|
HttpVer::Http1 => builder.listen("test", tcp, move || {
|
||||||
let app_cfg = AppConfig::__priv_test_new(
|
let app_cfg =
|
||||||
false,
|
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
|
||||||
local_addr.to_string(),
|
|
||||||
local_addr,
|
|
||||||
);
|
|
||||||
|
|
||||||
let fac = factory()
|
let fac = factory()
|
||||||
.into_factory()
|
.into_factory()
|
||||||
|
@ -175,11 +175,8 @@ where
|
||||||
.tcp()
|
.tcp()
|
||||||
}),
|
}),
|
||||||
HttpVer::Http2 => builder.listen("test", tcp, move || {
|
HttpVer::Http2 => builder.listen("test", tcp, move || {
|
||||||
let app_cfg = AppConfig::__priv_test_new(
|
let app_cfg =
|
||||||
false,
|
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
|
||||||
local_addr.to_string(),
|
|
||||||
local_addr,
|
|
||||||
);
|
|
||||||
|
|
||||||
let fac = factory()
|
let fac = factory()
|
||||||
.into_factory()
|
.into_factory()
|
||||||
|
@ -191,11 +188,8 @@ where
|
||||||
.tcp()
|
.tcp()
|
||||||
}),
|
}),
|
||||||
HttpVer::Both => builder.listen("test", tcp, move || {
|
HttpVer::Both => builder.listen("test", tcp, move || {
|
||||||
let app_cfg = AppConfig::__priv_test_new(
|
let app_cfg =
|
||||||
false,
|
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
|
||||||
local_addr.to_string(),
|
|
||||||
local_addr,
|
|
||||||
);
|
|
||||||
|
|
||||||
let fac = factory()
|
let fac = factory()
|
||||||
.into_factory()
|
.into_factory()
|
||||||
|
@ -210,11 +204,8 @@ where
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
StreamType::Openssl(acceptor) => match cfg.tp {
|
StreamType::Openssl(acceptor) => match cfg.tp {
|
||||||
HttpVer::Http1 => builder.listen("test", tcp, move || {
|
HttpVer::Http1 => builder.listen("test", tcp, move || {
|
||||||
let app_cfg = AppConfig::__priv_test_new(
|
let app_cfg =
|
||||||
false,
|
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
|
||||||
local_addr.to_string(),
|
|
||||||
local_addr,
|
|
||||||
);
|
|
||||||
|
|
||||||
let fac = factory()
|
let fac = factory()
|
||||||
.into_factory()
|
.into_factory()
|
||||||
|
@ -226,11 +217,8 @@ where
|
||||||
.openssl(acceptor.clone())
|
.openssl(acceptor.clone())
|
||||||
}),
|
}),
|
||||||
HttpVer::Http2 => builder.listen("test", tcp, move || {
|
HttpVer::Http2 => builder.listen("test", tcp, move || {
|
||||||
let app_cfg = AppConfig::__priv_test_new(
|
let app_cfg =
|
||||||
false,
|
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
|
||||||
local_addr.to_string(),
|
|
||||||
local_addr,
|
|
||||||
);
|
|
||||||
|
|
||||||
let fac = factory()
|
let fac = factory()
|
||||||
.into_factory()
|
.into_factory()
|
||||||
|
@ -242,11 +230,8 @@ where
|
||||||
.openssl(acceptor.clone())
|
.openssl(acceptor.clone())
|
||||||
}),
|
}),
|
||||||
HttpVer::Both => builder.listen("test", tcp, move || {
|
HttpVer::Both => builder.listen("test", tcp, move || {
|
||||||
let app_cfg = AppConfig::__priv_test_new(
|
let app_cfg =
|
||||||
false,
|
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
|
||||||
local_addr.to_string(),
|
|
||||||
local_addr,
|
|
||||||
);
|
|
||||||
|
|
||||||
let fac = factory()
|
let fac = factory()
|
||||||
.into_factory()
|
.into_factory()
|
||||||
|
@ -261,11 +246,8 @@ where
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
StreamType::Rustls(config) => match cfg.tp {
|
StreamType::Rustls(config) => match cfg.tp {
|
||||||
HttpVer::Http1 => builder.listen("test", tcp, move || {
|
HttpVer::Http1 => builder.listen("test", tcp, move || {
|
||||||
let app_cfg = AppConfig::__priv_test_new(
|
let app_cfg =
|
||||||
false,
|
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
|
||||||
local_addr.to_string(),
|
|
||||||
local_addr,
|
|
||||||
);
|
|
||||||
|
|
||||||
let fac = factory()
|
let fac = factory()
|
||||||
.into_factory()
|
.into_factory()
|
||||||
|
@ -277,11 +259,8 @@ where
|
||||||
.rustls(config.clone())
|
.rustls(config.clone())
|
||||||
}),
|
}),
|
||||||
HttpVer::Http2 => builder.listen("test", tcp, move || {
|
HttpVer::Http2 => builder.listen("test", tcp, move || {
|
||||||
let app_cfg = AppConfig::__priv_test_new(
|
let app_cfg =
|
||||||
false,
|
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
|
||||||
local_addr.to_string(),
|
|
||||||
local_addr,
|
|
||||||
);
|
|
||||||
|
|
||||||
let fac = factory()
|
let fac = factory()
|
||||||
.into_factory()
|
.into_factory()
|
||||||
|
@ -293,11 +272,8 @@ where
|
||||||
.rustls(config.clone())
|
.rustls(config.clone())
|
||||||
}),
|
}),
|
||||||
HttpVer::Both => builder.listen("test", tcp, move || {
|
HttpVer::Both => builder.listen("test", tcp, move || {
|
||||||
let app_cfg = AppConfig::__priv_test_new(
|
let app_cfg =
|
||||||
false,
|
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
|
||||||
local_addr.to_string(),
|
|
||||||
local_addr,
|
|
||||||
);
|
|
||||||
|
|
||||||
let fac = factory()
|
let fac = factory()
|
||||||
.into_factory()
|
.into_factory()
|
||||||
|
@ -394,6 +370,7 @@ pub struct TestServerConfig {
|
||||||
stream: StreamType,
|
stream: StreamType,
|
||||||
client_request_timeout: Duration,
|
client_request_timeout: Duration,
|
||||||
port: u16,
|
port: u16,
|
||||||
|
workers: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TestServerConfig {
|
impl Default for TestServerConfig {
|
||||||
|
@ -410,6 +387,7 @@ impl TestServerConfig {
|
||||||
stream: StreamType::Tcp,
|
stream: StreamType::Tcp,
|
||||||
client_request_timeout: Duration::from_secs(5),
|
client_request_timeout: Duration::from_secs(5),
|
||||||
port: 0,
|
port: 0,
|
||||||
|
workers: 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -452,6 +430,14 @@ impl TestServerConfig {
|
||||||
self.port = port;
|
self.port = port;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets number of workers for the test server.
|
||||||
|
///
|
||||||
|
/// By default, the server uses 1 worker
|
||||||
|
pub fn workers(mut self, workers: usize) -> Self {
|
||||||
|
self.workers = workers;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A basic HTTP server controller that simplifies the process of writing integration tests for
|
/// A basic HTTP server controller that simplifies the process of writing integration tests for
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
## Unreleased - 2023-xx-xx
|
## Unreleased - 2023-xx-xx
|
||||||
|
|
||||||
|
- Minimum supported Rust version (MSRV) is now 1.65 due to transitive `time` dependency.
|
||||||
|
|
||||||
## 4.2.0 - 2023-01-21
|
## 4.2.0 - 2023-01-21
|
||||||
|
|
||||||
- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency.
|
- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency.
|
||||||
|
|
|
@ -7,7 +7,7 @@ keywords = ["actix", "http", "web", "framework", "async"]
|
||||||
homepage = "https://actix.rs"
|
homepage = "https://actix.rs"
|
||||||
repository = "https://github.com/actix/actix-web"
|
repository = "https://github.com/actix/actix-web"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "actix_web_actors"
|
name = "actix_web_actors"
|
||||||
|
@ -32,7 +32,6 @@ actix-test = "0.1"
|
||||||
awc = { version = "3", default-features = false }
|
awc = { version = "3", default-features = false }
|
||||||
actix-web = { version = "4", features = ["macros"] }
|
actix-web = { version = "4", features = ["macros"] }
|
||||||
|
|
||||||
|
env_logger = "0.10"
|
||||||
|
futures-util = { version = "0.3.17", default-features = false, features = ["std"] }
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
|
|
||||||
env_logger = "0.9"
|
|
||||||
futures-util = { version = "0.3.17", default-features = false }
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-web-actors)
|
[](https://crates.io/crates/actix-web-actors)
|
||||||
[](https://docs.rs/actix-web-actors/4.2.0)
|
[](https://docs.rs/actix-web-actors/4.2.0)
|
||||||

|

|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-web-actors/4.2.0)
|
[](https://deps.rs/crate/actix-web-actors/4.2.0)
|
||||||
|
@ -14,4 +14,4 @@
|
||||||
## Documentation & Resources
|
## Documentation & Resources
|
||||||
|
|
||||||
- [API Documentation](https://docs.rs/actix-web-actors)
|
- [API Documentation](https://docs.rs/actix-web-actors)
|
||||||
- Minimum Supported Rust Version (MSRV): 1.59
|
- Minimum Supported Rust Version (MSRV): 1.65
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
use std::collections::VecDeque;
|
use std::{
|
||||||
use std::future::Future;
|
collections::VecDeque,
|
||||||
use std::pin::Pin;
|
future::Future,
|
||||||
use std::task::{Context, Poll};
|
pin::Pin,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
use actix::dev::{AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, ToEnvelope};
|
|
||||||
use actix::fut::ActorFuture;
|
|
||||||
use actix::{
|
use actix::{
|
||||||
|
dev::{AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, ToEnvelope},
|
||||||
|
fut::ActorFuture,
|
||||||
Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle,
|
Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle,
|
||||||
};
|
};
|
||||||
use actix_web::error::Error;
|
use actix_web::error::Error;
|
||||||
|
@ -247,9 +249,11 @@ mod tests {
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use actix::Actor;
|
use actix::Actor;
|
||||||
use actix_web::http::StatusCode;
|
use actix_web::{
|
||||||
use actix_web::test::{call_service, init_service, read_body, TestRequest};
|
http::StatusCode,
|
||||||
use actix_web::{web, App, HttpResponse};
|
test::{call_service, init_service, read_body, TestRequest},
|
||||||
|
web, App, HttpResponse,
|
||||||
|
};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -58,7 +58,6 @@
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::VecDeque,
|
collections::VecDeque,
|
||||||
convert::TryFrom,
|
|
||||||
future::Future,
|
future::Future,
|
||||||
io, mem,
|
io, mem,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
|
@ -67,17 +66,14 @@ use std::{
|
||||||
|
|
||||||
use actix::{
|
use actix::{
|
||||||
dev::{
|
dev::{
|
||||||
AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, StreamHandler,
|
AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, StreamHandler, ToEnvelope,
|
||||||
ToEnvelope,
|
|
||||||
},
|
},
|
||||||
fut::ActorFuture,
|
fut::ActorFuture,
|
||||||
Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message as ActixMessage,
|
Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message as ActixMessage,
|
||||||
SpawnHandle,
|
SpawnHandle,
|
||||||
};
|
};
|
||||||
use actix_http::ws::{hash_key, Codec};
|
use actix_http::ws::{hash_key, Codec};
|
||||||
pub use actix_http::ws::{
|
pub use actix_http::ws::{CloseCode, CloseReason, Frame, HandshakeError, Message, ProtocolError};
|
||||||
CloseCode, CloseReason, Frame, HandshakeError, Message, ProtocolError,
|
|
||||||
};
|
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
error::{Error, PayloadError},
|
error::{Error, PayloadError},
|
||||||
http::{
|
http::{
|
||||||
|
@ -427,16 +423,16 @@ pub fn handshake_with_protocols(
|
||||||
};
|
};
|
||||||
|
|
||||||
// check requested protocols
|
// check requested protocols
|
||||||
let protocol =
|
let protocol = req
|
||||||
req.headers()
|
.headers()
|
||||||
.get(&header::SEC_WEBSOCKET_PROTOCOL)
|
.get(&header::SEC_WEBSOCKET_PROTOCOL)
|
||||||
.and_then(|req_protocols| {
|
.and_then(|req_protocols| {
|
||||||
let req_protocols = req_protocols.to_str().ok()?;
|
let req_protocols = req_protocols.to_str().ok()?;
|
||||||
req_protocols
|
req_protocols
|
||||||
.split(',')
|
.split(',')
|
||||||
.map(|req_p| req_p.trim())
|
.map(|req_p| req_p.trim())
|
||||||
.find(|req_p| protocols.iter().any(|p| p == req_p))
|
.find(|req_p| protocols.iter().any(|p| p == req_p))
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut response = HttpResponse::build(StatusCode::SWITCHING_PROTOCOLS)
|
let mut response = HttpResponse::build(StatusCode::SWITCHING_PROTOCOLS)
|
||||||
.upgrade("websocket")
|
.upgrade("websocket")
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
## Unreleased - 2023-xx-xx
|
## Unreleased - 2023-xx-xx
|
||||||
|
|
||||||
|
- Minimum supported Rust version (MSRV) is now 1.65 due to transitive `time` dependency.
|
||||||
|
|
||||||
## 4.2.0 - 2023-02-26
|
## 4.2.0 - 2023-02-26
|
||||||
|
|
||||||
- Add support for custom methods with the `#[route]` macro. [#2969]
|
- Add support for custom methods with the `#[route]` macro. [#2969]
|
||||||
|
|
|
@ -9,7 +9,7 @@ authors = [
|
||||||
"Rob Ede <robjtede@icloud.com>",
|
"Rob Ede <robjtede@icloud.com>",
|
||||||
]
|
]
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-web-codegen)
|
[](https://crates.io/crates/actix-web-codegen)
|
||||||
[](https://docs.rs/actix-web-codegen/4.2.0)
|
[](https://docs.rs/actix-web-codegen/4.2.0)
|
||||||

|

|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-web-codegen/4.2.0)
|
[](https://deps.rs/crate/actix-web-codegen/4.2.0)
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
## Documentation & Resources
|
## Documentation & Resources
|
||||||
|
|
||||||
- [API Documentation](https://docs.rs/actix-web-codegen)
|
- [API Documentation](https://docs.rs/actix-web-codegen)
|
||||||
- Minimum Supported Rust Version (MSRV): 1.59
|
- Minimum Supported Rust Version (MSRV): 1.65
|
||||||
|
|
||||||
## Compile Testing
|
## Compile Testing
|
||||||
|
|
||||||
|
|
|
@ -153,37 +153,37 @@ pub fn routes(_: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
|
|
||||||
macro_rules! method_macro {
|
macro_rules! method_macro {
|
||||||
($variant:ident, $method:ident) => {
|
($variant:ident, $method:ident) => {
|
||||||
#[doc = concat!("Creates route handler with `actix_web::guard::", stringify!($variant), "`.")]
|
#[doc = concat!("Creates route handler with `actix_web::guard::", stringify!($variant), "`.")]
|
||||||
///
|
///
|
||||||
/// # Syntax
|
/// # Syntax
|
||||||
/// ```plain
|
/// ```plain
|
||||||
#[doc = concat!("#[", stringify!($method), r#"("path"[, attributes])]"#)]
|
#[doc = concat!("#[", stringify!($method), r#"("path"[, attributes])]"#)]
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// # Attributes
|
/// # Attributes
|
||||||
/// - `"path"`: Raw literal string with path for which to register handler.
|
/// - `"path"`: Raw literal string with path for which to register handler.
|
||||||
/// - `name = "resource_name"`: Specifies resource name for the handler. If not set, the function
|
/// - `name = "resource_name"`: Specifies resource name for the handler. If not set, the
|
||||||
/// name of handler is used.
|
/// function name of handler is used.
|
||||||
/// - `guard = "function_name"`: Registers function as guard using `actix_web::guard::fn_guard`.
|
/// - `guard = "function_name"`: Registers function as guard using `actix_web::guard::fn_guard`.
|
||||||
/// - `wrap = "Middleware"`: Registers a resource middleware.
|
/// - `wrap = "Middleware"`: Registers a resource middleware.
|
||||||
///
|
///
|
||||||
/// # Notes
|
/// # Notes
|
||||||
/// Function name can be specified as any expression that is going to be accessible to the
|
/// Function name can be specified as any expression that is going to be accessible to the
|
||||||
/// generate code, e.g `my_guard` or `my_module::my_guard`.
|
/// generate code, e.g `my_guard` or `my_module::my_guard`.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
/// # use actix_web::HttpResponse;
|
/// # use actix_web::HttpResponse;
|
||||||
#[doc = concat!("# use actix_web_codegen::", stringify!($method), ";")]
|
#[doc = concat!("# use actix_web_codegen::", stringify!($method), ";")]
|
||||||
#[doc = concat!("#[", stringify!($method), r#"("/")]"#)]
|
#[doc = concat!("#[", stringify!($method), r#"("/")]"#)]
|
||||||
/// async fn example() -> HttpResponse {
|
/// async fn example() -> HttpResponse {
|
||||||
/// HttpResponse::Ok().finish()
|
/// HttpResponse::Ok().finish()
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn $method(args: TokenStream, input: TokenStream) -> TokenStream {
|
pub fn $method(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
route::with_method(Some(route::MethodType::$variant), args, input)
|
route::with_method(Some(route::MethodType::$variant), args, input)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{collections::HashSet, convert::TryFrom};
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use actix_router::ResourceDef;
|
use actix_router::ResourceDef;
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
|
@ -488,31 +488,32 @@ pub(crate) fn with_methods(input: TokenStream) -> TokenStream {
|
||||||
|
|
||||||
ast.attrs = others.into_iter().map(Result::unwrap_err).collect();
|
ast.attrs = others.into_iter().map(Result::unwrap_err).collect();
|
||||||
|
|
||||||
let methods =
|
let methods = match methods
|
||||||
match methods
|
.into_iter()
|
||||||
.into_iter()
|
.map(Result::unwrap)
|
||||||
.map(Result::unwrap)
|
.map(|(method, attr)| {
|
||||||
.map(|(method, attr)| {
|
attr.parse_meta().and_then(|args| {
|
||||||
attr.parse_meta().and_then(|args| {
|
if let Meta::List(args) = args {
|
||||||
if let Meta::List(args) = args {
|
Args::new(args.nested.into_iter().collect(), Some(method))
|
||||||
Args::new(args.nested.into_iter().collect(), Some(method))
|
} else {
|
||||||
} else {
|
Err(syn::Error::new_spanned(attr, "Invalid input for macro"))
|
||||||
Err(syn::Error::new_spanned(attr, "Invalid input for macro"))
|
}
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>, _>>()
|
})
|
||||||
{
|
.collect::<Result<Vec<_>, _>>()
|
||||||
Ok(methods) if methods.is_empty() => return input_and_compile_error(
|
{
|
||||||
|
Ok(methods) if methods.is_empty() => {
|
||||||
|
return input_and_compile_error(
|
||||||
input,
|
input,
|
||||||
syn::Error::new(
|
syn::Error::new(
|
||||||
Span::call_site(),
|
Span::call_site(),
|
||||||
"The #[routes] macro requires at least one `#[<method>(..)]` attribute.",
|
"The #[routes] macro requires at least one `#[<method>(..)]` attribute.",
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
Ok(methods) => methods,
|
}
|
||||||
Err(err) => return input_and_compile_error(input, err),
|
Ok(methods) => methods,
|
||||||
};
|
Err(err) => return input_and_compile_error(input, err),
|
||||||
|
};
|
||||||
|
|
||||||
match Route::multiple(methods, ast) {
|
match Route::multiple(methods, ast) {
|
||||||
Ok(route) => route.into_token_stream().into(),
|
Ok(route) => route.into_token_stream().into(),
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#[rustversion::stable(1.59)] // MSRV
|
#[rustversion::stable(1.65)] // MSRV
|
||||||
#[test]
|
#[test]
|
||||||
fn compile_macros() {
|
fn compile_macros() {
|
||||||
let t = trybuild::TestCases::new();
|
let t = trybuild::TestCases::new();
|
||||||
|
|
|
@ -8,10 +8,20 @@ error[E0277]: the trait bound `fn() -> impl std::future::Future<Output = String>
|
||||||
--> tests/trybuild/route-custom-lowercase.rs:14:55
|
--> tests/trybuild/route-custom-lowercase.rs:14:55
|
||||||
|
|
|
|
||||||
14 | let srv = actix_test::start(|| App::new().service(index));
|
14 | let srv = actix_test::start(|| App::new().service(index));
|
||||||
| ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future<Output = String> {index}`
|
| ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for fn item `fn() -> impl std::future::Future<Output = String> {index}`
|
||||||
| |
|
| |
|
||||||
| required by a bound introduced by this call
|
| required by a bound introduced by this call
|
||||||
|
|
|
|
||||||
|
= help: the following other types implement trait `HttpServiceFactory`:
|
||||||
|
(A, B)
|
||||||
|
(A, B, C)
|
||||||
|
(A, B, C, D)
|
||||||
|
(A, B, C, D, E)
|
||||||
|
(A, B, C, D, E, F)
|
||||||
|
(A, B, C, D, E, F, G)
|
||||||
|
(A, B, C, D, E, F, G, H)
|
||||||
|
(A, B, C, D, E, F, G, H, I)
|
||||||
|
and $N others
|
||||||
note: required by a bound in `App::<T>::service`
|
note: required by a bound in `App::<T>::service`
|
||||||
--> $WORKSPACE/actix-web/src/app.rs
|
--> $WORKSPACE/actix-web/src/app.rs
|
||||||
|
|
|
|
||||||
|
|
|
@ -5,15 +5,25 @@ error: HTTP method defined more than once: `GET`
|
||||||
| ^^^^^
|
| ^^^^^
|
||||||
|
|
||||||
error[E0277]: the trait bound `fn() -> impl std::future::Future<Output = String> {index}: HttpServiceFactory` is not satisfied
|
error[E0277]: the trait bound `fn() -> impl std::future::Future<Output = String> {index}: HttpServiceFactory` is not satisfied
|
||||||
--> tests/trybuild/route-duplicate-method-fail.rs:12:55
|
--> tests/trybuild/route-duplicate-method-fail.rs:12:55
|
||||||
|
|
|
|
||||||
12 | let srv = actix_test::start(|| App::new().service(index));
|
12 | let srv = actix_test::start(|| App::new().service(index));
|
||||||
| ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future<Output = String> {index}`
|
| ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for fn item `fn() -> impl std::future::Future<Output = String> {index}`
|
||||||
| |
|
| |
|
||||||
| required by a bound introduced by this call
|
| required by a bound introduced by this call
|
||||||
|
|
|
|
||||||
|
= help: the following other types implement trait `HttpServiceFactory`:
|
||||||
|
(A, B)
|
||||||
|
(A, B, C)
|
||||||
|
(A, B, C, D)
|
||||||
|
(A, B, C, D, E)
|
||||||
|
(A, B, C, D, E, F)
|
||||||
|
(A, B, C, D, E, F, G)
|
||||||
|
(A, B, C, D, E, F, G, H)
|
||||||
|
(A, B, C, D, E, F, G, H, I)
|
||||||
|
and $N others
|
||||||
note: required by a bound in `App::<T>::service`
|
note: required by a bound in `App::<T>::service`
|
||||||
--> $WORKSPACE/actix-web/src/app.rs
|
--> $WORKSPACE/actix-web/src/app.rs
|
||||||
|
|
|
|
||||||
| F: HttpServiceFactory + 'static,
|
| F: HttpServiceFactory + 'static,
|
||||||
| ^^^^^^^^^^^^^^^^^^ required by this bound in `App::<T>::service`
|
| ^^^^^^^^^^^^^^^^^^ required by this bound in `App::<T>::service`
|
||||||
|
|
|
@ -7,15 +7,25 @@ error: The #[route(..)] macro requires at least one `method` attribute
|
||||||
= note: this error originates in the attribute macro `route` (in Nightly builds, run with -Z macro-backtrace for more info)
|
= note: this error originates in the attribute macro `route` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
|
||||||
error[E0277]: the trait bound `fn() -> impl std::future::Future<Output = String> {index}: HttpServiceFactory` is not satisfied
|
error[E0277]: the trait bound `fn() -> impl std::future::Future<Output = String> {index}: HttpServiceFactory` is not satisfied
|
||||||
--> tests/trybuild/route-missing-method-fail.rs:12:55
|
--> tests/trybuild/route-missing-method-fail.rs:12:55
|
||||||
|
|
|
|
||||||
12 | let srv = actix_test::start(|| App::new().service(index));
|
12 | let srv = actix_test::start(|| App::new().service(index));
|
||||||
| ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future<Output = String> {index}`
|
| ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for fn item `fn() -> impl std::future::Future<Output = String> {index}`
|
||||||
| |
|
| |
|
||||||
| required by a bound introduced by this call
|
| required by a bound introduced by this call
|
||||||
|
|
|
|
||||||
|
= help: the following other types implement trait `HttpServiceFactory`:
|
||||||
|
(A, B)
|
||||||
|
(A, B, C)
|
||||||
|
(A, B, C, D)
|
||||||
|
(A, B, C, D, E)
|
||||||
|
(A, B, C, D, E, F)
|
||||||
|
(A, B, C, D, E, F, G)
|
||||||
|
(A, B, C, D, E, F, G, H)
|
||||||
|
(A, B, C, D, E, F, G, H, I)
|
||||||
|
and $N others
|
||||||
note: required by a bound in `App::<T>::service`
|
note: required by a bound in `App::<T>::service`
|
||||||
--> $WORKSPACE/actix-web/src/app.rs
|
--> $WORKSPACE/actix-web/src/app.rs
|
||||||
|
|
|
|
||||||
| F: HttpServiceFactory + 'static,
|
| F: HttpServiceFactory + 'static,
|
||||||
| ^^^^^^^^^^^^^^^^^^ required by this bound in `App::<T>::service`
|
| ^^^^^^^^^^^^^^^^^^ required by this bound in `App::<T>::service`
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue