mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-03 17:41:30 +02:00
Compare commits
21 Commits
http-v2.0.
...
http-v2.0.
Author | SHA1 | Date | |
---|---|---|---|
aa2bd6fbfb | |||
5aad8e24c7 | |||
6e97bc09f8 | |||
160995b8d4 | |||
187646b2f9 | |||
46627be36f | |||
a78380739e | |||
cf1c8abe62 | |||
92b5bcd13f | |||
701bdacfa2 | |||
6dc47c4093 | |||
0ec335a39c | |||
f8d5ad6b53 | |||
43c362779d | |||
971ba3eee1 | |||
2fd96c03e5 | |||
ad7c6d2633 | |||
3362a3d61b | |||
769ea6bd5b | |||
1382094c15 | |||
78594a72bd |
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,6 +1,8 @@
|
||||
## PR Type
|
||||
What kind of change does this PR make?
|
||||
<!-- Thanks for considering contributing actix! -->
|
||||
<!-- Please fill out the following to make our reviews easy. -->
|
||||
|
||||
## PR Type
|
||||
<!-- What kind of change does this PR make? -->
|
||||
<!-- Bug Fix / Feature / Refactor / Code Style / Other -->
|
||||
INSERT_PR_TYPE
|
||||
|
||||
@ -13,6 +15,7 @@ Check your PR fulfills the following:
|
||||
- [ ] Tests for the changes have been added / updated.
|
||||
- [ ] Documentation comments have been added / updated.
|
||||
- [ ] A changelog entry has been made for the appropriate packages.
|
||||
- [ ] Format code with the latest stable rustfmt
|
||||
|
||||
|
||||
## Overview
|
||||
|
9
.github/workflows/bench.yml
vendored
9
.github/workflows/bench.yml
vendored
@ -1,13 +1,18 @@
|
||||
name: Benchmark (Linux)
|
||||
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
check_benchmark:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
|
32
.github/workflows/clippy-fmt.yml
vendored
Normal file
32
.github/workflows/clippy-fmt.yml
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
name: Clippy and rustfmt Check
|
||||
jobs:
|
||||
clippy_check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
components: rustfmt
|
||||
override: true
|
||||
- name: Check with rustfmt
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: clippy
|
||||
override: true
|
||||
- name: Check with Clippy
|
||||
uses: actions-rs/clippy-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --all-features --all --tests
|
11
.github/workflows/linux.yml
vendored
11
.github/workflows/linux.yml
vendored
@ -1,6 +1,11 @@
|
||||
name: CI (Linux)
|
||||
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
@ -8,7 +13,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
version:
|
||||
- 1.41.1 # MSRV
|
||||
- 1.42.0 # MSRV
|
||||
- stable
|
||||
- nightly
|
||||
|
||||
@ -16,7 +21,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install ${{ matrix.version }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
|
9
.github/workflows/macos.yml
vendored
9
.github/workflows/macos.yml
vendored
@ -1,6 +1,11 @@
|
||||
name: CI (macOS)
|
||||
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
@ -15,7 +20,7 @@ jobs:
|
||||
runs-on: macOS-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install ${{ matrix.version }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
|
12
.github/workflows/upload-doc.yml
vendored
12
.github/workflows/upload-doc.yml
vendored
@ -11,7 +11,7 @@ jobs:
|
||||
if: github.repository == 'actix/actix-web'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
@ -29,7 +29,9 @@ jobs:
|
||||
- name: Tweak HTML
|
||||
run: echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html
|
||||
|
||||
- name: Upload documentation
|
||||
run: |
|
||||
git clone https://github.com/davisp/ghp-import.git
|
||||
./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://${{ secrets.GITHUB_TOKEN }}@github.com/"${{ github.repository }}.git" target/doc
|
||||
- name: Deploy to GitHub Pages
|
||||
uses: JamesIves/github-pages-deploy-action@3.5.8
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BRANCH: gh-pages
|
||||
FOLDER: target/doc
|
||||
|
9
.github/workflows/windows.yml
vendored
9
.github/workflows/windows.yml
vendored
@ -1,6 +1,11 @@
|
||||
name: CI (Windows)
|
||||
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
env:
|
||||
VCPKGRS_DYNAMIC: 1
|
||||
@ -18,7 +23,7 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install ${{ matrix.version }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,6 +9,7 @@ guide/build/
|
||||
*.pid
|
||||
*.sock
|
||||
*~
|
||||
.DS_Store
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
32
CHANGES.md
32
CHANGES.md
@ -1,38 +1,50 @@
|
||||
# Changes
|
||||
|
||||
## [Unreleased]
|
||||
## Unreleased - 2020-xx-xx
|
||||
### Changed
|
||||
* `PayloadConfig` is now also considered in `Bytes` and `String` extractors when set
|
||||
using `App::data`. [#1610]
|
||||
* `web::Path` now has a public representation: `web::Path(pub T)` that enables
|
||||
destructuring. [#1594]
|
||||
* `ServiceRequest::app_data` allows retrieval of non-Data data without splitting into parts to
|
||||
access `HttpRequest` which already allows this. [#1618]
|
||||
* Re-export all error types from `awc`. [#1621]
|
||||
* MSRV is now 1.42.0.
|
||||
|
||||
### Fixed
|
||||
* Memory leak of app data in pooled requests. [#1609]
|
||||
|
||||
[#1594]: https://github.com/actix/actix-web/pull/1594
|
||||
[#1609]: https://github.com/actix/actix-web/pull/1609
|
||||
[#1610]: https://github.com/actix/actix-web/pull/1610
|
||||
[#1618]: https://github.com/actix/actix-web/pull/1618
|
||||
[#1621]: https://github.com/actix/actix-web/pull/1621
|
||||
|
||||
|
||||
## 3.0.0-beta.1 - 2020-07-13
|
||||
### Added
|
||||
|
||||
* Re-export `actix_rt::main` as `actix_web::main`.
|
||||
* `HttpRequest::match_pattern` and `ServiceRequest::match_pattern` for extracting the matched
|
||||
resource pattern.
|
||||
* `HttpRequest::match_name` and `ServiceRequest::match_name` for extracting matched resource name.
|
||||
|
||||
### Changed
|
||||
|
||||
* Fix actix_http::h1::dispatcher so it returns when HW_BUFFER_SIZE is reached. Should reduce peak memory consumption during large uploads. [#1550]
|
||||
* Migrate cookie handling to `cookie` crate. Actix-web no longer requires `ring` dependency.
|
||||
* MSRV is now 1.41.1
|
||||
|
||||
### Fixed
|
||||
|
||||
* `NormalizePath` improved consistency when path needs slashes added _and_ removed.
|
||||
|
||||
## [3.0.0-alpha.3] - 2020-05-21
|
||||
|
||||
## 3.0.0-alpha.3 - 2020-05-21
|
||||
### Added
|
||||
|
||||
* Add option to create `Data<T>` from `Arc<T>` [#1509]
|
||||
|
||||
### Changed
|
||||
|
||||
* Resources and Scopes can now access non-overridden data types set on App (or containing scopes) when setting their own data. [#1486]
|
||||
|
||||
* Fix audit issue logging by default peer address [#1485]
|
||||
|
||||
* Bump minimum supported Rust version to 1.40
|
||||
|
||||
* Replace deprecated `net2` crate with `socket2`
|
||||
|
||||
[#1485]: https://github.com/actix/actix-web/pull/1485
|
||||
|
14
Cargo.toml
14
Cargo.toml
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-web"
|
||||
version = "3.0.0-alpha.3"
|
||||
version = "3.0.0-beta.1"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
|
||||
readme = "README.md"
|
||||
@ -11,7 +11,7 @@ documentation = "https://docs.rs/actix-web/"
|
||||
categories = ["network-programming", "asynchronous",
|
||||
"web-programming::http-server",
|
||||
"web-programming::websocket"]
|
||||
license = "MIT/Apache-2.0"
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
@ -76,9 +76,9 @@ actix-macros = "0.1.0"
|
||||
actix-threadpool = "0.3.1"
|
||||
actix-tls = "2.0.0-alpha.1"
|
||||
|
||||
actix-web-codegen = "0.2.2"
|
||||
actix-http = "2.0.0-alpha.4"
|
||||
awc = { version = "2.0.0-alpha.2", default-features = false }
|
||||
actix-web-codegen = "0.3.0-beta.1"
|
||||
actix-http = "2.0.0-beta.3"
|
||||
awc = { version = "2.0.0-beta.1", default-features = false }
|
||||
|
||||
bytes = "0.5.3"
|
||||
derive_more = "0.99.2"
|
||||
@ -124,6 +124,10 @@ actix-files = { path = "actix-files" }
|
||||
actix-multipart = { path = "actix-multipart" }
|
||||
awc = { path = "awc" }
|
||||
|
||||
[[example]]
|
||||
name = "client"
|
||||
required-features = ["rustls"]
|
||||
|
||||
[[bench]]
|
||||
name = "server"
|
||||
harness = false
|
||||
|
20
MIGRATION.md
20
MIGRATION.md
@ -12,6 +12,26 @@
|
||||
* `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a
|
||||
`u64` instead of a `usize`.
|
||||
|
||||
* Code that was using `path.<index>` to access a `web::Path<(A, B, C)>`s elements now needs to use
|
||||
destructuring or `.into_inner()`. For example:
|
||||
|
||||
```rust
|
||||
// Previously:
|
||||
async fn some_route(path: web::Path<(String, String)>) -> String {
|
||||
format!("Hello, {} {}", path.0, path.1)
|
||||
}
|
||||
|
||||
// Now (this also worked before):
|
||||
async fn some_route(path: web::Path<(String, String)>) -> String {
|
||||
let (first_name, last_name) = path.into_inner();
|
||||
format!("Hello, {} {}", first_name, last_name)
|
||||
}
|
||||
// Or (this wasn't previously supported):
|
||||
async fn some_route(web::Path((first_name, last_name)): web::Path<(String, String)>) -> String {
|
||||
format!("Hello, {} {}", first_name, last_name)
|
||||
}
|
||||
```
|
||||
|
||||
## 2.0.0
|
||||
|
||||
* `HttpServer::start()` renamed to `HttpServer::run()`. It also possible to
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
[](https://crates.io/crates/actix-web)
|
||||
[](https://docs.rs/actix-web)
|
||||
[](https://blog.rust-lang.org/2020/02/27/Rust-1.41.1.html)
|
||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html)
|
||||

|
||||
<br />
|
||||
[](https://travis-ci.org/actix/actix-web)
|
||||
@ -32,7 +32,7 @@
|
||||
* Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/))
|
||||
* Includes an async [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html)
|
||||
* Supports [Actix actor framework](https://github.com/actix/actix)
|
||||
* Runs on stable Rust 1.41+
|
||||
* Runs on stable Rust 1.42+
|
||||
|
||||
## Documentation
|
||||
|
||||
@ -61,8 +61,8 @@ Code:
|
||||
use actix_web::{get, web, App, HttpServer, Responder};
|
||||
|
||||
#[get("/{id}/{name}/index.html")]
|
||||
async fn index(info: web::Path<(u32, String)>) -> impl Responder {
|
||||
format!("Hello {}! id:{}", info.1, info.0)
|
||||
async fn index(web::Path((id, name)): web::Path<(u32, String)>) -> impl Responder {
|
||||
format!("Hello {}! id:{}", name, id)
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
|
@ -1,11 +1,12 @@
|
||||
# Changes
|
||||
|
||||
## [unreleased] - xxx
|
||||
## [Unreleased] - 2020-xx-xx
|
||||
|
||||
## [0.3.0-beta.1] - 2020-07-15
|
||||
* Update `v_htmlescape` to 0.10
|
||||
* Update `actix-web` and `actix-http` dependencies to beta.1
|
||||
|
||||
## [0.3.0-alpha.1] - 2020-05-23
|
||||
|
||||
* Update `actix-web` and `actix-http` dependencies to alpha
|
||||
* Fix some typos in the docs
|
||||
* Bump minimum supported Rust version to 1.40
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-files"
|
||||
version = "0.3.0-alpha.1"
|
||||
version = "0.3.0-beta.1"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Static files support for actix web."
|
||||
readme = "README.md"
|
||||
@ -9,7 +9,7 @@ homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-web.git"
|
||||
documentation = "https://docs.rs/actix-files/"
|
||||
categories = ["asynchronous", "web-programming::http-server"]
|
||||
license = "MIT/Apache-2.0"
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
@ -17,8 +17,8 @@ name = "actix_files"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
actix-web = { version = "3.0.0-alpha.3", default-features = false }
|
||||
actix-http = "2.0.0-alpha.4"
|
||||
actix-web = { version = "3.0.0-beta.1", default-features = false }
|
||||
actix-http = "2.0.0-beta.3"
|
||||
actix-service = "1.0.1"
|
||||
bitflags = "1"
|
||||
bytes = "0.5.3"
|
||||
@ -33,4 +33,4 @@ v_htmlescape = "0.10"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "1.0.0"
|
||||
actix-web = { version = "3.0.0-alpha.3", features = ["openssl"] }
|
||||
actix-web = { version = "3.0.0-beta.1", features = ["openssl"] }
|
||||
|
@ -26,7 +26,6 @@ use actix_web::{web, FromRequest, HttpRequest, HttpResponse};
|
||||
use bytes::Bytes;
|
||||
use futures_core::Stream;
|
||||
use futures_util::future::{ok, ready, Either, FutureExt, LocalBoxFuture, Ready};
|
||||
use mime;
|
||||
use mime_guess::from_ext;
|
||||
use percent_encoding::{utf8_percent_encode, CONTROLS};
|
||||
use v_htmlescape::escape as escape_html_entity;
|
||||
@ -250,6 +249,8 @@ pub struct Files {
|
||||
renderer: Rc<DirectoryRenderer>,
|
||||
mime_override: Option<Rc<MimeOverride>>,
|
||||
file_flags: named::Flags,
|
||||
// FIXME: Should re-visit later.
|
||||
#[allow(clippy::redundant_allocation)]
|
||||
guards: Option<Rc<Box<dyn Guard>>>,
|
||||
}
|
||||
|
||||
@ -462,6 +463,8 @@ pub struct FilesService {
|
||||
renderer: Rc<DirectoryRenderer>,
|
||||
mime_override: Option<Rc<MimeOverride>>,
|
||||
file_flags: named::Flags,
|
||||
// FIXME: Should re-visit later.
|
||||
#[allow(clippy::redundant_allocation)]
|
||||
guards: Option<Rc<Box<dyn Guard>>>,
|
||||
}
|
||||
|
||||
@ -501,11 +504,8 @@ impl Service for FilesService {
|
||||
// execute user defined guards
|
||||
(**guard).check(req.head())
|
||||
} else {
|
||||
// default behaviour
|
||||
match *req.method() {
|
||||
Method::HEAD | Method::GET => true,
|
||||
_ => false,
|
||||
}
|
||||
// default behavior
|
||||
matches!(*req.method(), Method::HEAD | Method::GET)
|
||||
};
|
||||
|
||||
if !is_method_valid {
|
||||
@ -952,9 +952,7 @@ mod tests {
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_named_file_content_range_headers() {
|
||||
let srv = test::start(|| {
|
||||
App::new().service(Files::new("/", "."))
|
||||
});
|
||||
let srv = test::start(|| App::new().service(Files::new("/", ".")));
|
||||
|
||||
// Valid range header
|
||||
let response = srv
|
||||
@ -979,9 +977,7 @@ mod tests {
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_named_file_content_length_headers() {
|
||||
let srv = test::start(|| {
|
||||
App::new().service(Files::new("/", "."))
|
||||
});
|
||||
let srv = test::start(|| App::new().service(Files::new("/", ".")));
|
||||
|
||||
// Valid range header
|
||||
let response = srv
|
||||
@ -1020,15 +1016,9 @@ mod tests {
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_head_content_length_headers() {
|
||||
let srv = test::start(|| {
|
||||
App::new().service(Files::new("/", "."))
|
||||
});
|
||||
let srv = test::start(|| App::new().service(Files::new("/", ".")));
|
||||
|
||||
let response = srv
|
||||
.head("/tests/test.binary")
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
let response = srv.head("/tests/test.binary").send().await.unwrap();
|
||||
|
||||
let content_length = response
|
||||
.headers()
|
||||
@ -1097,12 +1087,10 @@ mod tests {
|
||||
#[actix_rt::test]
|
||||
async fn test_named_file_content_encoding() {
|
||||
let mut srv = test::init_service(App::new().wrap(Compress::default()).service(
|
||||
web::resource("/").to(|| {
|
||||
async {
|
||||
web::resource("/").to(|| async {
|
||||
NamedFile::open("Cargo.toml")
|
||||
.unwrap()
|
||||
.set_content_encoding(header::ContentEncoding::Identity)
|
||||
}
|
||||
}),
|
||||
))
|
||||
.await;
|
||||
@ -1119,12 +1107,10 @@ mod tests {
|
||||
#[actix_rt::test]
|
||||
async fn test_named_file_content_encoding_gzip() {
|
||||
let mut srv = test::init_service(App::new().wrap(Compress::default()).service(
|
||||
web::resource("/").to(|| {
|
||||
async {
|
||||
web::resource("/").to(|| async {
|
||||
NamedFile::open("Cargo.toml")
|
||||
.unwrap()
|
||||
.set_content_encoding(header::ContentEncoding::Gzip)
|
||||
}
|
||||
}),
|
||||
))
|
||||
.await;
|
||||
|
@ -8,7 +8,6 @@ use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
use bitflags::bitflags;
|
||||
use mime;
|
||||
use mime_guess::from_path;
|
||||
|
||||
use actix_http::body::SizedStream;
|
||||
@ -90,7 +89,7 @@ impl NamedFile {
|
||||
};
|
||||
|
||||
let ct = from_path(&path).first_or_octet_stream();
|
||||
let disposition_type = match ct.type_() {
|
||||
let disposition = match ct.type_() {
|
||||
mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline,
|
||||
_ => DispositionType::Attachment,
|
||||
};
|
||||
@ -104,8 +103,8 @@ impl NamedFile {
|
||||
}))
|
||||
}
|
||||
let cd = ContentDisposition {
|
||||
disposition: disposition_type,
|
||||
parameters: parameters,
|
||||
disposition,
|
||||
parameters,
|
||||
};
|
||||
(ct, cd)
|
||||
};
|
||||
|
@ -1,6 +1,23 @@
|
||||
# Changes
|
||||
|
||||
## [Unreleased] - xxx
|
||||
## [2.0.0-beta.3] - 2020-08-14
|
||||
|
||||
### Fixed
|
||||
* Memory leak of `client::pool::ConnectorPoolSupport`. [#1626]
|
||||
|
||||
[#1626]: https://github.com/actix/actix-web/pull/1626
|
||||
|
||||
|
||||
## [2.0.0-beta.2] - 2020-07-21
|
||||
### Fixed
|
||||
* Potential UB in h1 decoder using uninitialized memory. [#1614]
|
||||
|
||||
### Changed
|
||||
* Fix illegal chunked encoding. [#1615]
|
||||
|
||||
[#1614]: https://github.com/actix/actix-web/pull/1614
|
||||
[#1615]: https://github.com/actix/actix-web/pull/1615
|
||||
|
||||
|
||||
## [2.0.0-beta.1] - 2020-07-11
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-http"
|
||||
version = "2.0.0-beta.1"
|
||||
version = "2.0.0-beta.3"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix HTTP primitives"
|
||||
readme = "README.md"
|
||||
@ -11,7 +11,7 @@ documentation = "https://docs.rs/actix-http/"
|
||||
categories = ["network-programming", "asynchronous",
|
||||
"web-programming::http-server",
|
||||
"web-programming::websocket"]
|
||||
license = "MIT/Apache-2.0"
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
@ -103,3 +103,7 @@ harness = false
|
||||
[[bench]]
|
||||
name = "status-line"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "uninit-headers"
|
||||
harness = false
|
||||
|
137
actix-http/benches/uninit-headers.rs
Normal file
137
actix-http/benches/uninit-headers.rs
Normal file
@ -0,0 +1,137 @@
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
use bytes::BytesMut;
|
||||
|
||||
// A Miri run detects UB, seen on this playground:
|
||||
// https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f5d9aa166aa48df8dca05fce2b6c3915
|
||||
|
||||
fn bench_header_parsing(c: &mut Criterion) {
|
||||
c.bench_function("Original (Unsound) [short]", |b| {
|
||||
b.iter(|| {
|
||||
let mut buf = BytesMut::from(REQ_SHORT);
|
||||
_original::parse_headers(&mut buf);
|
||||
})
|
||||
});
|
||||
|
||||
c.bench_function("New (safe) [short]", |b| {
|
||||
b.iter(|| {
|
||||
let mut buf = BytesMut::from(REQ_SHORT);
|
||||
_new::parse_headers(&mut buf);
|
||||
})
|
||||
});
|
||||
|
||||
c.bench_function("Original (Unsound) [realistic]", |b| {
|
||||
b.iter(|| {
|
||||
let mut buf = BytesMut::from(REQ);
|
||||
_original::parse_headers(&mut buf);
|
||||
})
|
||||
});
|
||||
|
||||
c.bench_function("New (safe) [realistic]", |b| {
|
||||
b.iter(|| {
|
||||
let mut buf = BytesMut::from(REQ);
|
||||
_new::parse_headers(&mut buf);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, bench_header_parsing);
|
||||
criterion_main!(benches);
|
||||
|
||||
const MAX_HEADERS: usize = 96;
|
||||
|
||||
const EMPTY_HEADER_ARRAY: [httparse::Header<'static>; MAX_HEADERS] =
|
||||
[httparse::EMPTY_HEADER; MAX_HEADERS];
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct HeaderIndex {
|
||||
name: (usize, usize),
|
||||
value: (usize, usize),
|
||||
}
|
||||
|
||||
const EMPTY_HEADER_INDEX: HeaderIndex = HeaderIndex {
|
||||
name: (0, 0),
|
||||
value: (0, 0),
|
||||
};
|
||||
|
||||
const EMPTY_HEADER_INDEX_ARRAY: [HeaderIndex; MAX_HEADERS] =
|
||||
[EMPTY_HEADER_INDEX; MAX_HEADERS];
|
||||
|
||||
impl HeaderIndex {
|
||||
fn record(
|
||||
bytes: &[u8],
|
||||
headers: &[httparse::Header<'_>],
|
||||
indices: &mut [HeaderIndex],
|
||||
) {
|
||||
let bytes_ptr = bytes.as_ptr() as usize;
|
||||
for (header, indices) in headers.iter().zip(indices.iter_mut()) {
|
||||
let name_start = header.name.as_ptr() as usize - bytes_ptr;
|
||||
let name_end = name_start + header.name.len();
|
||||
indices.name = (name_start, name_end);
|
||||
let value_start = header.value.as_ptr() as usize - bytes_ptr;
|
||||
let value_end = value_start + header.value.len();
|
||||
indices.value = (value_start, value_end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// test cases taken from:
|
||||
// https://github.com/seanmonstar/httparse/blob/master/benches/parse.rs
|
||||
|
||||
const REQ_SHORT: &'static [u8] = b"\
|
||||
GET / HTTP/1.0\r\n\
|
||||
Host: example.com\r\n\
|
||||
Cookie: session=60; user_id=1\r\n\r\n";
|
||||
|
||||
const REQ: &'static [u8] = b"\
|
||||
GET /wp-content/uploads/2010/03/hello-kitty-darth-vader-pink.jpg HTTP/1.1\r\n\
|
||||
Host: www.kittyhell.com\r\n\
|
||||
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; ja-JP-mac; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 Pathtraq/0.9\r\n\
|
||||
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n\
|
||||
Accept-Language: ja,en-us;q=0.7,en;q=0.3\r\n\
|
||||
Accept-Encoding: gzip,deflate\r\n\
|
||||
Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7\r\n\
|
||||
Keep-Alive: 115\r\n\
|
||||
Connection: keep-alive\r\n\
|
||||
Cookie: wp_ozh_wsa_visits=2; wp_ozh_wsa_visit_lasttime=xxxxxxxxxx; __utma=xxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.x; __utmz=xxxxxxxxx.xxxxxxxxxx.x.x.utmccn=(referral)|utmcsr=reader.livedoor.com|utmcct=/reader/|utmcmd=referral|padding=under256\r\n\r\n";
|
||||
|
||||
mod _new {
|
||||
use super::*;
|
||||
|
||||
pub fn parse_headers(src: &mut BytesMut) -> usize {
|
||||
let mut headers: [HeaderIndex; MAX_HEADERS] = EMPTY_HEADER_INDEX_ARRAY;
|
||||
let mut parsed: [httparse::Header<'_>; MAX_HEADERS] = EMPTY_HEADER_ARRAY;
|
||||
|
||||
let mut req = httparse::Request::new(&mut parsed);
|
||||
match req.parse(src).unwrap() {
|
||||
httparse::Status::Complete(_len) => {
|
||||
HeaderIndex::record(src, req.headers, &mut headers);
|
||||
req.headers.len()
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod _original {
|
||||
use super::*;
|
||||
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
pub fn parse_headers(src: &mut BytesMut) -> usize {
|
||||
let mut headers: [HeaderIndex; MAX_HEADERS] =
|
||||
unsafe { MaybeUninit::uninit().assume_init() };
|
||||
|
||||
let mut parsed: [httparse::Header<'_>; MAX_HEADERS] =
|
||||
unsafe { MaybeUninit::uninit().assume_init() };
|
||||
|
||||
let mut req = httparse::Request::new(&mut parsed);
|
||||
match req.parse(src).unwrap() {
|
||||
httparse::Status::Complete(_len) => {
|
||||
HeaderIndex::record(src, req.headers, &mut headers);
|
||||
req.headers.len()
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
@ -21,12 +21,7 @@ pub enum BodySize {
|
||||
|
||||
impl BodySize {
|
||||
pub fn is_eof(&self) -> bool {
|
||||
match self {
|
||||
BodySize::None
|
||||
| BodySize::Empty
|
||||
| BodySize::Sized(0) => true,
|
||||
_ => false,
|
||||
}
|
||||
matches!(self, BodySize::None | BodySize::Empty | BodySize::Sized(0))
|
||||
}
|
||||
}
|
||||
|
||||
@ -192,14 +187,8 @@ impl MessageBody for Body {
|
||||
impl PartialEq for Body {
|
||||
fn eq(&self, other: &Body) -> bool {
|
||||
match *self {
|
||||
Body::None => match *other {
|
||||
Body::None => true,
|
||||
_ => false,
|
||||
},
|
||||
Body::Empty => match *other {
|
||||
Body::Empty => true,
|
||||
_ => false,
|
||||
},
|
||||
Body::None => matches!(*other, Body::None),
|
||||
Body::Empty => matches!(*other, Body::Empty),
|
||||
Body::Bytes(ref b) => match *other {
|
||||
Body::Bytes(ref b2) => b == b2,
|
||||
_ => false,
|
||||
@ -476,9 +465,9 @@ where
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use futures_util::stream;
|
||||
use futures_util::future::poll_fn;
|
||||
use futures_util::pin_mut;
|
||||
use futures_util::stream;
|
||||
|
||||
impl Body {
|
||||
pub(crate) fn get_ref(&self) -> &[u8] {
|
||||
@ -612,10 +601,6 @@ mod tests {
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_body_eq() {
|
||||
assert!(Body::None == Body::None);
|
||||
assert!(Body::None != Body::Empty);
|
||||
assert!(Body::Empty == Body::Empty);
|
||||
assert!(Body::Empty != Body::None);
|
||||
assert!(
|
||||
Body::Bytes(Bytes::from_static(b"1"))
|
||||
== Body::Bytes(Bytes::from_static(b"1"))
|
||||
@ -627,7 +612,7 @@ mod tests {
|
||||
async fn test_body_debug() {
|
||||
assert!(format!("{:?}", Body::None).contains("Body::None"));
|
||||
assert!(format!("{:?}", Body::Empty).contains("Body::Empty"));
|
||||
assert!(format!("{:?}", Body::Bytes(Bytes::from_static(b"1"))).contains("1"));
|
||||
assert!(format!("{:?}", Body::Bytes(Bytes::from_static(b"1"))).contains('1'));
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
|
@ -37,10 +37,10 @@ where
|
||||
trace!("Sending client request: {:?} {:?}", head, body.size());
|
||||
let head_req = head.as_ref().method == Method::HEAD;
|
||||
let length = body.size();
|
||||
let eof = match length {
|
||||
BodySize::None | BodySize::Empty | BodySize::Sized(0) => true,
|
||||
_ => false,
|
||||
};
|
||||
let eof = matches!(
|
||||
length,
|
||||
BodySize::None | BodySize::Empty | BodySize::Sized(0)
|
||||
);
|
||||
|
||||
let mut req = Request::new(());
|
||||
*req.uri_mut() = head.as_ref().uri.clone();
|
||||
|
@ -2,7 +2,7 @@ use std::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::rc::{Rc, Weak};
|
||||
use std::rc::Rc;
|
||||
use std::task::{Context, Poll};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
@ -65,14 +65,11 @@ where
|
||||
|
||||
// start support future
|
||||
actix_rt::spawn(ConnectorPoolSupport {
|
||||
connector: connector_rc.clone(),
|
||||
inner: Rc::downgrade(&inner_rc),
|
||||
connector: Rc::clone(&connector_rc),
|
||||
inner: Rc::clone(&inner_rc),
|
||||
});
|
||||
|
||||
ConnectionPool(
|
||||
connector_rc,
|
||||
inner_rc,
|
||||
)
|
||||
ConnectionPool(connector_rc, inner_rc)
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,6 +82,13 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Io> Drop for ConnectionPool<T, Io> {
|
||||
fn drop(&mut self) {
|
||||
// wake up the ConnectorPoolSupport when dropping so it can exit properly.
|
||||
self.1.borrow().waker.wake();
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Io> Service for ConnectionPool<T, Io>
|
||||
where
|
||||
Io: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||
@ -424,7 +428,7 @@ where
|
||||
Io: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||
{
|
||||
connector: T,
|
||||
inner: Weak<RefCell<Inner<Io>>>,
|
||||
inner: Rc<RefCell<Inner<Io>>>,
|
||||
}
|
||||
|
||||
impl<T, Io> Future for ConnectorPoolSupport<T, Io>
|
||||
@ -438,8 +442,13 @@ where
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
|
||||
if let Some(this_inner) = this.inner.upgrade() {
|
||||
let mut inner = this_inner.as_ref().borrow_mut();
|
||||
if Rc::strong_count(this.inner) == 1 {
|
||||
// If we are last copy of Inner<Io> it means the ConnectionPool is already gone
|
||||
// and we are safe to exit.
|
||||
return Poll::Ready(());
|
||||
}
|
||||
|
||||
let mut inner = this.inner.borrow_mut();
|
||||
inner.waker.register(cx.waker());
|
||||
|
||||
// check waiters
|
||||
@ -462,7 +471,7 @@ where
|
||||
if let Err(conn) = tx.send(Ok(IoConnection::new(
|
||||
io,
|
||||
created,
|
||||
Some(Acquired(key.clone(), Some(this_inner.clone()))),
|
||||
Some(Acquired(key.clone(), Some(this.inner.clone()))),
|
||||
))) {
|
||||
let (io, created) = conn.unwrap().into_inner();
|
||||
inner.release_conn(&key, io, created);
|
||||
@ -474,7 +483,7 @@ where
|
||||
OpenWaitingConnection::spawn(
|
||||
key.clone(),
|
||||
tx,
|
||||
this_inner.clone(),
|
||||
this.inner.clone(),
|
||||
this.connector.call(connect),
|
||||
inner.config.clone(),
|
||||
);
|
||||
@ -484,9 +493,6 @@ where
|
||||
}
|
||||
|
||||
Poll::Pending
|
||||
} else {
|
||||
Poll::Ready(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -964,7 +964,6 @@ impl ResponseError for actix::actors::resolver::ResolverError {}
|
||||
mod tests {
|
||||
use super::*;
|
||||
use http::{Error as HttpError, StatusCode};
|
||||
use httparse;
|
||||
use std::io;
|
||||
|
||||
#[test]
|
||||
|
@ -1,7 +1,6 @@
|
||||
use std::convert::TryFrom;
|
||||
use std::io;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::task::Poll;
|
||||
|
||||
use actix_codec::Decoder;
|
||||
@ -46,7 +45,7 @@ impl<T: MessageType> Decoder for MessageDecoder<T> {
|
||||
|
||||
pub(crate) enum PayloadLength {
|
||||
Payload(PayloadType),
|
||||
Upgrade,
|
||||
UpgradeWebSocket,
|
||||
None,
|
||||
}
|
||||
|
||||
@ -65,7 +64,7 @@ pub(crate) trait MessageType: Sized {
|
||||
raw_headers: &[HeaderIndex],
|
||||
) -> Result<PayloadLength, ParseError> {
|
||||
let mut ka = None;
|
||||
let mut has_upgrade = false;
|
||||
let mut has_upgrade_websocket = false;
|
||||
let mut expect = false;
|
||||
let mut chunked = false;
|
||||
let mut content_length = None;
|
||||
@ -77,7 +76,7 @@ pub(crate) trait MessageType: Sized {
|
||||
let name =
|
||||
HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]).unwrap();
|
||||
|
||||
// Unsafe: httparse check header value for valid utf-8
|
||||
// SAFETY: httparse checks header value is valid UTF-8
|
||||
let value = unsafe {
|
||||
HeaderValue::from_maybe_shared_unchecked(
|
||||
slice.slice(idx.value.0..idx.value.1),
|
||||
@ -124,12 +123,9 @@ pub(crate) trait MessageType: Sized {
|
||||
};
|
||||
}
|
||||
header::UPGRADE => {
|
||||
has_upgrade = true;
|
||||
// check content-length, some clients (dart)
|
||||
// sends "content-length: 0" with websocket upgrade
|
||||
if let Ok(val) = value.to_str().map(|val| val.trim()) {
|
||||
if val.eq_ignore_ascii_case("websocket") {
|
||||
content_length = None;
|
||||
has_upgrade_websocket = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -156,13 +152,13 @@ pub(crate) trait MessageType: Sized {
|
||||
Ok(PayloadLength::Payload(PayloadType::Payload(
|
||||
PayloadDecoder::chunked(),
|
||||
)))
|
||||
} else if has_upgrade_websocket {
|
||||
Ok(PayloadLength::UpgradeWebSocket)
|
||||
} else if let Some(len) = content_length {
|
||||
// Content-Length
|
||||
Ok(PayloadLength::Payload(PayloadType::Payload(
|
||||
PayloadDecoder::length(len),
|
||||
)))
|
||||
} else if has_upgrade {
|
||||
Ok(PayloadLength::Upgrade)
|
||||
} else {
|
||||
Ok(PayloadLength::None)
|
||||
}
|
||||
@ -184,16 +180,11 @@ impl MessageType for Request {
|
||||
&mut self.head_mut().headers
|
||||
}
|
||||
|
||||
#[allow(clippy::uninit_assumed_init)]
|
||||
fn decode(src: &mut BytesMut) -> Result<Option<(Self, PayloadType)>, ParseError> {
|
||||
// Unsafe: we read only this data only after httparse parses headers into.
|
||||
// performance bump for pipeline benchmarks.
|
||||
let mut headers: [HeaderIndex; MAX_HEADERS] =
|
||||
unsafe { MaybeUninit::uninit().assume_init() };
|
||||
let mut headers: [HeaderIndex; MAX_HEADERS] = EMPTY_HEADER_INDEX_ARRAY;
|
||||
|
||||
let (len, method, uri, ver, h_len) = {
|
||||
let mut parsed: [httparse::Header<'_>; MAX_HEADERS] =
|
||||
unsafe { MaybeUninit::uninit().assume_init() };
|
||||
let mut parsed: [httparse::Header<'_>; MAX_HEADERS] = EMPTY_HEADER_ARRAY;
|
||||
|
||||
let mut req = httparse::Request::new(&mut parsed);
|
||||
match req.parse(src)? {
|
||||
@ -222,7 +213,7 @@ impl MessageType for Request {
|
||||
// payload decoder
|
||||
let decoder = match length {
|
||||
PayloadLength::Payload(pl) => pl,
|
||||
PayloadLength::Upgrade => {
|
||||
PayloadLength::UpgradeWebSocket => {
|
||||
// upgrade(websocket)
|
||||
PayloadType::Stream(PayloadDecoder::eof())
|
||||
}
|
||||
@ -260,16 +251,11 @@ impl MessageType for ResponseHead {
|
||||
&mut self.headers
|
||||
}
|
||||
|
||||
#[allow(clippy::uninit_assumed_init)]
|
||||
fn decode(src: &mut BytesMut) -> Result<Option<(Self, PayloadType)>, ParseError> {
|
||||
// Unsafe: we read only this data only after httparse parses headers into.
|
||||
// performance bump for pipeline benchmarks.
|
||||
let mut headers: [HeaderIndex; MAX_HEADERS] =
|
||||
unsafe { MaybeUninit::uninit().assume_init() };
|
||||
let mut headers: [HeaderIndex; MAX_HEADERS] = EMPTY_HEADER_INDEX_ARRAY;
|
||||
|
||||
let (len, ver, status, h_len) = {
|
||||
let mut parsed: [httparse::Header<'_>; MAX_HEADERS] =
|
||||
unsafe { MaybeUninit::uninit().assume_init() };
|
||||
let mut parsed: [httparse::Header<'_>; MAX_HEADERS] = EMPTY_HEADER_ARRAY;
|
||||
|
||||
let mut res = httparse::Response::new(&mut parsed);
|
||||
match res.parse(src)? {
|
||||
@ -324,6 +310,17 @@ pub(crate) struct HeaderIndex {
|
||||
pub(crate) value: (usize, usize),
|
||||
}
|
||||
|
||||
pub(crate) const EMPTY_HEADER_INDEX: HeaderIndex = HeaderIndex {
|
||||
name: (0, 0),
|
||||
value: (0, 0),
|
||||
};
|
||||
|
||||
pub(crate) const EMPTY_HEADER_INDEX_ARRAY: [HeaderIndex; MAX_HEADERS] =
|
||||
[EMPTY_HEADER_INDEX; MAX_HEADERS];
|
||||
|
||||
pub(crate) const EMPTY_HEADER_ARRAY: [httparse::Header<'static>; MAX_HEADERS] =
|
||||
[httparse::EMPTY_HEADER; MAX_HEADERS];
|
||||
|
||||
impl HeaderIndex {
|
||||
pub(crate) fn record(
|
||||
bytes: &[u8],
|
||||
@ -655,10 +652,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn is_unhandled(&self) -> bool {
|
||||
match self {
|
||||
PayloadType::Stream(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
matches!(self, PayloadType::Stream(_))
|
||||
}
|
||||
}
|
||||
|
||||
@ -670,10 +664,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
fn eof(&self) -> bool {
|
||||
match *self {
|
||||
PayloadItem::Eof => true,
|
||||
_ => false,
|
||||
}
|
||||
matches!(*self, PayloadItem::Eof)
|
||||
}
|
||||
}
|
||||
|
||||
@ -979,7 +970,7 @@ mod tests {
|
||||
unreachable!("Error");
|
||||
}
|
||||
|
||||
// type in chunked
|
||||
// intentional typo in "chunked"
|
||||
let mut buf = BytesMut::from(
|
||||
"GET /test HTTP/1.1\r\n\
|
||||
transfer-encoding: chnked\r\n\r\n",
|
||||
@ -1040,7 +1031,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_http_request_upgrade() {
|
||||
fn test_http_request_upgrade_websocket() {
|
||||
let mut buf = BytesMut::from(
|
||||
"GET /test HTTP/1.1\r\n\
|
||||
connection: upgrade\r\n\
|
||||
@ -1054,6 +1045,26 @@ mod tests {
|
||||
assert!(pl.is_unhandled());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_http_request_upgrade_h2c() {
|
||||
let mut buf = BytesMut::from(
|
||||
"GET /test HTTP/1.1\r\n\
|
||||
connection: upgrade, http2-settings\r\n\
|
||||
upgrade: h2c\r\n\
|
||||
http2-settings: dummy\r\n\r\n",
|
||||
);
|
||||
let mut reader = MessageDecoder::<Request>::default();
|
||||
let (req, pl) = reader.decode(&mut buf).unwrap().unwrap();
|
||||
// `connection: upgrade, http2-settings` doesn't work properly..
|
||||
// see MessageType::set_headers().
|
||||
//
|
||||
// The line below should be:
|
||||
// assert_eq!(req.head().connection_type(), ConnectionType::Upgrade);
|
||||
assert_eq!(req.head().connection_type(), ConnectionType::KeepAlive);
|
||||
assert!(req.upgrade());
|
||||
assert!(!pl.is_unhandled());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_http_request_parser_utf8() {
|
||||
let mut buf = BytesMut::from(
|
||||
|
@ -132,19 +132,11 @@ where
|
||||
B: MessageBody,
|
||||
{
|
||||
fn is_empty(&self) -> bool {
|
||||
if let State::None = self {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
matches!(self, State::None)
|
||||
}
|
||||
|
||||
fn is_call(&self) -> bool {
|
||||
if let State::ServiceCall(_) = self {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
matches!(self, State::ServiceCall(_))
|
||||
}
|
||||
}
|
||||
enum PollResponse {
|
||||
@ -156,14 +148,8 @@ enum PollResponse {
|
||||
impl PartialEq for PollResponse {
|
||||
fn eq(&self, other: &PollResponse) -> bool {
|
||||
match self {
|
||||
PollResponse::DrainWriteBuf => match other {
|
||||
PollResponse::DrainWriteBuf => true,
|
||||
_ => false,
|
||||
},
|
||||
PollResponse::DoNothing => match other {
|
||||
PollResponse::DoNothing => true,
|
||||
_ => false,
|
||||
},
|
||||
PollResponse::DrainWriteBuf => matches!(other, PollResponse::DrainWriteBuf),
|
||||
PollResponse::DoNothing => matches!(other, PollResponse::DoNothing),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
@ -303,7 +303,8 @@ where
|
||||
}
|
||||
}
|
||||
},
|
||||
ServiceResponseStateProj::SendPayload(ref mut stream, ref mut body) => loop {
|
||||
ServiceResponseStateProj::SendPayload(ref mut stream, ref mut body) => {
|
||||
loop {
|
||||
loop {
|
||||
if let Some(ref mut buffer) = this.buffer {
|
||||
match stream.poll_capacity(cx) {
|
||||
@ -317,7 +318,8 @@ where
|
||||
warn!("{:?}", e);
|
||||
return Poll::Ready(());
|
||||
} else if !buffer.is_empty() {
|
||||
let cap = std::cmp::min(buffer.len(), CHUNK_SIZE);
|
||||
let cap =
|
||||
std::cmp::min(buffer.len(), CHUNK_SIZE);
|
||||
stream.reserve_capacity(cap);
|
||||
} else {
|
||||
this.buffer.take();
|
||||
@ -332,7 +334,8 @@ where
|
||||
match body.as_mut().poll_next(cx) {
|
||||
Poll::Pending => return Poll::Pending,
|
||||
Poll::Ready(None) => {
|
||||
if let Err(e) = stream.send_data(Bytes::new(), true) {
|
||||
if let Err(e) = stream.send_data(Bytes::new(), true)
|
||||
{
|
||||
warn!("{:?}", e);
|
||||
}
|
||||
return Poll::Ready(());
|
||||
@ -351,7 +354,8 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use http::Method;
|
||||
use http::header;
|
||||
use http::Method;
|
||||
|
||||
header! {
|
||||
/// `Allow` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.4.1)
|
||||
|
@ -387,26 +387,17 @@ impl ContentDisposition {
|
||||
|
||||
/// Returns `true` if it is [`Inline`](DispositionType::Inline).
|
||||
pub fn is_inline(&self) -> bool {
|
||||
match self.disposition {
|
||||
DispositionType::Inline => true,
|
||||
_ => false,
|
||||
}
|
||||
matches!(self.disposition, DispositionType::Inline)
|
||||
}
|
||||
|
||||
/// Returns `true` if it is [`Attachment`](DispositionType::Attachment).
|
||||
pub fn is_attachment(&self) -> bool {
|
||||
match self.disposition {
|
||||
DispositionType::Attachment => true,
|
||||
_ => false,
|
||||
}
|
||||
matches!(self.disposition, DispositionType::Attachment)
|
||||
}
|
||||
|
||||
/// Returns `true` if it is [`FormData`](DispositionType::FormData).
|
||||
pub fn is_form_data(&self) -> bool {
|
||||
match self.disposition {
|
||||
DispositionType::FormData => true,
|
||||
_ => false,
|
||||
}
|
||||
matches!(self.disposition, DispositionType::FormData)
|
||||
}
|
||||
|
||||
/// Returns `true` if it is [`Ext`](DispositionType::Ext) and the `disp_type` matches.
|
||||
|
@ -9,11 +9,13 @@
|
||||
|
||||
pub use self::accept_charset::AcceptCharset;
|
||||
//pub use self::accept_encoding::AcceptEncoding;
|
||||
pub use self::accept_language::AcceptLanguage;
|
||||
pub use self::accept::Accept;
|
||||
pub use self::accept_language::AcceptLanguage;
|
||||
pub use self::allow::Allow;
|
||||
pub use self::cache_control::{CacheControl, CacheDirective};
|
||||
pub use self::content_disposition::{ContentDisposition, DispositionType, DispositionParam};
|
||||
pub use self::content_disposition::{
|
||||
ContentDisposition, DispositionParam, DispositionType,
|
||||
};
|
||||
pub use self::content_language::ContentLanguage;
|
||||
pub use self::content_range::{ContentRange, ContentRangeSpec};
|
||||
pub use self::content_type::ContentType;
|
||||
@ -47,7 +49,7 @@ macro_rules! __hyper__deref {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
@ -74,8 +76,8 @@ macro_rules! test_header {
|
||||
($id:ident, $raw:expr) => {
|
||||
#[test]
|
||||
fn $id() {
|
||||
use $crate::test;
|
||||
use super::*;
|
||||
use $crate::test;
|
||||
|
||||
let raw = $raw;
|
||||
let a: Vec<Vec<u8>> = raw.iter().map(|x| x.to_vec()).collect();
|
||||
@ -118,7 +120,7 @@ macro_rules! test_header {
|
||||
// Test formatting
|
||||
if typed.is_some() {
|
||||
let raw = &($raw)[..];
|
||||
let mut iter = raw.iter().map(|b|str::from_utf8(&b[..]).unwrap());
|
||||
let mut iter = raw.iter().map(|b| str::from_utf8(&b[..]).unwrap());
|
||||
let mut joined = String::new();
|
||||
joined.push_str(iter.next().unwrap());
|
||||
for s in iter {
|
||||
@ -128,7 +130,7 @@ macro_rules! test_header {
|
||||
assert_eq!(format!("{}", typed.unwrap()), joined);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
@ -330,11 +332,10 @@ macro_rules! header {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
mod accept_charset;
|
||||
//mod accept_encoding;
|
||||
mod accept_language;
|
||||
mod accept;
|
||||
mod accept_language;
|
||||
mod allow;
|
||||
mod cache_control;
|
||||
mod content_disposition;
|
||||
|
@ -148,10 +148,7 @@ impl ContentEncoding {
|
||||
#[inline]
|
||||
/// Is the content compressed?
|
||||
pub fn is_compression(self) -> bool {
|
||||
match self {
|
||||
ContentEncoding::Identity | ContentEncoding::Auto => false,
|
||||
_ => true,
|
||||
}
|
||||
matches!(self, ContentEncoding::Identity | ContentEncoding::Auto)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -167,7 +167,6 @@ where
|
||||
mod tests {
|
||||
use bytes::Bytes;
|
||||
use encoding_rs::ISO_8859_2;
|
||||
use mime;
|
||||
|
||||
use super::*;
|
||||
use crate::test::TestRequest;
|
||||
|
@ -229,10 +229,7 @@ mod tests {
|
||||
fn is_none(
|
||||
frm: &Result<Option<(bool, OpCode, Option<BytesMut>)>, ProtocolError>,
|
||||
) -> bool {
|
||||
match *frm {
|
||||
Ok(None) => true,
|
||||
_ => false,
|
||||
}
|
||||
matches!(*frm, Ok(None))
|
||||
}
|
||||
|
||||
fn extract(
|
||||
|
@ -139,7 +139,7 @@ mod tests {
|
||||
let mut masked = unmasked.clone();
|
||||
apply_mask_fallback(&mut masked[1..], &mask);
|
||||
|
||||
let mut masked_fast = unmasked.clone();
|
||||
let mut masked_fast = unmasked;
|
||||
apply_mask(&mut masked_fast[1..], mask_u32);
|
||||
|
||||
assert_eq!(masked, masked_fast);
|
||||
|
@ -274,9 +274,7 @@ async fn test_h2_head_empty() {
|
||||
async fn test_h2_head_binary() {
|
||||
let mut srv = test_server(move || {
|
||||
HttpService::build()
|
||||
.h2(|_| {
|
||||
ok::<_, ()>(Response::Ok().body(STR))
|
||||
})
|
||||
.h2(|_| ok::<_, ()>(Response::Ok().body(STR)))
|
||||
.openssl(ssl_acceptor())
|
||||
.map_err(|_| ())
|
||||
})
|
||||
|
@ -280,9 +280,7 @@ async fn test_h2_head_empty() {
|
||||
async fn test_h2_head_binary() {
|
||||
let mut srv = test_server(move || {
|
||||
HttpService::build()
|
||||
.h2(|_| {
|
||||
ok::<_, ()>(Response::Ok().body(STR))
|
||||
})
|
||||
.h2(|_| ok::<_, ()>(Response::Ok().body(STR)))
|
||||
.rustls(ssl_acceptor())
|
||||
})
|
||||
.await;
|
||||
|
@ -489,9 +489,7 @@ async fn test_h1_head_empty() {
|
||||
async fn test_h1_head_binary() {
|
||||
let mut srv = test_server(|| {
|
||||
HttpService::build()
|
||||
.h1(|_| {
|
||||
ok::<_, ()>(Response::Ok().body(STR))
|
||||
})
|
||||
.h1(|_| ok::<_, ()>(Response::Ok().body(STR)))
|
||||
.tcp()
|
||||
})
|
||||
.await;
|
||||
|
@ -1,15 +1,17 @@
|
||||
# Changes
|
||||
|
||||
## [0.3.0-alpha.1] - 2020-05-25
|
||||
## Unreleased - 2020-xx-xx
|
||||
|
||||
|
||||
## 0.3.0-beta.1 - 2020-07-15
|
||||
* Update `actix-web` to 3.0.0-beta.1
|
||||
|
||||
|
||||
## 0.3.0-alpha.1 - 2020-05-25
|
||||
* Update `actix-web` to 3.0.0-alpha.3
|
||||
|
||||
* Bump minimum supported Rust version to 1.40
|
||||
|
||||
* Minimize `futures` dependencies
|
||||
|
||||
* Remove the unused `time` dependency
|
||||
|
||||
* Fix missing `std::error::Error` implement for `MultipartError`.
|
||||
|
||||
## [0.2.0] - 2019-12-20
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-multipart"
|
||||
version = "0.3.0-alpha.1"
|
||||
version = "0.3.0-beta.1"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Multipart support for actix web framework."
|
||||
readme = "README.md"
|
||||
@ -8,7 +8,7 @@ keywords = ["http", "web", "framework", "async", "futures"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-web.git"
|
||||
documentation = "https://docs.rs/actix-multipart/"
|
||||
license = "MIT/Apache-2.0"
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
@ -16,7 +16,7 @@ name = "actix_multipart"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
actix-web = { version = "3.0.0-alpha.3", default-features = false }
|
||||
actix-web = { version = "3.0.0-beta.1", default-features = false }
|
||||
actix-service = "1.0.1"
|
||||
actix-utils = "1.0.3"
|
||||
bytes = "0.5.3"
|
||||
@ -29,4 +29,4 @@ twoway = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "1.0.0"
|
||||
actix-http = "2.0.0-alpha.4"
|
||||
actix-http = "2.0.0-beta.3"
|
||||
|
@ -9,8 +9,6 @@ use std::{cmp, fmt};
|
||||
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures_util::stream::{LocalBoxStream, Stream, StreamExt};
|
||||
use httparse;
|
||||
use mime;
|
||||
|
||||
use actix_utils::task::LocalWaker;
|
||||
use actix_web::error::{ParseError, PayloadError};
|
||||
@ -876,11 +874,11 @@ mod tests {
|
||||
|
||||
impl SlowStream {
|
||||
fn new(bytes: Bytes) -> SlowStream {
|
||||
return SlowStream {
|
||||
bytes: bytes,
|
||||
SlowStream {
|
||||
bytes,
|
||||
pos: 0,
|
||||
ready: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,10 +2,13 @@
|
||||
|
||||
## [Unreleased] - 2020-xx-xx
|
||||
|
||||
|
||||
## [3.0.0-beta.1] - 2020-xx-xx
|
||||
* Update `actix-web` & `actix-http` dependencies to beta.1
|
||||
* Bump minimum supported Rust version to 1.40
|
||||
|
||||
## [3.0.0-alpha.1] - 2020-05-08
|
||||
|
||||
## [3.0.0-alpha.1] - 2020-05-08
|
||||
* Update the actix-web dependency to 3.0.0-alpha.1
|
||||
* Update the actix dependency to 0.10.0-alpha.2
|
||||
* Update the actix-http dependency to 2.0.0-alpha.3
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-web-actors"
|
||||
version = "3.0.0-alpha.1"
|
||||
version = "3.0.0-beta.1"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix actors support for actix web framework."
|
||||
readme = "README.md"
|
||||
@ -8,7 +8,7 @@ keywords = ["actix", "http", "web", "framework", "async"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-web.git"
|
||||
documentation = "https://docs.rs/actix-web-actors/"
|
||||
license = "MIT/Apache-2.0"
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
@ -17,8 +17,8 @@ path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
actix = "0.10.0-alpha.2"
|
||||
actix-web = { version = "3.0.0-alpha.3", default-features = false }
|
||||
actix-http = "2.0.0-alpha.4"
|
||||
actix-web = { version = "3.0.0-beta.1", default-features = false }
|
||||
actix-http = "2.0.0-beta.3"
|
||||
actix-codec = "0.2.0"
|
||||
bytes = "0.5.2"
|
||||
futures-channel = { version = "0.3.5", default-features = false }
|
||||
|
@ -30,8 +30,8 @@ impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for Ws {
|
||||
async fn test_simple() {
|
||||
let mut srv = test::start(|| {
|
||||
App::new().service(web::resource("/").to(
|
||||
|req: HttpRequest, stream: web::Payload| {
|
||||
async move { ws::start(Ws, &req, stream) }
|
||||
|req: HttpRequest, stream: web::Payload| async move {
|
||||
ws::start(Ws, &req, stream)
|
||||
},
|
||||
))
|
||||
});
|
||||
@ -51,7 +51,7 @@ async fn test_simple() {
|
||||
.await
|
||||
.unwrap();
|
||||
let item = framed.next().await.unwrap().unwrap();
|
||||
assert_eq!(item, ws::Frame::Binary(Bytes::from_static(b"text").into()));
|
||||
assert_eq!(item, ws::Frame::Binary(Bytes::from_static(b"text")));
|
||||
|
||||
framed.send(ws::Message::Ping("text".into())).await.unwrap();
|
||||
let item = framed.next().await.unwrap().unwrap();
|
||||
|
@ -1,18 +1,20 @@
|
||||
# Changes
|
||||
|
||||
## [Unreleased] - XXXX-XX-XX
|
||||
## Unreleased - 2020-xx-xx
|
||||
|
||||
* Add main entry-point macro that uses re-exported runtime.
|
||||
|
||||
## 0.3.0-beta.1 - 2020-07-14
|
||||
* Add main entry-point macro that uses re-exported runtime. [#1559]
|
||||
|
||||
[#1559]: https://github.com/actix/actix-web/pull/1559
|
||||
|
||||
|
||||
## [0.2.2] - 2020-05-23
|
||||
|
||||
* Add resource middleware on actix-web-codegen [#1467]
|
||||
|
||||
[#1467]: https://github.com/actix/actix-web/pull/1467
|
||||
|
||||
## [0.2.1] - 2020-02-25
|
||||
|
||||
* Add `#[allow(missing_docs)]` attribute to generated structs [#1368]
|
||||
* Allow the handler function to be named as `config` [#1290]
|
||||
|
||||
@ -26,7 +28,6 @@
|
||||
## [0.1.3] - 2019-10-14
|
||||
|
||||
* Bump up `syn` & `quote` to 1.0
|
||||
|
||||
* Provide better error message
|
||||
|
||||
## [0.1.2] - 2019-06-04
|
||||
|
@ -1,13 +1,13 @@
|
||||
[package]
|
||||
name = "actix-web-codegen"
|
||||
version = "0.2.2"
|
||||
version = "0.3.0-beta.1"
|
||||
description = "Actix web proc macros"
|
||||
readme = "README.md"
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-web"
|
||||
documentation = "https://docs.rs/actix-web-codegen"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
license = "MIT/Apache-2.0"
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
@ -20,5 +20,5 @@ proc-macro2 = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "1.0.0"
|
||||
actix-web = "3.0.0-alpha.3"
|
||||
actix-web = "3.0.0-beta.1"
|
||||
futures-util = { version = "0.3.5", default-features = false }
|
||||
|
@ -3,7 +3,7 @@ extern crate proc_macro;
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||
use quote::{format_ident, quote, ToTokens, TokenStreamExt};
|
||||
use syn::{AttributeArgs, Ident, NestedMeta, parse_macro_input};
|
||||
use syn::{parse_macro_input, AttributeArgs, Ident, NestedMeta};
|
||||
|
||||
enum ResourceType {
|
||||
Async,
|
||||
@ -196,7 +196,12 @@ impl ToTokens for Route {
|
||||
name,
|
||||
guard,
|
||||
ast,
|
||||
args: Args { path, guards, wrappers },
|
||||
args:
|
||||
Args {
|
||||
path,
|
||||
guards,
|
||||
wrappers,
|
||||
},
|
||||
resource_type,
|
||||
} = self;
|
||||
let resource_name = name.to_string();
|
||||
|
@ -2,11 +2,11 @@ use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use actix_web::{http, test, web::Path, App, HttpResponse, Responder, Error};
|
||||
use actix_web::dev::{Service, Transform, ServiceRequest, ServiceResponse};
|
||||
use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform};
|
||||
use actix_web::http::header::{HeaderName, HeaderValue};
|
||||
use actix_web::{http, test, web::Path, App, Error, HttpResponse, Responder};
|
||||
use actix_web_codegen::{connect, delete, get, head, options, patch, post, put, trace};
|
||||
use futures_util::future;
|
||||
use actix_web::http::header::{HeaderName, HeaderValue};
|
||||
|
||||
// Make sure that we can name function as 'config'
|
||||
#[get("/config")]
|
||||
@ -112,6 +112,7 @@ where
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse<B>;
|
||||
type Error = Error;
|
||||
#[allow(clippy::type_complexity)]
|
||||
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
|
||||
|
||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
@ -119,7 +120,6 @@ where
|
||||
}
|
||||
|
||||
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
||||
|
||||
let fut = self.service.call(req);
|
||||
|
||||
Box::pin(async move {
|
||||
@ -223,10 +223,7 @@ async fn test_auto_async() {
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_wrap() {
|
||||
let srv = test::start(|| {
|
||||
App::new()
|
||||
.service(get_wrap)
|
||||
});
|
||||
let srv = test::start(|| App::new().service(get_wrap));
|
||||
|
||||
let request = srv.request(http::Method::GET, srv.url("/test/wrap"));
|
||||
let response = request.send().await.unwrap();
|
||||
|
@ -1,5 +1,17 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2020-xx-xx
|
||||
|
||||
|
||||
## 2.0.0-beta.2 - 2020-07-21
|
||||
### Changed
|
||||
* Update `actix-http` dependency to 2.0.0-beta.2
|
||||
|
||||
|
||||
## [2.0.0-beta.1] - 2020-07-14
|
||||
### Changed
|
||||
* Update `actix-http` dependency to 2.0.0-beta.1
|
||||
|
||||
## [2.0.0-alpha.2] - 2020-05-21
|
||||
|
||||
### Changed
|
||||
|
@ -1,17 +1,20 @@
|
||||
[package]
|
||||
name = "awc"
|
||||
version = "2.0.0-alpha.2"
|
||||
version = "2.0.0-beta.2"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix http client."
|
||||
description = "Async HTTP client library that uses the Actix runtime."
|
||||
readme = "README.md"
|
||||
keywords = ["actix", "http", "framework", "async", "web"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-web.git"
|
||||
documentation = "https://docs.rs/awc/"
|
||||
categories = ["network-programming", "asynchronous",
|
||||
categories = [
|
||||
"network-programming",
|
||||
"asynchronous",
|
||||
"web-programming::http-client",
|
||||
"web-programming::websocket"]
|
||||
license = "MIT/Apache-2.0"
|
||||
"web-programming::websocket",
|
||||
]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
@ -36,7 +39,7 @@ compress = ["actix-http/compress"]
|
||||
[dependencies]
|
||||
actix-codec = "0.2.0"
|
||||
actix-service = "1.0.1"
|
||||
actix-http = "2.0.0-alpha.4"
|
||||
actix-http = "2.0.0-beta.3"
|
||||
actix-rt = "1.0.0"
|
||||
|
||||
base64 = "0.12"
|
||||
@ -56,7 +59,7 @@ rust-tls = { version = "0.17.0", package = "rustls", optional = true, features =
|
||||
[dev-dependencies]
|
||||
actix-connect = { version = "2.0.0-alpha.2", features = ["openssl"] }
|
||||
actix-web = { version = "3.0.0-alpha.3", features = ["openssl"] }
|
||||
actix-http = { version = "2.0.0-alpha.4", features = ["openssl"] }
|
||||
actix-http = { version = "2.0.0-beta.3", features = ["openssl"] }
|
||||
actix-http-test = { version = "2.0.0-alpha.1", features = ["openssl"] }
|
||||
actix-utils = "1.0.3"
|
||||
actix-server = "1.0.0"
|
||||
|
@ -402,14 +402,12 @@ mod tests {
|
||||
|
||||
fn json_eq(err: JsonPayloadError, other: JsonPayloadError) -> bool {
|
||||
match err {
|
||||
JsonPayloadError::Payload(PayloadError::Overflow) => match other {
|
||||
JsonPayloadError::Payload(PayloadError::Overflow) => true,
|
||||
_ => false,
|
||||
},
|
||||
JsonPayloadError::ContentType => match other {
|
||||
JsonPayloadError::ContentType => true,
|
||||
_ => false,
|
||||
},
|
||||
JsonPayloadError::Payload(PayloadError::Overflow) => {
|
||||
matches!(other, JsonPayloadError::Payload(PayloadError::Overflow))
|
||||
}
|
||||
JsonPayloadError::ContentType => {
|
||||
matches!(other, JsonPayloadError::ContentType)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
@ -167,8 +167,7 @@ async fn test_connection_reuse() {
|
||||
})
|
||||
.and_then(
|
||||
HttpService::new(map_config(
|
||||
App::new()
|
||||
.service(web::resource("/").route(web::to(|| HttpResponse::Ok()))),
|
||||
App::new().service(web::resource("/").route(web::to(HttpResponse::Ok))),
|
||||
|_| AppConfig::default(),
|
||||
))
|
||||
.tcp(),
|
||||
@ -205,8 +204,7 @@ async fn test_connection_force_close() {
|
||||
})
|
||||
.and_then(
|
||||
HttpService::new(map_config(
|
||||
App::new()
|
||||
.service(web::resource("/").route(web::to(|| HttpResponse::Ok()))),
|
||||
App::new().service(web::resource("/").route(web::to(HttpResponse::Ok))),
|
||||
|_| AppConfig::default(),
|
||||
))
|
||||
.tcp(),
|
||||
|
@ -32,8 +32,7 @@ async fn test_connection_window_size() {
|
||||
let srv = test_server(move || {
|
||||
HttpService::build()
|
||||
.h2(map_config(
|
||||
App::new()
|
||||
.service(web::resource("/").route(web::to(|| HttpResponse::Ok()))),
|
||||
App::new().service(web::resource("/").route(web::to(HttpResponse::Ok))),
|
||||
|_| AppConfig::default(),
|
||||
))
|
||||
.openssl(ssl_acceptor())
|
||||
|
@ -64,9 +64,8 @@ async fn _test_connection_reuse_h2() {
|
||||
.and_then(
|
||||
HttpService::build()
|
||||
.h2(map_config(
|
||||
App::new().service(
|
||||
web::resource("/").route(web::to(|| HttpResponse::Ok())),
|
||||
),
|
||||
App::new()
|
||||
.service(web::resource("/").route(web::to(HttpResponse::Ok))),
|
||||
|_| AppConfig::default(),
|
||||
))
|
||||
.openssl(ssl_acceptor())
|
||||
|
@ -45,9 +45,8 @@ async fn test_connection_reuse_h2() {
|
||||
.and_then(
|
||||
HttpService::build()
|
||||
.h2(map_config(
|
||||
App::new().service(
|
||||
web::resource("/").route(web::to(|| HttpResponse::Ok())),
|
||||
),
|
||||
App::new()
|
||||
.service(web::resource("/").route(web::to(HttpResponse::Ok))),
|
||||
|_| AppConfig::default(),
|
||||
))
|
||||
.openssl(ssl_acceptor())
|
||||
|
2
docs/graphs/.gitignore
vendored
Normal file
2
docs/graphs/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# do not track rendered graphs
|
||||
*.png
|
11
docs/graphs/dependency-graphs.md
Normal file
11
docs/graphs/dependency-graphs.md
Normal file
@ -0,0 +1,11 @@
|
||||
# Actix Ecosystem Dependency Graphs
|
||||
|
||||
See rendered versions of these dot graphs [on the wiki](https://github.com/actix/actix-web/wiki/Dependency-Graph).
|
||||
|
||||
## Rendering
|
||||
|
||||
Dot graphs were rendered using the `dot` command from [GraphViz](https://www.graphviz.org/doc/info/command.html):
|
||||
|
||||
```sh
|
||||
for f in $(ls docs/graphs/*.dot | xargs); do dot $f -Tpng -o${f:r}.png; done
|
||||
```
|
25
docs/graphs/net-only.dot
Normal file
25
docs/graphs/net-only.dot
Normal file
@ -0,0 +1,25 @@
|
||||
digraph {
|
||||
subgraph cluster_net {
|
||||
label="actix/actix-net";
|
||||
"actix-codec"
|
||||
"actix-connect"
|
||||
"actix-macros"
|
||||
"actix-rt"
|
||||
"actix-server"
|
||||
"actix-service"
|
||||
"actix-testing"
|
||||
"actix-threadpool"
|
||||
"actix-tls"
|
||||
"actix-tracing"
|
||||
"actix-utils"
|
||||
"actix-router"
|
||||
}
|
||||
|
||||
"actix-utils" -> { "actix-service" "actix-rt" "actix-codec" }
|
||||
"actix-tracing" -> { "actix-service" }
|
||||
"actix-tls" -> { "actix-service" "actix-codec" "actix-utils" "actix-rt" }
|
||||
"actix-testing" -> { "actix-rt" "actix-macros" "actix-server" "actix-service" }
|
||||
"actix-server" -> { "actix-service" "actix-rt" "actix-codec" "actix-utils" }
|
||||
"actix-rt" -> { "actix-macros" "actix-threadpool" }
|
||||
"actix-connect" -> { "actix-service" "actix-codec" "actix-utils" "actix-rt" }
|
||||
}
|
30
docs/graphs/web-focus.dot
Normal file
30
docs/graphs/web-focus.dot
Normal file
@ -0,0 +1,30 @@
|
||||
digraph {
|
||||
subgraph cluster_web {
|
||||
label="actix/actix-web"
|
||||
"awc"
|
||||
"actix-web"
|
||||
"actix-files"
|
||||
"actix-http"
|
||||
"actix-multipart"
|
||||
"actix-web-actors"
|
||||
"actix-web-codegen"
|
||||
}
|
||||
|
||||
"actix-web" -> { "actix-codec" "actix-service" "actix-utils" "actix-router" "actix-rt" "actix-server" "actix-testing" "actix-macros" "actix-threadpool" "actix-tls" "actix-web-codegen" "actix-http" "awc" }
|
||||
"awc" -> { "actix-codec" "actix-service" "actix-http" "actix-rt" }
|
||||
"actix-web-actors" -> { "actix" "actix-web" "actix-http" "actix-codec" }
|
||||
"actix-multipart" -> { "actix-web" "actix-service" "actix-utils" }
|
||||
"actix-http" -> { "actix-service" "actix-codec" "actix-connect" "actix-utils" "actix-rt" "actix-threadpool" }
|
||||
"actix-http" -> { "actix" "actix-tls" }[color=blue] // optional
|
||||
"actix-files" -> { "actix-web" "actix-http" }
|
||||
|
||||
// net
|
||||
|
||||
"actix-utils" -> { "actix-service" "actix-rt" "actix-codec" }
|
||||
"actix-tracing" -> { "actix-service" }
|
||||
"actix-tls" -> { "actix-service" "actix-codec" "actix-utils" "actix-rt" }
|
||||
"actix-testing" -> { "actix-rt" "actix-macros" "actix-server" "actix-service" }
|
||||
"actix-server" -> { "actix-service" "actix-rt" "actix-codec" "actix-utils" }
|
||||
"actix-rt" -> { "actix-macros" "actix-threadpool" }
|
||||
"actix-connect" -> { "actix-service" "actix-codec" "actix-utils" "actix-rt" }
|
||||
}
|
19
docs/graphs/web-only.dot
Normal file
19
docs/graphs/web-only.dot
Normal file
@ -0,0 +1,19 @@
|
||||
digraph {
|
||||
subgraph cluster_web {
|
||||
label="actix/actix-web"
|
||||
"awc"
|
||||
"actix-web"
|
||||
"actix-files"
|
||||
"actix-http"
|
||||
"actix-multipart"
|
||||
"actix-web-actors"
|
||||
"actix-web-codegen"
|
||||
}
|
||||
|
||||
"actix-web" -> { "actix-web-codegen" "actix-http" "awc" }
|
||||
"awc" -> { "actix-http" }
|
||||
"actix-web-actors" -> { "actix" "actix-web" "actix-http" }
|
||||
"actix-multipart" -> { "actix-web" }
|
||||
"actix-http" -> { "actix" }[color=blue] // optional
|
||||
"actix-files" -> { "actix-web" "actix-http" }
|
||||
}
|
@ -1 +1 @@
|
||||
1.41.1
|
||||
1.42.0
|
||||
|
21
src/app.rs
21
src/app.rs
@ -489,7 +489,7 @@ mod tests {
|
||||
#[actix_rt::test]
|
||||
async fn test_default_resource() {
|
||||
let mut srv = init_service(
|
||||
App::new().service(web::resource("/test").to(|| HttpResponse::Ok())),
|
||||
App::new().service(web::resource("/test").to(HttpResponse::Ok)),
|
||||
)
|
||||
.await;
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
@ -502,13 +502,13 @@ mod tests {
|
||||
|
||||
let mut srv = init_service(
|
||||
App::new()
|
||||
.service(web::resource("/test").to(|| HttpResponse::Ok()))
|
||||
.service(web::resource("/test").to(HttpResponse::Ok))
|
||||
.service(
|
||||
web::resource("/test2")
|
||||
.default_service(|r: ServiceRequest| {
|
||||
ok(r.into_response(HttpResponse::Created()))
|
||||
})
|
||||
.route(web::get().to(|| HttpResponse::Ok())),
|
||||
.route(web::get().to(HttpResponse::Ok)),
|
||||
)
|
||||
.default_service(|r: ServiceRequest| {
|
||||
ok(r.into_response(HttpResponse::MethodNotAllowed()))
|
||||
@ -585,7 +585,7 @@ mod tests {
|
||||
DefaultHeaders::new()
|
||||
.header(header::CONTENT_TYPE, HeaderValue::from_static("0001")),
|
||||
)
|
||||
.route("/test", web::get().to(|| HttpResponse::Ok())),
|
||||
.route("/test", web::get().to(HttpResponse::Ok)),
|
||||
)
|
||||
.await;
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
@ -601,7 +601,7 @@ mod tests {
|
||||
async fn test_router_wrap() {
|
||||
let mut srv = init_service(
|
||||
App::new()
|
||||
.route("/test", web::get().to(|| HttpResponse::Ok()))
|
||||
.route("/test", web::get().to(HttpResponse::Ok))
|
||||
.wrap(
|
||||
DefaultHeaders::new()
|
||||
.header(header::CONTENT_TYPE, HeaderValue::from_static("0001")),
|
||||
@ -632,7 +632,7 @@ mod tests {
|
||||
Ok(res)
|
||||
}
|
||||
})
|
||||
.service(web::resource("/test").to(|| HttpResponse::Ok())),
|
||||
.service(web::resource("/test").to(HttpResponse::Ok)),
|
||||
)
|
||||
.await;
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
@ -648,7 +648,7 @@ mod tests {
|
||||
async fn test_router_wrap_fn() {
|
||||
let mut srv = init_service(
|
||||
App::new()
|
||||
.route("/test", web::get().to(|| HttpResponse::Ok()))
|
||||
.route("/test", web::get().to(HttpResponse::Ok))
|
||||
.wrap_fn(|req, srv| {
|
||||
let fut = srv.call(req);
|
||||
async {
|
||||
@ -679,10 +679,9 @@ mod tests {
|
||||
.route(
|
||||
"/test",
|
||||
web::get().to(|req: HttpRequest| {
|
||||
HttpResponse::Ok().body(format!(
|
||||
"{}",
|
||||
req.url_for("youtube", &["12345"]).unwrap()
|
||||
))
|
||||
HttpResponse::Ok().body(
|
||||
req.url_for("youtube", &["12345"]).unwrap().to_string(),
|
||||
)
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
@ -10,6 +10,7 @@ use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url};
|
||||
use actix_service::boxed::{self, BoxService, BoxServiceFactory};
|
||||
use actix_service::{fn_service, Service, ServiceFactory};
|
||||
use futures_util::future::{join_all, ok, FutureExt, LocalBoxFuture};
|
||||
use tinyvec::tiny_vec;
|
||||
|
||||
use crate::config::{AppConfig, AppService};
|
||||
use crate::data::{DataFactory, FnDataFactory};
|
||||
@ -245,7 +246,7 @@ where
|
||||
inner.path.reset();
|
||||
inner.head = head;
|
||||
inner.payload = payload;
|
||||
inner.app_data.push(self.data.clone());
|
||||
inner.app_data = tiny_vec![self.data.clone()];
|
||||
req
|
||||
} else {
|
||||
HttpRequest::new(
|
||||
@ -474,7 +475,7 @@ mod tests {
|
||||
let mut app = init_service(
|
||||
App::new()
|
||||
.data(DropData(data.clone()))
|
||||
.service(web::resource("/test").to(|| HttpResponse::Ok())),
|
||||
.service(web::resource("/test").to(HttpResponse::Ok)),
|
||||
)
|
||||
.await;
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
|
@ -311,10 +311,9 @@ mod tests {
|
||||
.route(
|
||||
"/test",
|
||||
web::get().to(|req: HttpRequest| {
|
||||
HttpResponse::Ok().body(format!(
|
||||
"{}",
|
||||
req.url_for("youtube", &["12345"]).unwrap()
|
||||
))
|
||||
HttpResponse::Ok().body(
|
||||
req.url_for("youtube", &["12345"]).unwrap().to_string(),
|
||||
)
|
||||
}),
|
||||
),
|
||||
)
|
||||
@ -330,9 +329,9 @@ mod tests {
|
||||
async fn test_service() {
|
||||
let mut srv = init_service(App::new().configure(|cfg| {
|
||||
cfg.service(
|
||||
web::resource("/test").route(web::get().to(|| HttpResponse::Created())),
|
||||
web::resource("/test").route(web::get().to(HttpResponse::Created)),
|
||||
)
|
||||
.route("/index.html", web::get().to(|| HttpResponse::Ok()));
|
||||
.route("/index.html", web::get().to(HttpResponse::Ok));
|
||||
}))
|
||||
.await;
|
||||
|
||||
|
15
src/data.rs
15
src/data.rs
@ -200,13 +200,13 @@ mod tests {
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_route_data_extractor() {
|
||||
let mut srv =
|
||||
init_service(App::new().service(web::resource("/").data(10usize).route(
|
||||
web::get().to(|data: web::Data<usize>| {
|
||||
let _ = data.clone();
|
||||
HttpResponse::Ok()
|
||||
}),
|
||||
)))
|
||||
let mut srv = init_service(
|
||||
App::new().service(
|
||||
web::resource("/")
|
||||
.data(10usize)
|
||||
.route(web::get().to(|_data: web::Data<usize>| HttpResponse::Ok())),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
let req = TestRequest::default().to_request();
|
||||
@ -233,7 +233,6 @@ mod tests {
|
||||
web::resource("/").data(10usize).route(web::get().to(
|
||||
|data: web::Data<usize>| {
|
||||
assert_eq!(**data, 10);
|
||||
let _ = data.clone();
|
||||
HttpResponse::Ok()
|
||||
},
|
||||
)),
|
||||
|
@ -9,8 +9,8 @@
|
||||
//! use actix_web::{get, web, App, HttpServer, Responder};
|
||||
//!
|
||||
//! #[get("/{id}/{name}/index.html")]
|
||||
//! async fn index(info: web::Path<(u32, String)>) -> impl Responder {
|
||||
//! format!("Hello {}! id:{}", info.1, info.0)
|
||||
//! async fn index(web::Path((id, name)): web::Path<(u32, String)>) -> impl Responder {
|
||||
//! format!("Hello {}! id:{}", name, id)
|
||||
//! }
|
||||
//!
|
||||
//! #[actix_web::main]
|
||||
@ -213,9 +213,7 @@ pub mod client {
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
pub use awc::error::{
|
||||
ConnectError, InvalidUrl, PayloadError, SendRequestError, WsClientError,
|
||||
};
|
||||
pub use awc::error::*;
|
||||
pub use awc::{
|
||||
test, Client, ClientBuilder, ClientRequest, ClientResponse, Connector,
|
||||
};
|
||||
|
@ -626,7 +626,7 @@ mod tests {
|
||||
Ok(())
|
||||
};
|
||||
let s = format!("{}", FormatDisplay(&render));
|
||||
assert!(s.contains(&format!("{}", now.format("%Y-%m-%dT%H:%M:%S"))));
|
||||
assert!(s.contains(&now.format("%Y-%m-%dT%H:%M:%S")));
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
|
@ -129,7 +129,7 @@ mod tests {
|
||||
let mut app = init_service(
|
||||
App::new()
|
||||
.wrap(NormalizePath::default())
|
||||
.service(web::resource("/v1/something/").to(|| HttpResponse::Ok())),
|
||||
.service(web::resource("/v1/something/").to(HttpResponse::Ok)),
|
||||
)
|
||||
.await;
|
||||
|
||||
|
@ -276,6 +276,7 @@ impl HttpMessage for HttpRequest {
|
||||
|
||||
impl Drop for HttpRequest {
|
||||
fn drop(&mut self) {
|
||||
// if possible, contribute to current worker's HttpRequest allocation pool
|
||||
if Rc::strong_count(&self.0) == 1 {
|
||||
let v = &mut self.0.pool.0.borrow_mut();
|
||||
if v.len() < 128 {
|
||||
@ -340,25 +341,32 @@ impl fmt::Debug for HttpRequest {
|
||||
}
|
||||
}
|
||||
|
||||
/// Request's objects pool
|
||||
/// Slab-allocated `HttpRequest` Pool
|
||||
///
|
||||
/// Since request processing may yield for asynchronous events to complete, a worker may have many
|
||||
/// requests in-flight at any time. Pooling requests like this amortizes the performance and memory
|
||||
/// costs of allocating and de-allocating HttpRequest objects as frequently as they otherwise would.
|
||||
///
|
||||
/// Request objects are added when they are dropped (see `<HttpRequest as Drop>::drop`) and re-used
|
||||
/// in `<AppInitService as Service>::call` when there are available objects in the list.
|
||||
///
|
||||
/// The pool's initial capacity is 128 items.
|
||||
pub(crate) struct HttpRequestPool(RefCell<Vec<Rc<HttpRequestInner>>>);
|
||||
|
||||
impl HttpRequestPool {
|
||||
/// Allocates a slab of memory for pool use.
|
||||
pub(crate) fn create() -> &'static HttpRequestPool {
|
||||
let pool = HttpRequestPool(RefCell::new(Vec::with_capacity(128)));
|
||||
Box::leak(Box::new(pool))
|
||||
}
|
||||
|
||||
/// Get message from the pool
|
||||
/// Re-use a previously allocated (but now completed/discarded) HttpRequest object.
|
||||
#[inline]
|
||||
pub(crate) fn get_request(&self) -> Option<HttpRequest> {
|
||||
if let Some(inner) = self.0.borrow_mut().pop() {
|
||||
Some(HttpRequest(inner))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
self.0.borrow_mut().pop().map(HttpRequest)
|
||||
}
|
||||
|
||||
/// Clears all allocated HttpRequest objects.
|
||||
pub(crate) fn clear(&self) {
|
||||
self.0.borrow_mut().clear()
|
||||
}
|
||||
|
@ -607,7 +607,7 @@ mod tests {
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static("0001"),
|
||||
))
|
||||
.route(web::get().to(|| HttpResponse::Ok())),
|
||||
.route(web::get().to(HttpResponse::Ok)),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
@ -637,7 +637,7 @@ mod tests {
|
||||
})
|
||||
}
|
||||
})
|
||||
.route(web::get().to(|| HttpResponse::Ok())),
|
||||
.route(web::get().to(HttpResponse::Ok)),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
@ -684,9 +684,7 @@ mod tests {
|
||||
async fn test_default_resource() {
|
||||
let mut srv = init_service(
|
||||
App::new()
|
||||
.service(
|
||||
web::resource("/test").route(web::get().to(|| HttpResponse::Ok())),
|
||||
)
|
||||
.service(web::resource("/test").route(web::get().to(HttpResponse::Ok)))
|
||||
.default_service(|r: ServiceRequest| {
|
||||
ok(r.into_response(HttpResponse::BadRequest()))
|
||||
}),
|
||||
@ -705,7 +703,7 @@ mod tests {
|
||||
let mut srv = init_service(
|
||||
App::new().service(
|
||||
web::resource("/test")
|
||||
.route(web::get().to(|| HttpResponse::Ok()))
|
||||
.route(web::get().to(HttpResponse::Ok))
|
||||
.default_service(|r: ServiceRequest| {
|
||||
ok(r.into_response(HttpResponse::BadRequest()))
|
||||
}),
|
||||
@ -731,17 +729,17 @@ mod tests {
|
||||
.service(
|
||||
web::resource("/test/{p}")
|
||||
.guard(guard::Get())
|
||||
.to(|| HttpResponse::Ok()),
|
||||
.to(HttpResponse::Ok),
|
||||
)
|
||||
.service(
|
||||
web::resource("/test/{p}")
|
||||
.guard(guard::Put())
|
||||
.to(|| HttpResponse::Created()),
|
||||
.to(HttpResponse::Created),
|
||||
)
|
||||
.service(
|
||||
web::resource("/test/{p}")
|
||||
.guard(guard::Delete())
|
||||
.to(|| HttpResponse::NoContent()),
|
||||
.to(HttpResponse::NoContent),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
@ -783,7 +781,8 @@ mod tests {
|
||||
data3: web::Data<f64>| {
|
||||
assert_eq!(**data1, 10);
|
||||
assert_eq!(**data2, '*');
|
||||
assert_eq!(**data3, 1.0);
|
||||
let error = std::f64::EPSILON;
|
||||
assert!((**data3 - 1.0).abs() < error);
|
||||
HttpResponse::Ok()
|
||||
},
|
||||
),
|
||||
|
@ -480,7 +480,7 @@ pub(crate) mod tests {
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
match resp.response().body() {
|
||||
ResponseBody::Body(Body::Bytes(ref b)) => {
|
||||
let bytes: Bytes = b.clone().into();
|
||||
let bytes = b.clone();
|
||||
assert_eq!(bytes, Bytes::from_static(b"some"));
|
||||
}
|
||||
_ => panic!(),
|
||||
|
@ -362,7 +362,7 @@ mod tests {
|
||||
App::new()
|
||||
.service(
|
||||
web::resource("/test")
|
||||
.route(web::get().to(|| HttpResponse::Ok()))
|
||||
.route(web::get().to(HttpResponse::Ok))
|
||||
.route(web::put().to(|| async {
|
||||
Err::<HttpResponse, _>(error::ErrorBadRequest("err"))
|
||||
}))
|
||||
|
97
src/scope.rs
97
src/scope.rs
@ -678,12 +678,9 @@ mod tests {
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_scope() {
|
||||
let mut srv = init_service(
|
||||
App::new().service(
|
||||
web::scope("/app")
|
||||
.service(web::resource("/path1").to(|| HttpResponse::Ok())),
|
||||
),
|
||||
)
|
||||
let mut srv = init_service(App::new().service(
|
||||
web::scope("/app").service(web::resource("/path1").to(HttpResponse::Ok)),
|
||||
))
|
||||
.await;
|
||||
|
||||
let req = TestRequest::with_uri("/app/path1").to_request();
|
||||
@ -696,8 +693,8 @@ mod tests {
|
||||
let mut srv = init_service(
|
||||
App::new().service(
|
||||
web::scope("/app")
|
||||
.service(web::resource("").to(|| HttpResponse::Ok()))
|
||||
.service(web::resource("/").to(|| HttpResponse::Created())),
|
||||
.service(web::resource("").to(HttpResponse::Ok))
|
||||
.service(web::resource("/").to(HttpResponse::Created)),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
@ -714,7 +711,7 @@ mod tests {
|
||||
#[actix_rt::test]
|
||||
async fn test_scope_root2() {
|
||||
let mut srv = init_service(App::new().service(
|
||||
web::scope("/app/").service(web::resource("").to(|| HttpResponse::Ok())),
|
||||
web::scope("/app/").service(web::resource("").to(HttpResponse::Ok)),
|
||||
))
|
||||
.await;
|
||||
|
||||
@ -730,7 +727,7 @@ mod tests {
|
||||
#[actix_rt::test]
|
||||
async fn test_scope_root3() {
|
||||
let mut srv = init_service(App::new().service(
|
||||
web::scope("/app/").service(web::resource("/").to(|| HttpResponse::Ok())),
|
||||
web::scope("/app/").service(web::resource("/").to(HttpResponse::Ok)),
|
||||
))
|
||||
.await;
|
||||
|
||||
@ -748,8 +745,8 @@ mod tests {
|
||||
let mut srv = init_service(
|
||||
App::new().service(
|
||||
web::scope("app")
|
||||
.route("/path1", web::get().to(|| HttpResponse::Ok()))
|
||||
.route("/path1", web::delete().to(|| HttpResponse::Ok())),
|
||||
.route("/path1", web::get().to(HttpResponse::Ok))
|
||||
.route("/path1", web::delete().to(HttpResponse::Ok)),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
@ -777,8 +774,8 @@ mod tests {
|
||||
App::new().service(
|
||||
web::scope("app").service(
|
||||
web::resource("path1")
|
||||
.route(web::get().to(|| HttpResponse::Ok()))
|
||||
.route(web::delete().to(|| HttpResponse::Ok())),
|
||||
.route(web::get().to(HttpResponse::Ok))
|
||||
.route(web::delete().to(HttpResponse::Ok)),
|
||||
),
|
||||
),
|
||||
)
|
||||
@ -807,7 +804,7 @@ mod tests {
|
||||
App::new().service(
|
||||
web::scope("/app")
|
||||
.guard(guard::Get())
|
||||
.service(web::resource("/path1").to(|| HttpResponse::Ok())),
|
||||
.service(web::resource("/path1").to(HttpResponse::Ok)),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
@ -842,7 +839,7 @@ mod tests {
|
||||
|
||||
match resp.response().body() {
|
||||
ResponseBody::Body(Body::Bytes(ref b)) => {
|
||||
let bytes: Bytes = b.clone().into();
|
||||
let bytes = b.clone();
|
||||
assert_eq!(bytes, Bytes::from_static(b"project: project1"));
|
||||
}
|
||||
_ => panic!(),
|
||||
@ -855,14 +852,9 @@ mod tests {
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_nested_scope() {
|
||||
let mut srv = init_service(
|
||||
App::new().service(
|
||||
web::scope("/app")
|
||||
.service(web::scope("/t1").service(
|
||||
web::resource("/path1").to(|| HttpResponse::Created()),
|
||||
)),
|
||||
),
|
||||
)
|
||||
let mut srv = init_service(App::new().service(web::scope("/app").service(
|
||||
web::scope("/t1").service(web::resource("/path1").to(HttpResponse::Created)),
|
||||
)))
|
||||
.await;
|
||||
|
||||
let req = TestRequest::with_uri("/app/t1/path1").to_request();
|
||||
@ -872,14 +864,9 @@ mod tests {
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_nested_scope_no_slash() {
|
||||
let mut srv = init_service(
|
||||
App::new().service(
|
||||
web::scope("/app")
|
||||
.service(web::scope("t1").service(
|
||||
web::resource("/path1").to(|| HttpResponse::Created()),
|
||||
)),
|
||||
),
|
||||
)
|
||||
let mut srv = init_service(App::new().service(web::scope("/app").service(
|
||||
web::scope("t1").service(web::resource("/path1").to(HttpResponse::Created)),
|
||||
)))
|
||||
.await;
|
||||
|
||||
let req = TestRequest::with_uri("/app/t1/path1").to_request();
|
||||
@ -893,8 +880,8 @@ mod tests {
|
||||
App::new().service(
|
||||
web::scope("/app").service(
|
||||
web::scope("/t1")
|
||||
.service(web::resource("").to(|| HttpResponse::Ok()))
|
||||
.service(web::resource("/").to(|| HttpResponse::Created())),
|
||||
.service(web::resource("").to(HttpResponse::Ok))
|
||||
.service(web::resource("/").to(HttpResponse::Created)),
|
||||
),
|
||||
),
|
||||
)
|
||||
@ -916,7 +903,7 @@ mod tests {
|
||||
web::scope("/app").service(
|
||||
web::scope("/t1")
|
||||
.guard(guard::Get())
|
||||
.service(web::resource("/path1").to(|| HttpResponse::Ok())),
|
||||
.service(web::resource("/path1").to(HttpResponse::Ok)),
|
||||
),
|
||||
),
|
||||
)
|
||||
@ -953,7 +940,7 @@ mod tests {
|
||||
|
||||
match resp.response().body() {
|
||||
ResponseBody::Body(Body::Bytes(ref b)) => {
|
||||
let bytes: Bytes = b.clone().into();
|
||||
let bytes = b.clone();
|
||||
assert_eq!(bytes, Bytes::from_static(b"project: project_1"));
|
||||
}
|
||||
_ => panic!(),
|
||||
@ -981,7 +968,7 @@ mod tests {
|
||||
|
||||
match resp.response().body() {
|
||||
ResponseBody::Body(Body::Bytes(ref b)) => {
|
||||
let bytes: Bytes = b.clone().into();
|
||||
let bytes = b.clone();
|
||||
assert_eq!(bytes, Bytes::from_static(b"project: test - 1"));
|
||||
}
|
||||
_ => panic!(),
|
||||
@ -997,7 +984,7 @@ mod tests {
|
||||
let mut srv = init_service(
|
||||
App::new().service(
|
||||
web::scope("/app")
|
||||
.service(web::resource("/path1").to(|| HttpResponse::Ok()))
|
||||
.service(web::resource("/path1").to(HttpResponse::Ok))
|
||||
.default_service(|r: ServiceRequest| {
|
||||
ok(r.into_response(HttpResponse::BadRequest()))
|
||||
}),
|
||||
@ -1018,9 +1005,10 @@ mod tests {
|
||||
async fn test_default_resource_propagation() {
|
||||
let mut srv = init_service(
|
||||
App::new()
|
||||
.service(web::scope("/app1").default_service(
|
||||
web::resource("").to(|| HttpResponse::BadRequest()),
|
||||
))
|
||||
.service(
|
||||
web::scope("/app1")
|
||||
.default_service(web::resource("").to(HttpResponse::BadRequest)),
|
||||
)
|
||||
.service(web::scope("/app2"))
|
||||
.default_service(|r: ServiceRequest| {
|
||||
ok(r.into_response(HttpResponse::MethodNotAllowed()))
|
||||
@ -1043,17 +1031,17 @@ mod tests {
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_middleware() {
|
||||
let mut srv =
|
||||
init_service(
|
||||
let mut srv = init_service(
|
||||
App::new().service(
|
||||
web::scope("app")
|
||||
.wrap(DefaultHeaders::new().header(
|
||||
.wrap(
|
||||
DefaultHeaders::new().header(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static("0001"),
|
||||
))
|
||||
),
|
||||
)
|
||||
.service(
|
||||
web::resource("/test")
|
||||
.route(web::get().to(|| HttpResponse::Ok())),
|
||||
web::resource("/test").route(web::get().to(HttpResponse::Ok)),
|
||||
),
|
||||
),
|
||||
)
|
||||
@ -1084,7 +1072,7 @@ mod tests {
|
||||
Ok(res)
|
||||
}
|
||||
})
|
||||
.route("/test", web::get().to(|| HttpResponse::Ok())),
|
||||
.route("/test", web::get().to(HttpResponse::Ok)),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
@ -1105,7 +1093,6 @@ mod tests {
|
||||
"/t",
|
||||
web::get().to(|data: web::Data<usize>| {
|
||||
assert_eq!(**data, 10);
|
||||
let _ = data.clone();
|
||||
HttpResponse::Ok()
|
||||
}),
|
||||
),
|
||||
@ -1141,7 +1128,6 @@ mod tests {
|
||||
"/t",
|
||||
web::get().to(|data: web::Data<usize>| {
|
||||
assert_eq!(**data, 10);
|
||||
let _ = data.clone();
|
||||
HttpResponse::Ok()
|
||||
}),
|
||||
),
|
||||
@ -1157,7 +1143,7 @@ mod tests {
|
||||
async fn test_scope_config() {
|
||||
let mut srv =
|
||||
init_service(App::new().service(web::scope("/app").configure(|s| {
|
||||
s.route("/path1", web::get().to(|| HttpResponse::Ok()));
|
||||
s.route("/path1", web::get().to(HttpResponse::Ok));
|
||||
})))
|
||||
.await;
|
||||
|
||||
@ -1171,7 +1157,7 @@ mod tests {
|
||||
let mut srv =
|
||||
init_service(App::new().service(web::scope("/app").configure(|s| {
|
||||
s.service(web::scope("/v1").configure(|s| {
|
||||
s.route("/", web::get().to(|| HttpResponse::Ok()));
|
||||
s.route("/", web::get().to(HttpResponse::Ok));
|
||||
}));
|
||||
})))
|
||||
.await;
|
||||
@ -1193,10 +1179,9 @@ mod tests {
|
||||
s.route(
|
||||
"/",
|
||||
web::get().to(|req: HttpRequest| async move {
|
||||
HttpResponse::Ok().body(format!(
|
||||
"{}",
|
||||
req.url_for("youtube", &["xxxxxx"]).unwrap().as_str()
|
||||
))
|
||||
HttpResponse::Ok().body(
|
||||
req.url_for("youtube", &["xxxxxx"]).unwrap().to_string(),
|
||||
)
|
||||
}),
|
||||
);
|
||||
}));
|
||||
|
@ -12,7 +12,6 @@ use actix_router::{IntoPattern, Path, Resource, ResourceDef, Url};
|
||||
use actix_service::{IntoServiceFactory, ServiceFactory};
|
||||
|
||||
use crate::config::{AppConfig, AppService};
|
||||
use crate::data::Data;
|
||||
use crate::dev::insert_slash;
|
||||
use crate::guard::Guard;
|
||||
use crate::info::ConnectionInfo;
|
||||
@ -226,12 +225,11 @@ impl ServiceRequest {
|
||||
self.0.app_config()
|
||||
}
|
||||
|
||||
/// Get an application data stored with `App::data()` method during
|
||||
/// application configuration.
|
||||
pub fn app_data<T: 'static>(&self) -> Option<Data<T>> {
|
||||
/// Counterpart to [`HttpRequest::app_data`](../struct.HttpRequest.html#method.app_data).
|
||||
pub fn app_data<T: 'static>(&self) -> Option<&T> {
|
||||
for container in (self.0).0.app_data.iter().rev() {
|
||||
if let Some(data) = container.get::<Data<T>>() {
|
||||
return Some(Data::clone(&data));
|
||||
if let Some(data) = container.get::<T>() {
|
||||
return Some(data);
|
||||
}
|
||||
}
|
||||
|
||||
@ -595,6 +593,28 @@ mod tests {
|
||||
let resp = srv.call(req).await.unwrap();
|
||||
assert_eq!(resp.status(), http::StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_service_data() {
|
||||
let mut srv = init_service(
|
||||
App::new()
|
||||
.data(42u32)
|
||||
.service(web::service("/test").name("test").finish(
|
||||
|req: ServiceRequest| {
|
||||
assert_eq!(
|
||||
req.app_data::<web::Data<u32>>().unwrap().as_ref(),
|
||||
&42
|
||||
);
|
||||
ok(req.into_response(HttpResponse::Ok().finish()))
|
||||
},
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
let req = TestRequest::with_uri("/test").to_request();
|
||||
let resp = srv.call(req).await.unwrap();
|
||||
assert_eq!(resp.status(), http::StatusCode::OK);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fmt_debug() {
|
||||
let req = TestRequest::get()
|
||||
|
@ -23,7 +23,6 @@ use futures_util::future::ok;
|
||||
use futures_util::StreamExt;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use serde_json;
|
||||
use socket2::{Domain, Protocol, Socket, Type};
|
||||
|
||||
pub use actix_http::test::TestBuffer;
|
||||
|
@ -407,18 +407,15 @@ mod tests {
|
||||
|
||||
fn eq(err: UrlencodedError, other: UrlencodedError) -> bool {
|
||||
match err {
|
||||
UrlencodedError::Overflow { .. } => match other {
|
||||
UrlencodedError::Overflow { .. } => true,
|
||||
_ => false,
|
||||
},
|
||||
UrlencodedError::UnknownLength => match other {
|
||||
UrlencodedError::UnknownLength => true,
|
||||
_ => false,
|
||||
},
|
||||
UrlencodedError::ContentType => match other {
|
||||
UrlencodedError::ContentType => true,
|
||||
_ => false,
|
||||
},
|
||||
UrlencodedError::Overflow { .. } => {
|
||||
matches!(other, UrlencodedError::Overflow { .. })
|
||||
}
|
||||
UrlencodedError::UnknownLength => {
|
||||
matches!(other, UrlencodedError::UnknownLength)
|
||||
}
|
||||
UrlencodedError::ContentType => {
|
||||
matches!(other, UrlencodedError::ContentType)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
@ -433,14 +433,10 @@ mod tests {
|
||||
|
||||
fn json_eq(err: JsonPayloadError, other: JsonPayloadError) -> bool {
|
||||
match err {
|
||||
JsonPayloadError::Overflow => match other {
|
||||
JsonPayloadError::Overflow => true,
|
||||
_ => false,
|
||||
},
|
||||
JsonPayloadError::ContentType => match other {
|
||||
JsonPayloadError::ContentType => true,
|
||||
_ => false,
|
||||
},
|
||||
JsonPayloadError::Overflow => matches!(other, JsonPayloadError::Overflow),
|
||||
JsonPayloadError::ContentType => {
|
||||
matches!(other, JsonPayloadError::ContentType)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@ -486,7 +482,7 @@ mod tests {
|
||||
.to_http_parts();
|
||||
|
||||
let s = Json::<MyObject>::from_request(&req, &mut pl).await;
|
||||
let mut resp = Response::from_error(s.err().unwrap().into());
|
||||
let mut resp = Response::from_error(s.err().unwrap());
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
let body = load_stream(resp.take_body()).await.unwrap();
|
||||
|
@ -25,8 +25,8 @@ use crate::FromRequest;
|
||||
/// /// extract path info from "/{username}/{count}/index.html" url
|
||||
/// /// {username} - deserializes to a String
|
||||
/// /// {count} - - deserializes to a u32
|
||||
/// async fn index(info: web::Path<(String, u32)>) -> String {
|
||||
/// format!("Welcome {}! {}", info.0, info.1)
|
||||
/// async fn index(web::Path((username, count)): web::Path<(String, u32)>) -> String {
|
||||
/// format!("Welcome {}! {}", username, count)
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
@ -61,20 +61,18 @@ use crate::FromRequest;
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
pub struct Path<T> {
|
||||
inner: T,
|
||||
}
|
||||
pub struct Path<T>(pub T);
|
||||
|
||||
impl<T> Path<T> {
|
||||
/// Deconstruct to an inner value
|
||||
pub fn into_inner(self) -> T {
|
||||
self.inner
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRef<T> for Path<T> {
|
||||
fn as_ref(&self) -> &T {
|
||||
&self.inner
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,31 +80,31 @@ impl<T> ops::Deref for Path<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
&self.inner
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ops::DerefMut for Path<T> {
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
&mut self.inner
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for Path<T> {
|
||||
fn from(inner: T) -> Path<T> {
|
||||
Path { inner }
|
||||
Path(inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug> fmt::Debug for Path<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.inner.fmt(f)
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Display> fmt::Display for Path<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.inner.fmt(f)
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,8 +118,8 @@ impl<T: fmt::Display> fmt::Display for Path<T> {
|
||||
/// /// extract path info from "/{username}/{count}/index.html" url
|
||||
/// /// {username} - deserializes to a String
|
||||
/// /// {count} - - deserializes to a u32
|
||||
/// async fn index(info: web::Path<(String, u32)>) -> String {
|
||||
/// format!("Welcome {}! {}", info.0, info.1)
|
||||
/// async fn index(web::Path((username, count)): web::Path<(String, u32)>) -> String {
|
||||
/// format!("Welcome {}! {}", username, count)
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
@ -173,7 +171,7 @@ where
|
||||
|
||||
ready(
|
||||
de::Deserialize::deserialize(PathDeserializer::new(req.match_info()))
|
||||
.map(|inner| Path { inner })
|
||||
.map(Path)
|
||||
.map_err(move |e| {
|
||||
log::debug!(
|
||||
"Failed during Path extractor deserialization. \
|
||||
@ -290,21 +288,22 @@ mod tests {
|
||||
resource.match_path(req.match_info_mut());
|
||||
|
||||
let (req, mut pl) = req.into_parts();
|
||||
let res = <(Path<(String, String)>,)>::from_request(&req, &mut pl)
|
||||
let (Path(res),) = <(Path<(String, String)>,)>::from_request(&req, &mut pl)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!((res.0).0, "name");
|
||||
assert_eq!((res.0).1, "user1");
|
||||
assert_eq!(res.0, "name");
|
||||
assert_eq!(res.1, "user1");
|
||||
|
||||
let res = <(Path<(String, String)>, Path<(String, String)>)>::from_request(
|
||||
let (Path(a), Path(b)) =
|
||||
<(Path<(String, String)>, Path<(String, String)>)>::from_request(
|
||||
&req, &mut pl,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!((res.0).0, "name");
|
||||
assert_eq!((res.0).1, "user1");
|
||||
assert_eq!((res.1).0, "name");
|
||||
assert_eq!((res.1).1, "user1");
|
||||
assert_eq!(a.0, "name");
|
||||
assert_eq!(a.1, "user1");
|
||||
assert_eq!(b.0, "name");
|
||||
assert_eq!(b.1, "user1");
|
||||
|
||||
let () = <()>::from_request(&req, &mut pl).await.unwrap();
|
||||
}
|
||||
@ -329,7 +328,7 @@ mod tests {
|
||||
let s = s.into_inner();
|
||||
assert_eq!(s.value, "user2");
|
||||
|
||||
let s = Path::<(String, String)>::from_request(&req, &mut pl)
|
||||
let Path(s) = Path::<(String, String)>::from_request(&req, &mut pl)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(s.0, "name");
|
||||
@ -344,7 +343,7 @@ mod tests {
|
||||
assert_eq!(s.as_ref().key, "name");
|
||||
assert_eq!(s.value, 32);
|
||||
|
||||
let s = Path::<(String, u8)>::from_request(&req, &mut pl)
|
||||
let Path(s) = Path::<(String, u8)>::from_request(&req, &mut pl)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(s.0, "name");
|
||||
|
@ -13,10 +13,10 @@ use futures_util::future::{err, ok, Either, FutureExt, LocalBoxFuture, Ready};
|
||||
use futures_util::StreamExt;
|
||||
use mime::Mime;
|
||||
|
||||
use crate::dev;
|
||||
use crate::extract::FromRequest;
|
||||
use crate::http::header;
|
||||
use crate::request::HttpRequest;
|
||||
use crate::{dev, web};
|
||||
|
||||
/// Payload extractor returns request 's payload stream.
|
||||
///
|
||||
@ -142,13 +142,8 @@ impl FromRequest for Bytes {
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
|
||||
let tmp;
|
||||
let cfg = if let Some(cfg) = req.app_data::<PayloadConfig>() {
|
||||
cfg
|
||||
} else {
|
||||
tmp = PayloadConfig::default();
|
||||
&tmp
|
||||
};
|
||||
// allow both Config and Data<Config>
|
||||
let cfg = PayloadConfig::from_req(req);
|
||||
|
||||
if let Err(e) = cfg.check_mimetype(req) {
|
||||
return Either::Right(err(e));
|
||||
@ -197,13 +192,7 @@ impl FromRequest for String {
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
|
||||
let tmp;
|
||||
let cfg = if let Some(cfg) = req.app_data::<PayloadConfig>() {
|
||||
cfg
|
||||
} else {
|
||||
tmp = PayloadConfig::default();
|
||||
&tmp
|
||||
};
|
||||
let cfg = PayloadConfig::from_req(req);
|
||||
|
||||
// check content-type
|
||||
if let Err(e) = cfg.check_mimetype(req) {
|
||||
@ -237,7 +226,12 @@ impl FromRequest for String {
|
||||
)
|
||||
}
|
||||
}
|
||||
/// Payload configuration for request's payload.
|
||||
|
||||
/// Configuration for request's payload.
|
||||
///
|
||||
/// Applies to the built-in `Bytes` and `String` extractors. Note that the Payload extractor does
|
||||
/// not automatically check conformance with this configuration to allow more flexibility when
|
||||
/// building extractors on top of `Payload`.
|
||||
#[derive(Clone)]
|
||||
pub struct PayloadConfig {
|
||||
limit: usize,
|
||||
@ -284,14 +278,28 @@ impl PayloadConfig {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Allow payload config extraction from app data checking both `T` and `Data<T>`, in that
|
||||
/// order, and falling back to the default payload config.
|
||||
fn from_req(req: &HttpRequest) -> &PayloadConfig {
|
||||
req.app_data::<PayloadConfig>()
|
||||
.or_else(|| {
|
||||
req.app_data::<web::Data<PayloadConfig>>()
|
||||
.map(|d| d.as_ref())
|
||||
})
|
||||
.unwrap_or_else(|| &DEFAULT_PAYLOAD_CONFIG)
|
||||
}
|
||||
}
|
||||
|
||||
// Allow shared refs to default.
|
||||
static DEFAULT_PAYLOAD_CONFIG: PayloadConfig = PayloadConfig {
|
||||
limit: 262_144, // 2^18 bytes (~256kB)
|
||||
mimetype: None,
|
||||
};
|
||||
|
||||
impl Default for PayloadConfig {
|
||||
fn default() -> Self {
|
||||
PayloadConfig {
|
||||
limit: 262_144,
|
||||
mimetype: None,
|
||||
}
|
||||
DEFAULT_PAYLOAD_CONFIG.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@ -407,8 +415,9 @@ mod tests {
|
||||
use bytes::Bytes;
|
||||
|
||||
use super::*;
|
||||
use crate::http::header;
|
||||
use crate::test::TestRequest;
|
||||
use crate::http::{header, StatusCode};
|
||||
use crate::test::{call_service, init_service, TestRequest};
|
||||
use crate::{web, App, Responder};
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_payload_config() {
|
||||
@ -428,6 +437,86 @@ mod tests {
|
||||
assert!(cfg.check_mimetype(&req).is_ok());
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_config_recall_locations() {
|
||||
async fn bytes_handler(_: Bytes) -> impl Responder {
|
||||
"payload is probably json bytes"
|
||||
}
|
||||
|
||||
async fn string_handler(_: String) -> impl Responder {
|
||||
"payload is probably json string"
|
||||
}
|
||||
|
||||
let mut srv = init_service(
|
||||
App::new()
|
||||
.service(
|
||||
web::resource("/bytes-app-data")
|
||||
.app_data(
|
||||
PayloadConfig::default().mimetype(mime::APPLICATION_JSON),
|
||||
)
|
||||
.route(web::get().to(bytes_handler)),
|
||||
)
|
||||
.service(
|
||||
web::resource("/bytes-data")
|
||||
.data(PayloadConfig::default().mimetype(mime::APPLICATION_JSON))
|
||||
.route(web::get().to(bytes_handler)),
|
||||
)
|
||||
.service(
|
||||
web::resource("/string-app-data")
|
||||
.app_data(
|
||||
PayloadConfig::default().mimetype(mime::APPLICATION_JSON),
|
||||
)
|
||||
.route(web::get().to(string_handler)),
|
||||
)
|
||||
.service(
|
||||
web::resource("/string-data")
|
||||
.data(PayloadConfig::default().mimetype(mime::APPLICATION_JSON))
|
||||
.route(web::get().to(string_handler)),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
let req = TestRequest::with_uri("/bytes-app-data").to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
let req = TestRequest::with_uri("/bytes-data").to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
let req = TestRequest::with_uri("/string-app-data").to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
let req = TestRequest::with_uri("/string-data").to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
let req = TestRequest::with_uri("/bytes-app-data")
|
||||
.header(header::CONTENT_TYPE, mime::APPLICATION_JSON)
|
||||
.to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let req = TestRequest::with_uri("/bytes-data")
|
||||
.header(header::CONTENT_TYPE, mime::APPLICATION_JSON)
|
||||
.to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let req = TestRequest::with_uri("/string-app-data")
|
||||
.header(header::CONTENT_TYPE, mime::APPLICATION_JSON)
|
||||
.to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let req = TestRequest::with_uri("/string-data")
|
||||
.header(header::CONTENT_TYPE, mime::APPLICATION_JSON)
|
||||
.to_request();
|
||||
let resp = call_service(&mut srv, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_bytes() {
|
||||
let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "11")
|
||||
|
@ -2,7 +2,7 @@
|
||||
name = "actix-http-test"
|
||||
version = "2.0.0-alpha.1"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix http test server"
|
||||
description = "Actix HTTP test server"
|
||||
readme = "README.md"
|
||||
keywords = ["http", "web", "framework", "async", "futures"]
|
||||
homepage = "https://actix.rs"
|
||||
@ -11,7 +11,7 @@ documentation = "https://docs.rs/actix-http-test/"
|
||||
categories = ["network-programming", "asynchronous",
|
||||
"web-programming::http-server",
|
||||
"web-programming::websocket"]
|
||||
license = "MIT/Apache-2.0"
|
||||
license = "MIT OR Apache-2.0"
|
||||
exclude = [".gitignore", ".cargo/config"]
|
||||
edition = "2018"
|
||||
|
||||
@ -53,4 +53,4 @@ open-ssl = { version = "0.10", package = "openssl", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-web = "3.0.0-alpha.3"
|
||||
actix-http = "2.0.0-alpha.4"
|
||||
actix-http = "2.0.0-beta.3"
|
||||
|
@ -851,7 +851,7 @@ async fn test_slow_request() {
|
||||
use std::net;
|
||||
|
||||
let srv = test::start_with(test::config().client_timeout(200), || {
|
||||
App::new().service(web::resource("/").route(web::to(|| HttpResponse::Ok())))
|
||||
App::new().service(web::resource("/").route(web::to(HttpResponse::Ok)))
|
||||
});
|
||||
|
||||
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
|
||||
|
Reference in New Issue
Block a user