1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-06 19:00:18 +02:00

Compare commits

...

21 Commits

Author SHA1 Message Date
aa2bd6fbfb http: Bump up to 2.0.0-beta.3 (#1630) 2020-08-14 19:42:14 +09:00
5aad8e24c7 Re-export all error types from awc (#1621) 2020-08-14 01:24:35 +01:00
6e97bc09f8 Use action to upload docs 2020-08-13 16:04:50 +09:00
160995b8d4 fix awc pool leak (#1626) 2020-08-09 21:49:43 +01:00
187646b2f9 match HttpRequest app_data behavior in ServiceRequest (#1618) 2020-08-09 15:51:38 +01:00
46627be36f add dep graph dot graphs (#1601) 2020-08-09 13:54:35 +01:00
a78380739e require rustls feature for client example (#1625) 2020-08-09 13:32:37 +01:00
cf1c8abe62 prepare release http & awc (#1617) 2020-07-22 01:13:10 +01:00
92b5bcd13f Check format and tweak CI config (#1619) 2020-07-22 00:28:33 +01:00
701bdacfa2 Fix illegal chunked encoding (#1615)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2020-07-21 17:24:56 +01:00
6dc47c4093 fix soundness concern in h1 decoder (#1614)
Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-07-21 16:25:33 +01:00
0ec335a39c bump MSRV to 1.42 (#1616) 2020-07-21 16:40:30 +09:00
f8d5ad6b53 Make web::Path a tuple struct with a public inner value (#1594)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2020-07-21 00:54:26 +01:00
43c362779d also try extracting payload config as Data<T> (#1610) 2020-07-20 17:40:58 +01:00
971ba3eee1 fix continous growth of app data in pooled requests (#1609)
fixes #1606
fixes #1607
2020-07-18 16:17:00 +01:00
2fd96c03e5 prepare beta.1 release for multipart/files/actors (#1605) 2020-07-16 11:38:57 +01:00
ad7c6d2633 prepare actix-web v3.0.0-beta.1 release (#1600) 2020-07-15 00:44:44 +01:00
3362a3d61b Merge pull request #1603 from JohnTitor/license
Avoid using deprecated `/` in license field
2020-07-14 15:25:48 +09:00
769ea6bd5b Merge pull request #1602 from actix/release/awc-beta-1
prepare awc v2.0.0-beta.1 release
2020-07-14 13:33:05 +09:00
1382094c15 Avoid using deprecated / in license field 2020-07-14 11:19:56 +09:00
78594a72bd prepare awc v2.0.0-beta.1 release 2020-07-14 03:16:26 +01:00
80 changed files with 956 additions and 559 deletions

View File

@ -1,6 +1,8 @@
## PR Type <!-- Thanks for considering contributing actix! -->
What kind of change does this PR make? <!-- 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 --> <!-- Bug Fix / Feature / Refactor / Code Style / Other -->
INSERT_PR_TYPE INSERT_PR_TYPE
@ -13,6 +15,7 @@ Check your PR fulfills the following:
- [ ] Tests for the changes have been added / updated. - [ ] Tests for the changes have been added / updated.
- [ ] Documentation comments have been added / updated. - [ ] Documentation comments have been added / updated.
- [ ] A changelog entry has been made for the appropriate packages. - [ ] A changelog entry has been made for the appropriate packages.
- [ ] Format code with the latest stable rustfmt
## Overview ## Overview

View File

@ -1,13 +1,18 @@
name: Benchmark (Linux) name: Benchmark (Linux)
on: [push, pull_request] on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
jobs: jobs:
check_benchmark: check_benchmark:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@master - uses: actions/checkout@v2
- name: Install Rust - name: Install Rust
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1

32
.github/workflows/clippy-fmt.yml vendored Normal file
View 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

View File

@ -1,6 +1,11 @@
name: CI (Linux) name: CI (Linux)
on: [push, pull_request] on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
jobs: jobs:
build_and_test: build_and_test:
@ -8,7 +13,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
version: version:
- 1.41.1 # MSRV - 1.42.0 # MSRV
- stable - stable
- nightly - nightly
@ -16,7 +21,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@master - uses: actions/checkout@v2
- name: Install ${{ matrix.version }} - name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1

View File

@ -1,6 +1,11 @@
name: CI (macOS) name: CI (macOS)
on: [push, pull_request] on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
jobs: jobs:
build_and_test: build_and_test:
@ -15,7 +20,7 @@ jobs:
runs-on: macOS-latest runs-on: macOS-latest
steps: steps:
- uses: actions/checkout@master - uses: actions/checkout@v2
- name: Install ${{ matrix.version }} - name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1

View File

@ -11,7 +11,7 @@ jobs:
if: github.repository == 'actix/actix-web' if: github.repository == 'actix/actix-web'
steps: steps:
- uses: actions/checkout@master - uses: actions/checkout@v2
- name: Install Rust - name: Install Rust
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
@ -29,7 +29,9 @@ jobs:
- name: Tweak HTML - name: Tweak HTML
run: echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html run: echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html
- name: Upload documentation - name: Deploy to GitHub Pages
run: | uses: JamesIves/github-pages-deploy-action@3.5.8
git clone https://github.com/davisp/ghp-import.git with:
./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://${{ secrets.GITHUB_TOKEN }}@github.com/"${{ github.repository }}.git" target/doc GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH: gh-pages
FOLDER: target/doc

View File

@ -1,6 +1,11 @@
name: CI (Windows) name: CI (Windows)
on: [push, pull_request] on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
env: env:
VCPKGRS_DYNAMIC: 1 VCPKGRS_DYNAMIC: 1
@ -18,7 +23,7 @@ jobs:
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- uses: actions/checkout@master - uses: actions/checkout@v2
- name: Install ${{ matrix.version }} - name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1

1
.gitignore vendored
View File

@ -9,6 +9,7 @@ guide/build/
*.pid *.pid
*.sock *.sock
*~ *~
.DS_Store
# These are backup files generated by rustfmt # These are backup files generated by rustfmt
**/*.rs.bk **/*.rs.bk

View File

@ -1,38 +1,50 @@
# Changes # 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 ### Added
* Re-export `actix_rt::main` as `actix_web::main`. * Re-export `actix_rt::main` as `actix_web::main`.
* `HttpRequest::match_pattern` and `ServiceRequest::match_pattern` for extracting the matched * `HttpRequest::match_pattern` and `ServiceRequest::match_pattern` for extracting the matched
resource pattern. resource pattern.
* `HttpRequest::match_name` and `ServiceRequest::match_name` for extracting matched resource name. * `HttpRequest::match_name` and `ServiceRequest::match_name` for extracting matched resource name.
### Changed ### Changed
* Fix actix_http::h1::dispatcher so it returns when HW_BUFFER_SIZE is reached. Should reduce peak memory consumption during large uploads. [#1550] * 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. * Migrate cookie handling to `cookie` crate. Actix-web no longer requires `ring` dependency.
* MSRV is now 1.41.1 * MSRV is now 1.41.1
### Fixed ### Fixed
* `NormalizePath` improved consistency when path needs slashes added _and_ removed. * `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 ### Added
* Add option to create `Data<T>` from `Arc<T>` [#1509] * Add option to create `Data<T>` from `Arc<T>` [#1509]
### Changed ### Changed
* Resources and Scopes can now access non-overridden data types set on App (or containing scopes) when setting their own data. [#1486] * 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] * Fix audit issue logging by default peer address [#1485]
* Bump minimum supported Rust version to 1.40 * Bump minimum supported Rust version to 1.40
* Replace deprecated `net2` crate with `socket2` * Replace deprecated `net2` crate with `socket2`
[#1485]: https://github.com/actix/actix-web/pull/1485 [#1485]: https://github.com/actix/actix-web/pull/1485

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-web" name = "actix-web"
version = "3.0.0-alpha.3" version = "3.0.0-beta.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
readme = "README.md" readme = "README.md"
@ -11,7 +11,7 @@ documentation = "https://docs.rs/actix-web/"
categories = ["network-programming", "asynchronous", categories = ["network-programming", "asynchronous",
"web-programming::http-server", "web-programming::http-server",
"web-programming::websocket"] "web-programming::websocket"]
license = "MIT/Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2018" edition = "2018"
[package.metadata.docs.rs] [package.metadata.docs.rs]
@ -76,9 +76,9 @@ actix-macros = "0.1.0"
actix-threadpool = "0.3.1" actix-threadpool = "0.3.1"
actix-tls = "2.0.0-alpha.1" actix-tls = "2.0.0-alpha.1"
actix-web-codegen = "0.2.2" actix-web-codegen = "0.3.0-beta.1"
actix-http = "2.0.0-alpha.4" actix-http = "2.0.0-beta.3"
awc = { version = "2.0.0-alpha.2", default-features = false } awc = { version = "2.0.0-beta.1", default-features = false }
bytes = "0.5.3" bytes = "0.5.3"
derive_more = "0.99.2" derive_more = "0.99.2"
@ -124,6 +124,10 @@ actix-files = { path = "actix-files" }
actix-multipart = { path = "actix-multipart" } actix-multipart = { path = "actix-multipart" }
awc = { path = "awc" } awc = { path = "awc" }
[[example]]
name = "client"
required-features = ["rustls"]
[[bench]] [[bench]]
name = "server" name = "server"
harness = false harness = false

View File

@ -12,6 +12,26 @@
* `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a * `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a
`u64` instead of a `usize`. `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 ## 2.0.0
* `HttpServer::start()` renamed to `HttpServer::run()`. It also possible to * `HttpServer::start()` renamed to `HttpServer::run()`. It also possible to

View File

@ -7,7 +7,7 @@
[![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web)
[![Documentation](https://docs.rs/actix-web/badge.svg)](https://docs.rs/actix-web) [![Documentation](https://docs.rs/actix-web/badge.svg)](https://docs.rs/actix-web)
[![Version](https://img.shields.io/badge/rustc-1.41+-lightgray.svg)](https://blog.rust-lang.org/2020/02/27/Rust-1.41.1.html) [![Version](https://img.shields.io/badge/rustc-1.42+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html)
![License](https://img.shields.io/crates/l/actix-web.svg) ![License](https://img.shields.io/crates/l/actix-web.svg)
<br /> <br />
[![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web)
@ -32,7 +32,7 @@
* Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) * 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) * Includes an async [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html)
* Supports [Actix actor framework](https://github.com/actix/actix) * Supports [Actix actor framework](https://github.com/actix/actix)
* Runs on stable Rust 1.41+ * Runs on stable Rust 1.42+
## Documentation ## Documentation
@ -61,8 +61,8 @@ Code:
use actix_web::{get, web, App, HttpServer, Responder}; use actix_web::{get, web, App, HttpServer, Responder};
#[get("/{id}/{name}/index.html")] #[get("/{id}/{name}/index.html")]
async fn index(info: web::Path<(u32, String)>) -> impl Responder { async fn index(web::Path((id, name)): web::Path<(u32, String)>) -> impl Responder {
format!("Hello {}! id:{}", info.1, info.0) format!("Hello {}! id:{}", name, id)
} }
#[actix_web::main] #[actix_web::main]

View File

@ -1,11 +1,12 @@
# Changes # Changes
## [unreleased] - xxx ## [Unreleased] - 2020-xx-xx
## [0.3.0-beta.1] - 2020-07-15
* Update `v_htmlescape` to 0.10 * Update `v_htmlescape` to 0.10
* Update `actix-web` and `actix-http` dependencies to beta.1
## [0.3.0-alpha.1] - 2020-05-23 ## [0.3.0-alpha.1] - 2020-05-23
* Update `actix-web` and `actix-http` dependencies to alpha * Update `actix-web` and `actix-http` dependencies to alpha
* Fix some typos in the docs * Fix some typos in the docs
* Bump minimum supported Rust version to 1.40 * Bump minimum supported Rust version to 1.40

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-files" name = "actix-files"
version = "0.3.0-alpha.1" version = "0.3.0-beta.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Static files support for actix web." description = "Static files support for actix web."
readme = "README.md" readme = "README.md"
@ -9,7 +9,7 @@ homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web.git" repository = "https://github.com/actix/actix-web.git"
documentation = "https://docs.rs/actix-files/" documentation = "https://docs.rs/actix-files/"
categories = ["asynchronous", "web-programming::http-server"] categories = ["asynchronous", "web-programming::http-server"]
license = "MIT/Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2018" edition = "2018"
[lib] [lib]
@ -17,8 +17,8 @@ name = "actix_files"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
actix-web = { version = "3.0.0-alpha.3", default-features = false } actix-web = { version = "3.0.0-beta.1", default-features = false }
actix-http = "2.0.0-alpha.4" actix-http = "2.0.0-beta.3"
actix-service = "1.0.1" actix-service = "1.0.1"
bitflags = "1" bitflags = "1"
bytes = "0.5.3" bytes = "0.5.3"
@ -33,4 +33,4 @@ v_htmlescape = "0.10"
[dev-dependencies] [dev-dependencies]
actix-rt = "1.0.0" 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"] }

View File

@ -26,7 +26,6 @@ use actix_web::{web, FromRequest, HttpRequest, HttpResponse};
use bytes::Bytes; use bytes::Bytes;
use futures_core::Stream; use futures_core::Stream;
use futures_util::future::{ok, ready, Either, FutureExt, LocalBoxFuture, Ready}; use futures_util::future::{ok, ready, Either, FutureExt, LocalBoxFuture, Ready};
use mime;
use mime_guess::from_ext; use mime_guess::from_ext;
use percent_encoding::{utf8_percent_encode, CONTROLS}; use percent_encoding::{utf8_percent_encode, CONTROLS};
use v_htmlescape::escape as escape_html_entity; use v_htmlescape::escape as escape_html_entity;
@ -250,6 +249,8 @@ pub struct Files {
renderer: Rc<DirectoryRenderer>, renderer: Rc<DirectoryRenderer>,
mime_override: Option<Rc<MimeOverride>>, mime_override: Option<Rc<MimeOverride>>,
file_flags: named::Flags, file_flags: named::Flags,
// FIXME: Should re-visit later.
#[allow(clippy::redundant_allocation)]
guards: Option<Rc<Box<dyn Guard>>>, guards: Option<Rc<Box<dyn Guard>>>,
} }
@ -462,6 +463,8 @@ pub struct FilesService {
renderer: Rc<DirectoryRenderer>, renderer: Rc<DirectoryRenderer>,
mime_override: Option<Rc<MimeOverride>>, mime_override: Option<Rc<MimeOverride>>,
file_flags: named::Flags, file_flags: named::Flags,
// FIXME: Should re-visit later.
#[allow(clippy::redundant_allocation)]
guards: Option<Rc<Box<dyn Guard>>>, guards: Option<Rc<Box<dyn Guard>>>,
} }
@ -501,11 +504,8 @@ impl Service for FilesService {
// execute user defined guards // execute user defined guards
(**guard).check(req.head()) (**guard).check(req.head())
} else { } else {
// default behaviour // default behavior
match *req.method() { matches!(*req.method(), Method::HEAD | Method::GET)
Method::HEAD | Method::GET => true,
_ => false,
}
}; };
if !is_method_valid { if !is_method_valid {
@ -952,9 +952,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_named_file_content_range_headers() { async fn test_named_file_content_range_headers() {
let srv = test::start(|| { let srv = test::start(|| App::new().service(Files::new("/", ".")));
App::new().service(Files::new("/", "."))
});
// Valid range header // Valid range header
let response = srv let response = srv
@ -979,9 +977,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_named_file_content_length_headers() { async fn test_named_file_content_length_headers() {
let srv = test::start(|| { let srv = test::start(|| App::new().service(Files::new("/", ".")));
App::new().service(Files::new("/", "."))
});
// Valid range header // Valid range header
let response = srv let response = srv
@ -1020,15 +1016,9 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_head_content_length_headers() { async fn test_head_content_length_headers() {
let srv = test::start(|| { let srv = test::start(|| App::new().service(Files::new("/", ".")));
App::new().service(Files::new("/", "."))
});
let response = srv let response = srv.head("/tests/test.binary").send().await.unwrap();
.head("/tests/test.binary")
.send()
.await
.unwrap();
let content_length = response let content_length = response
.headers() .headers()
@ -1097,12 +1087,10 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_named_file_content_encoding() { async fn test_named_file_content_encoding() {
let mut srv = test::init_service(App::new().wrap(Compress::default()).service( let mut srv = test::init_service(App::new().wrap(Compress::default()).service(
web::resource("/").to(|| { web::resource("/").to(|| async {
async {
NamedFile::open("Cargo.toml") NamedFile::open("Cargo.toml")
.unwrap() .unwrap()
.set_content_encoding(header::ContentEncoding::Identity) .set_content_encoding(header::ContentEncoding::Identity)
}
}), }),
)) ))
.await; .await;
@ -1119,12 +1107,10 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_named_file_content_encoding_gzip() { async fn test_named_file_content_encoding_gzip() {
let mut srv = test::init_service(App::new().wrap(Compress::default()).service( let mut srv = test::init_service(App::new().wrap(Compress::default()).service(
web::resource("/").to(|| { web::resource("/").to(|| async {
async {
NamedFile::open("Cargo.toml") NamedFile::open("Cargo.toml")
.unwrap() .unwrap()
.set_content_encoding(header::ContentEncoding::Gzip) .set_content_encoding(header::ContentEncoding::Gzip)
}
}), }),
)) ))
.await; .await;

View File

@ -8,7 +8,6 @@ use std::time::{SystemTime, UNIX_EPOCH};
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
use bitflags::bitflags; use bitflags::bitflags;
use mime;
use mime_guess::from_path; use mime_guess::from_path;
use actix_http::body::SizedStream; use actix_http::body::SizedStream;
@ -90,7 +89,7 @@ impl NamedFile {
}; };
let ct = from_path(&path).first_or_octet_stream(); 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, mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline,
_ => DispositionType::Attachment, _ => DispositionType::Attachment,
}; };
@ -104,8 +103,8 @@ impl NamedFile {
})) }))
} }
let cd = ContentDisposition { let cd = ContentDisposition {
disposition: disposition_type, disposition,
parameters: parameters, parameters,
}; };
(ct, cd) (ct, cd)
}; };

View File

@ -1,6 +1,23 @@
# Changes # 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 ## [2.0.0-beta.1] - 2020-07-11

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-http" name = "actix-http"
version = "2.0.0-beta.1" version = "2.0.0-beta.3"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix HTTP primitives" description = "Actix HTTP primitives"
readme = "README.md" readme = "README.md"
@ -11,7 +11,7 @@ documentation = "https://docs.rs/actix-http/"
categories = ["network-programming", "asynchronous", categories = ["network-programming", "asynchronous",
"web-programming::http-server", "web-programming::http-server",
"web-programming::websocket"] "web-programming::websocket"]
license = "MIT/Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2018" edition = "2018"
[package.metadata.docs.rs] [package.metadata.docs.rs]
@ -103,3 +103,7 @@ harness = false
[[bench]] [[bench]]
name = "status-line" name = "status-line"
harness = false harness = false
[[bench]]
name = "uninit-headers"
harness = false

View 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!(),
}
}
}

View File

@ -21,12 +21,7 @@ pub enum BodySize {
impl BodySize { impl BodySize {
pub fn is_eof(&self) -> bool { pub fn is_eof(&self) -> bool {
match self { matches!(self, BodySize::None | BodySize::Empty | BodySize::Sized(0))
BodySize::None
| BodySize::Empty
| BodySize::Sized(0) => true,
_ => false,
}
} }
} }
@ -192,14 +187,8 @@ impl MessageBody for Body {
impl PartialEq for Body { impl PartialEq for Body {
fn eq(&self, other: &Body) -> bool { fn eq(&self, other: &Body) -> bool {
match *self { match *self {
Body::None => match *other { Body::None => matches!(*other, Body::None),
Body::None => true, Body::Empty => matches!(*other, Body::Empty),
_ => false,
},
Body::Empty => match *other {
Body::Empty => true,
_ => false,
},
Body::Bytes(ref b) => match *other { Body::Bytes(ref b) => match *other {
Body::Bytes(ref b2) => b == b2, Body::Bytes(ref b2) => b == b2,
_ => false, _ => false,
@ -476,9 +465,9 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use futures_util::stream;
use futures_util::future::poll_fn; use futures_util::future::poll_fn;
use futures_util::pin_mut; use futures_util::pin_mut;
use futures_util::stream;
impl Body { impl Body {
pub(crate) fn get_ref(&self) -> &[u8] { pub(crate) fn get_ref(&self) -> &[u8] {
@ -612,10 +601,6 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_body_eq() { 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!( assert!(
Body::Bytes(Bytes::from_static(b"1")) Body::Bytes(Bytes::from_static(b"1"))
== 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() { async fn test_body_debug() {
assert!(format!("{:?}", Body::None).contains("Body::None")); assert!(format!("{:?}", Body::None).contains("Body::None"));
assert!(format!("{:?}", Body::Empty).contains("Body::Empty")); 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] #[actix_rt::test]

View File

@ -37,10 +37,10 @@ where
trace!("Sending client request: {:?} {:?}", head, body.size()); trace!("Sending client request: {:?} {:?}", head, body.size());
let head_req = head.as_ref().method == Method::HEAD; let head_req = head.as_ref().method == Method::HEAD;
let length = body.size(); let length = body.size();
let eof = match length { let eof = matches!(
BodySize::None | BodySize::Empty | BodySize::Sized(0) => true, length,
_ => false, BodySize::None | BodySize::Empty | BodySize::Sized(0)
}; );
let mut req = Request::new(()); let mut req = Request::new(());
*req.uri_mut() = head.as_ref().uri.clone(); *req.uri_mut() = head.as_ref().uri.clone();

View File

@ -2,7 +2,7 @@ use std::cell::RefCell;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::future::Future; use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
use std::rc::{Rc, Weak}; use std::rc::Rc;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
@ -65,14 +65,11 @@ where
// start support future // start support future
actix_rt::spawn(ConnectorPoolSupport { actix_rt::spawn(ConnectorPoolSupport {
connector: connector_rc.clone(), connector: Rc::clone(&connector_rc),
inner: Rc::downgrade(&inner_rc), inner: Rc::clone(&inner_rc),
}); });
ConnectionPool( ConnectionPool(connector_rc, inner_rc)
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> impl<T, Io> Service for ConnectionPool<T, Io>
where where
Io: AsyncRead + AsyncWrite + Unpin + 'static, Io: AsyncRead + AsyncWrite + Unpin + 'static,
@ -424,7 +428,7 @@ where
Io: AsyncRead + AsyncWrite + Unpin + 'static, Io: AsyncRead + AsyncWrite + Unpin + 'static,
{ {
connector: T, connector: T,
inner: Weak<RefCell<Inner<Io>>>, inner: Rc<RefCell<Inner<Io>>>,
} }
impl<T, Io> Future for ConnectorPoolSupport<T, 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> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project(); let this = self.project();
if let Some(this_inner) = this.inner.upgrade() { if Rc::strong_count(this.inner) == 1 {
let mut inner = this_inner.as_ref().borrow_mut(); // 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()); inner.waker.register(cx.waker());
// check waiters // check waiters
@ -462,7 +471,7 @@ where
if let Err(conn) = tx.send(Ok(IoConnection::new( if let Err(conn) = tx.send(Ok(IoConnection::new(
io, io,
created, created,
Some(Acquired(key.clone(), Some(this_inner.clone()))), Some(Acquired(key.clone(), Some(this.inner.clone()))),
))) { ))) {
let (io, created) = conn.unwrap().into_inner(); let (io, created) = conn.unwrap().into_inner();
inner.release_conn(&key, io, created); inner.release_conn(&key, io, created);
@ -474,7 +483,7 @@ where
OpenWaitingConnection::spawn( OpenWaitingConnection::spawn(
key.clone(), key.clone(),
tx, tx,
this_inner.clone(), this.inner.clone(),
this.connector.call(connect), this.connector.call(connect),
inner.config.clone(), inner.config.clone(),
); );
@ -484,9 +493,6 @@ where
} }
Poll::Pending Poll::Pending
} else {
Poll::Ready(())
}
} }
} }

View File

@ -964,7 +964,6 @@ impl ResponseError for actix::actors::resolver::ResolverError {}
mod tests { mod tests {
use super::*; use super::*;
use http::{Error as HttpError, StatusCode}; use http::{Error as HttpError, StatusCode};
use httparse;
use std::io; use std::io;
#[test] #[test]

View File

@ -1,7 +1,6 @@
use std::convert::TryFrom; use std::convert::TryFrom;
use std::io; use std::io;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::mem::MaybeUninit;
use std::task::Poll; use std::task::Poll;
use actix_codec::Decoder; use actix_codec::Decoder;
@ -46,7 +45,7 @@ impl<T: MessageType> Decoder for MessageDecoder<T> {
pub(crate) enum PayloadLength { pub(crate) enum PayloadLength {
Payload(PayloadType), Payload(PayloadType),
Upgrade, UpgradeWebSocket,
None, None,
} }
@ -65,7 +64,7 @@ pub(crate) trait MessageType: Sized {
raw_headers: &[HeaderIndex], raw_headers: &[HeaderIndex],
) -> Result<PayloadLength, ParseError> { ) -> Result<PayloadLength, ParseError> {
let mut ka = None; let mut ka = None;
let mut has_upgrade = false; let mut has_upgrade_websocket = false;
let mut expect = false; let mut expect = false;
let mut chunked = false; let mut chunked = false;
let mut content_length = None; let mut content_length = None;
@ -77,7 +76,7 @@ pub(crate) trait MessageType: Sized {
let name = let name =
HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]).unwrap(); 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 { let value = unsafe {
HeaderValue::from_maybe_shared_unchecked( HeaderValue::from_maybe_shared_unchecked(
slice.slice(idx.value.0..idx.value.1), slice.slice(idx.value.0..idx.value.1),
@ -124,12 +123,9 @@ pub(crate) trait MessageType: Sized {
}; };
} }
header::UPGRADE => { 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 let Ok(val) = value.to_str().map(|val| val.trim()) {
if val.eq_ignore_ascii_case("websocket") { 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( Ok(PayloadLength::Payload(PayloadType::Payload(
PayloadDecoder::chunked(), PayloadDecoder::chunked(),
))) )))
} else if has_upgrade_websocket {
Ok(PayloadLength::UpgradeWebSocket)
} else if let Some(len) = content_length { } else if let Some(len) = content_length {
// Content-Length // Content-Length
Ok(PayloadLength::Payload(PayloadType::Payload( Ok(PayloadLength::Payload(PayloadType::Payload(
PayloadDecoder::length(len), PayloadDecoder::length(len),
))) )))
} else if has_upgrade {
Ok(PayloadLength::Upgrade)
} else { } else {
Ok(PayloadLength::None) Ok(PayloadLength::None)
} }
@ -184,16 +180,11 @@ impl MessageType for Request {
&mut self.head_mut().headers &mut self.head_mut().headers
} }
#[allow(clippy::uninit_assumed_init)]
fn decode(src: &mut BytesMut) -> Result<Option<(Self, PayloadType)>, ParseError> { fn decode(src: &mut BytesMut) -> Result<Option<(Self, PayloadType)>, ParseError> {
// Unsafe: we read only this data only after httparse parses headers into. let mut headers: [HeaderIndex; MAX_HEADERS] = EMPTY_HEADER_INDEX_ARRAY;
// performance bump for pipeline benchmarks.
let mut headers: [HeaderIndex; MAX_HEADERS] =
unsafe { MaybeUninit::uninit().assume_init() };
let (len, method, uri, ver, h_len) = { let (len, method, uri, ver, h_len) = {
let mut parsed: [httparse::Header<'_>; MAX_HEADERS] = let mut parsed: [httparse::Header<'_>; MAX_HEADERS] = EMPTY_HEADER_ARRAY;
unsafe { MaybeUninit::uninit().assume_init() };
let mut req = httparse::Request::new(&mut parsed); let mut req = httparse::Request::new(&mut parsed);
match req.parse(src)? { match req.parse(src)? {
@ -222,7 +213,7 @@ impl MessageType for Request {
// payload decoder // payload decoder
let decoder = match length { let decoder = match length {
PayloadLength::Payload(pl) => pl, PayloadLength::Payload(pl) => pl,
PayloadLength::Upgrade => { PayloadLength::UpgradeWebSocket => {
// upgrade(websocket) // upgrade(websocket)
PayloadType::Stream(PayloadDecoder::eof()) PayloadType::Stream(PayloadDecoder::eof())
} }
@ -260,16 +251,11 @@ impl MessageType for ResponseHead {
&mut self.headers &mut self.headers
} }
#[allow(clippy::uninit_assumed_init)]
fn decode(src: &mut BytesMut) -> Result<Option<(Self, PayloadType)>, ParseError> { fn decode(src: &mut BytesMut) -> Result<Option<(Self, PayloadType)>, ParseError> {
// Unsafe: we read only this data only after httparse parses headers into. let mut headers: [HeaderIndex; MAX_HEADERS] = EMPTY_HEADER_INDEX_ARRAY;
// performance bump for pipeline benchmarks.
let mut headers: [HeaderIndex; MAX_HEADERS] =
unsafe { MaybeUninit::uninit().assume_init() };
let (len, ver, status, h_len) = { let (len, ver, status, h_len) = {
let mut parsed: [httparse::Header<'_>; MAX_HEADERS] = let mut parsed: [httparse::Header<'_>; MAX_HEADERS] = EMPTY_HEADER_ARRAY;
unsafe { MaybeUninit::uninit().assume_init() };
let mut res = httparse::Response::new(&mut parsed); let mut res = httparse::Response::new(&mut parsed);
match res.parse(src)? { match res.parse(src)? {
@ -324,6 +310,17 @@ pub(crate) struct HeaderIndex {
pub(crate) value: (usize, usize), 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 { impl HeaderIndex {
pub(crate) fn record( pub(crate) fn record(
bytes: &[u8], bytes: &[u8],
@ -655,10 +652,7 @@ mod tests {
} }
fn is_unhandled(&self) -> bool { fn is_unhandled(&self) -> bool {
match self { matches!(self, PayloadType::Stream(_))
PayloadType::Stream(_) => true,
_ => false,
}
} }
} }
@ -670,10 +664,7 @@ mod tests {
} }
} }
fn eof(&self) -> bool { fn eof(&self) -> bool {
match *self { matches!(*self, PayloadItem::Eof)
PayloadItem::Eof => true,
_ => false,
}
} }
} }
@ -979,7 +970,7 @@ mod tests {
unreachable!("Error"); unreachable!("Error");
} }
// type in chunked // intentional typo in "chunked"
let mut buf = BytesMut::from( let mut buf = BytesMut::from(
"GET /test HTTP/1.1\r\n\ "GET /test HTTP/1.1\r\n\
transfer-encoding: chnked\r\n\r\n", transfer-encoding: chnked\r\n\r\n",
@ -1040,7 +1031,7 @@ mod tests {
} }
#[test] #[test]
fn test_http_request_upgrade() { fn test_http_request_upgrade_websocket() {
let mut buf = BytesMut::from( let mut buf = BytesMut::from(
"GET /test HTTP/1.1\r\n\ "GET /test HTTP/1.1\r\n\
connection: upgrade\r\n\ connection: upgrade\r\n\
@ -1054,6 +1045,26 @@ mod tests {
assert!(pl.is_unhandled()); 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] #[test]
fn test_http_request_parser_utf8() { fn test_http_request_parser_utf8() {
let mut buf = BytesMut::from( let mut buf = BytesMut::from(

View File

@ -132,19 +132,11 @@ where
B: MessageBody, B: MessageBody,
{ {
fn is_empty(&self) -> bool { fn is_empty(&self) -> bool {
if let State::None = self { matches!(self, State::None)
true
} else {
false
}
} }
fn is_call(&self) -> bool { fn is_call(&self) -> bool {
if let State::ServiceCall(_) = self { matches!(self, State::ServiceCall(_))
true
} else {
false
}
} }
} }
enum PollResponse { enum PollResponse {
@ -156,14 +148,8 @@ enum PollResponse {
impl PartialEq for PollResponse { impl PartialEq for PollResponse {
fn eq(&self, other: &PollResponse) -> bool { fn eq(&self, other: &PollResponse) -> bool {
match self { match self {
PollResponse::DrainWriteBuf => match other { PollResponse::DrainWriteBuf => matches!(other, PollResponse::DrainWriteBuf),
PollResponse::DrainWriteBuf => true, PollResponse::DoNothing => matches!(other, PollResponse::DoNothing),
_ => false,
},
PollResponse::DoNothing => match other {
PollResponse::DoNothing => true,
_ => false,
},
_ => false, _ => false,
} }
} }

View File

@ -303,7 +303,8 @@ where
} }
} }
}, },
ServiceResponseStateProj::SendPayload(ref mut stream, ref mut body) => loop { ServiceResponseStateProj::SendPayload(ref mut stream, ref mut body) => {
loop {
loop { loop {
if let Some(ref mut buffer) = this.buffer { if let Some(ref mut buffer) = this.buffer {
match stream.poll_capacity(cx) { match stream.poll_capacity(cx) {
@ -317,7 +318,8 @@ where
warn!("{:?}", e); warn!("{:?}", e);
return Poll::Ready(()); return Poll::Ready(());
} else if !buffer.is_empty() { } 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); stream.reserve_capacity(cap);
} else { } else {
this.buffer.take(); this.buffer.take();
@ -332,7 +334,8 @@ where
match body.as_mut().poll_next(cx) { match body.as_mut().poll_next(cx) {
Poll::Pending => return Poll::Pending, Poll::Pending => return Poll::Pending,
Poll::Ready(None) => { 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); warn!("{:?}", e);
} }
return Poll::Ready(()); return Poll::Ready(());
@ -351,7 +354,8 @@ where
} }
} }
} }
}, }
}
} }
} }
} }

View File

@ -1,5 +1,5 @@
use http::Method;
use http::header; use http::header;
use http::Method;
header! { header! {
/// `Allow` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.4.1) /// `Allow` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.4.1)

View File

@ -387,26 +387,17 @@ impl ContentDisposition {
/// Returns `true` if it is [`Inline`](DispositionType::Inline). /// Returns `true` if it is [`Inline`](DispositionType::Inline).
pub fn is_inline(&self) -> bool { pub fn is_inline(&self) -> bool {
match self.disposition { matches!(self.disposition, DispositionType::Inline)
DispositionType::Inline => true,
_ => false,
}
} }
/// Returns `true` if it is [`Attachment`](DispositionType::Attachment). /// Returns `true` if it is [`Attachment`](DispositionType::Attachment).
pub fn is_attachment(&self) -> bool { pub fn is_attachment(&self) -> bool {
match self.disposition { matches!(self.disposition, DispositionType::Attachment)
DispositionType::Attachment => true,
_ => false,
}
} }
/// Returns `true` if it is [`FormData`](DispositionType::FormData). /// Returns `true` if it is [`FormData`](DispositionType::FormData).
pub fn is_form_data(&self) -> bool { pub fn is_form_data(&self) -> bool {
match self.disposition { matches!(self.disposition, DispositionType::FormData)
DispositionType::FormData => true,
_ => false,
}
} }
/// Returns `true` if it is [`Ext`](DispositionType::Ext) and the `disp_type` matches. /// Returns `true` if it is [`Ext`](DispositionType::Ext) and the `disp_type` matches.

View File

@ -9,11 +9,13 @@
pub use self::accept_charset::AcceptCharset; pub use self::accept_charset::AcceptCharset;
//pub use self::accept_encoding::AcceptEncoding; //pub use self::accept_encoding::AcceptEncoding;
pub use self::accept_language::AcceptLanguage;
pub use self::accept::Accept; pub use self::accept::Accept;
pub use self::accept_language::AcceptLanguage;
pub use self::allow::Allow; pub use self::allow::Allow;
pub use self::cache_control::{CacheControl, CacheDirective}; 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_language::ContentLanguage;
pub use self::content_range::{ContentRange, ContentRangeSpec}; pub use self::content_range::{ContentRange, ContentRangeSpec};
pub use self::content_type::ContentType; pub use self::content_type::ContentType;
@ -47,7 +49,7 @@ macro_rules! __hyper__deref {
&mut self.0 &mut self.0
} }
} }
} };
} }
#[doc(hidden)] #[doc(hidden)]
@ -74,8 +76,8 @@ macro_rules! test_header {
($id:ident, $raw:expr) => { ($id:ident, $raw:expr) => {
#[test] #[test]
fn $id() { fn $id() {
use $crate::test;
use super::*; use super::*;
use $crate::test;
let raw = $raw; let raw = $raw;
let a: Vec<Vec<u8>> = raw.iter().map(|x| x.to_vec()).collect(); let a: Vec<Vec<u8>> = raw.iter().map(|x| x.to_vec()).collect();
@ -128,7 +130,7 @@ macro_rules! test_header {
assert_eq!(format!("{}", typed.unwrap()), joined); assert_eq!(format!("{}", typed.unwrap()), joined);
} }
} }
} };
} }
#[macro_export] #[macro_export]
@ -330,11 +332,10 @@ macro_rules! header {
}; };
} }
mod accept_charset; mod accept_charset;
//mod accept_encoding; //mod accept_encoding;
mod accept_language;
mod accept; mod accept;
mod accept_language;
mod allow; mod allow;
mod cache_control; mod cache_control;
mod content_disposition; mod content_disposition;

View File

@ -148,10 +148,7 @@ impl ContentEncoding {
#[inline] #[inline]
/// Is the content compressed? /// Is the content compressed?
pub fn is_compression(self) -> bool { pub fn is_compression(self) -> bool {
match self { matches!(self, ContentEncoding::Identity | ContentEncoding::Auto)
ContentEncoding::Identity | ContentEncoding::Auto => false,
_ => true,
}
} }
#[inline] #[inline]

View File

@ -167,7 +167,6 @@ where
mod tests { mod tests {
use bytes::Bytes; use bytes::Bytes;
use encoding_rs::ISO_8859_2; use encoding_rs::ISO_8859_2;
use mime;
use super::*; use super::*;
use crate::test::TestRequest; use crate::test::TestRequest;

View File

@ -229,10 +229,7 @@ mod tests {
fn is_none( fn is_none(
frm: &Result<Option<(bool, OpCode, Option<BytesMut>)>, ProtocolError>, frm: &Result<Option<(bool, OpCode, Option<BytesMut>)>, ProtocolError>,
) -> bool { ) -> bool {
match *frm { matches!(*frm, Ok(None))
Ok(None) => true,
_ => false,
}
} }
fn extract( fn extract(

View File

@ -139,7 +139,7 @@ mod tests {
let mut masked = unmasked.clone(); let mut masked = unmasked.clone();
apply_mask_fallback(&mut masked[1..], &mask); 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); apply_mask(&mut masked_fast[1..], mask_u32);
assert_eq!(masked, masked_fast); assert_eq!(masked, masked_fast);

View File

@ -274,9 +274,7 @@ async fn test_h2_head_empty() {
async fn test_h2_head_binary() { async fn test_h2_head_binary() {
let mut srv = test_server(move || { let mut srv = test_server(move || {
HttpService::build() HttpService::build()
.h2(|_| { .h2(|_| ok::<_, ()>(Response::Ok().body(STR)))
ok::<_, ()>(Response::Ok().body(STR))
})
.openssl(ssl_acceptor()) .openssl(ssl_acceptor())
.map_err(|_| ()) .map_err(|_| ())
}) })

View File

@ -280,9 +280,7 @@ async fn test_h2_head_empty() {
async fn test_h2_head_binary() { async fn test_h2_head_binary() {
let mut srv = test_server(move || { let mut srv = test_server(move || {
HttpService::build() HttpService::build()
.h2(|_| { .h2(|_| ok::<_, ()>(Response::Ok().body(STR)))
ok::<_, ()>(Response::Ok().body(STR))
})
.rustls(ssl_acceptor()) .rustls(ssl_acceptor())
}) })
.await; .await;

View File

@ -489,9 +489,7 @@ async fn test_h1_head_empty() {
async fn test_h1_head_binary() { async fn test_h1_head_binary() {
let mut srv = test_server(|| { let mut srv = test_server(|| {
HttpService::build() HttpService::build()
.h1(|_| { .h1(|_| ok::<_, ()>(Response::Ok().body(STR)))
ok::<_, ()>(Response::Ok().body(STR))
})
.tcp() .tcp()
}) })
.await; .await;

View File

@ -1,15 +1,17 @@
# Changes # 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 * Update `actix-web` to 3.0.0-alpha.3
* Bump minimum supported Rust version to 1.40 * Bump minimum supported Rust version to 1.40
* Minimize `futures` dependencies * Minimize `futures` dependencies
* Remove the unused `time` dependency * Remove the unused `time` dependency
* Fix missing `std::error::Error` implement for `MultipartError`. * Fix missing `std::error::Error` implement for `MultipartError`.
## [0.2.0] - 2019-12-20 ## [0.2.0] - 2019-12-20

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-multipart" name = "actix-multipart"
version = "0.3.0-alpha.1" version = "0.3.0-beta.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Multipart support for actix web framework." description = "Multipart support for actix web framework."
readme = "README.md" readme = "README.md"
@ -8,7 +8,7 @@ keywords = ["http", "web", "framework", "async", "futures"]
homepage = "https://actix.rs" homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web.git" repository = "https://github.com/actix/actix-web.git"
documentation = "https://docs.rs/actix-multipart/" documentation = "https://docs.rs/actix-multipart/"
license = "MIT/Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2018" edition = "2018"
[lib] [lib]
@ -16,7 +16,7 @@ name = "actix_multipart"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [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-service = "1.0.1"
actix-utils = "1.0.3" actix-utils = "1.0.3"
bytes = "0.5.3" bytes = "0.5.3"
@ -29,4 +29,4 @@ twoway = "0.2"
[dev-dependencies] [dev-dependencies]
actix-rt = "1.0.0" actix-rt = "1.0.0"
actix-http = "2.0.0-alpha.4" actix-http = "2.0.0-beta.3"

View File

@ -9,8 +9,6 @@ use std::{cmp, fmt};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures_util::stream::{LocalBoxStream, Stream, StreamExt}; use futures_util::stream::{LocalBoxStream, Stream, StreamExt};
use httparse;
use mime;
use actix_utils::task::LocalWaker; use actix_utils::task::LocalWaker;
use actix_web::error::{ParseError, PayloadError}; use actix_web::error::{ParseError, PayloadError};
@ -876,11 +874,11 @@ mod tests {
impl SlowStream { impl SlowStream {
fn new(bytes: Bytes) -> SlowStream { fn new(bytes: Bytes) -> SlowStream {
return SlowStream { SlowStream {
bytes: bytes, bytes,
pos: 0, pos: 0,
ready: false, ready: false,
}; }
} }
} }

View File

@ -2,10 +2,13 @@
## [Unreleased] - 2020-xx-xx ## [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 * 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-web dependency to 3.0.0-alpha.1
* Update the actix dependency to 0.10.0-alpha.2 * Update the actix dependency to 0.10.0-alpha.2
* Update the actix-http dependency to 2.0.0-alpha.3 * Update the actix-http dependency to 2.0.0-alpha.3

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-web-actors" name = "actix-web-actors"
version = "3.0.0-alpha.1" version = "3.0.0-beta.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix actors support for actix web framework." description = "Actix actors support for actix web framework."
readme = "README.md" readme = "README.md"
@ -8,7 +8,7 @@ keywords = ["actix", "http", "web", "framework", "async"]
homepage = "https://actix.rs" homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web.git" repository = "https://github.com/actix/actix-web.git"
documentation = "https://docs.rs/actix-web-actors/" documentation = "https://docs.rs/actix-web-actors/"
license = "MIT/Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2018" edition = "2018"
[lib] [lib]
@ -17,8 +17,8 @@ path = "src/lib.rs"
[dependencies] [dependencies]
actix = "0.10.0-alpha.2" actix = "0.10.0-alpha.2"
actix-web = { version = "3.0.0-alpha.3", default-features = false } actix-web = { version = "3.0.0-beta.1", default-features = false }
actix-http = "2.0.0-alpha.4" actix-http = "2.0.0-beta.3"
actix-codec = "0.2.0" actix-codec = "0.2.0"
bytes = "0.5.2" bytes = "0.5.2"
futures-channel = { version = "0.3.5", default-features = false } futures-channel = { version = "0.3.5", default-features = false }

View File

@ -30,8 +30,8 @@ impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for Ws {
async fn test_simple() { async fn test_simple() {
let mut srv = test::start(|| { let mut srv = test::start(|| {
App::new().service(web::resource("/").to( App::new().service(web::resource("/").to(
|req: HttpRequest, stream: web::Payload| { |req: HttpRequest, stream: web::Payload| async move {
async move { ws::start(Ws, &req, stream) } ws::start(Ws, &req, stream)
}, },
)) ))
}); });
@ -51,7 +51,7 @@ async fn test_simple() {
.await .await
.unwrap(); .unwrap();
let item = framed.next().await.unwrap().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(); framed.send(ws::Message::Ping("text".into())).await.unwrap();
let item = framed.next().await.unwrap().unwrap(); let item = framed.next().await.unwrap().unwrap();

View File

@ -1,18 +1,20 @@
# Changes # 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 ## [0.2.2] - 2020-05-23
* Add resource middleware on actix-web-codegen [#1467] * Add resource middleware on actix-web-codegen [#1467]
[#1467]: https://github.com/actix/actix-web/pull/1467 [#1467]: https://github.com/actix/actix-web/pull/1467
## [0.2.1] - 2020-02-25 ## [0.2.1] - 2020-02-25
* Add `#[allow(missing_docs)]` attribute to generated structs [#1368] * Add `#[allow(missing_docs)]` attribute to generated structs [#1368]
* Allow the handler function to be named as `config` [#1290] * Allow the handler function to be named as `config` [#1290]
@ -26,7 +28,6 @@
## [0.1.3] - 2019-10-14 ## [0.1.3] - 2019-10-14
* Bump up `syn` & `quote` to 1.0 * Bump up `syn` & `quote` to 1.0
* Provide better error message * Provide better error message
## [0.1.2] - 2019-06-04 ## [0.1.2] - 2019-06-04

View File

@ -1,13 +1,13 @@
[package] [package]
name = "actix-web-codegen" name = "actix-web-codegen"
version = "0.2.2" version = "0.3.0-beta.1"
description = "Actix web proc macros" description = "Actix web proc macros"
readme = "README.md" readme = "README.md"
homepage = "https://actix.rs" homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web" repository = "https://github.com/actix/actix-web"
documentation = "https://docs.rs/actix-web-codegen" documentation = "https://docs.rs/actix-web-codegen"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
license = "MIT/Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2018" edition = "2018"
[lib] [lib]
@ -20,5 +20,5 @@ proc-macro2 = "1"
[dev-dependencies] [dev-dependencies]
actix-rt = "1.0.0" 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 } futures-util = { version = "0.3.5", default-features = false }

View File

@ -3,7 +3,7 @@ extern crate proc_macro;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2}; use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{format_ident, quote, ToTokens, TokenStreamExt}; 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 { enum ResourceType {
Async, Async,
@ -196,7 +196,12 @@ impl ToTokens for Route {
name, name,
guard, guard,
ast, ast,
args: Args { path, guards, wrappers }, args:
Args {
path,
guards,
wrappers,
},
resource_type, resource_type,
} = self; } = self;
let resource_name = name.to_string(); let resource_name = name.to_string();

View File

@ -2,11 +2,11 @@ use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use actix_web::{http, test, web::Path, App, HttpResponse, Responder, Error}; use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform};
use actix_web::dev::{Service, Transform, ServiceRequest, ServiceResponse}; 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 actix_web_codegen::{connect, delete, get, head, options, patch, post, put, trace};
use futures_util::future; use futures_util::future;
use actix_web::http::header::{HeaderName, HeaderValue};
// Make sure that we can name function as 'config' // Make sure that we can name function as 'config'
#[get("/config")] #[get("/config")]
@ -112,6 +112,7 @@ where
type Request = ServiceRequest; type Request = ServiceRequest;
type Response = ServiceResponse<B>; type Response = ServiceResponse<B>;
type Error = Error; type Error = Error;
#[allow(clippy::type_complexity)]
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>; type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), 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 { fn call(&mut self, req: ServiceRequest) -> Self::Future {
let fut = self.service.call(req); let fut = self.service.call(req);
Box::pin(async move { Box::pin(async move {
@ -223,10 +223,7 @@ async fn test_auto_async() {
#[actix_rt::test] #[actix_rt::test]
async fn test_wrap() { async fn test_wrap() {
let srv = test::start(|| { let srv = test::start(|| App::new().service(get_wrap));
App::new()
.service(get_wrap)
});
let request = srv.request(http::Method::GET, srv.url("/test/wrap")); let request = srv.request(http::Method::GET, srv.url("/test/wrap"));
let response = request.send().await.unwrap(); let response = request.send().await.unwrap();

View File

@ -1,5 +1,17 @@
# Changes # 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 ## [2.0.0-alpha.2] - 2020-05-21
### Changed ### Changed

View File

@ -1,17 +1,20 @@
[package] [package]
name = "awc" name = "awc"
version = "2.0.0-alpha.2" version = "2.0.0-beta.2"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix http client." description = "Async HTTP client library that uses the Actix runtime."
readme = "README.md" readme = "README.md"
keywords = ["actix", "http", "framework", "async", "web"] keywords = ["actix", "http", "framework", "async", "web"]
homepage = "https://actix.rs" homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web.git" repository = "https://github.com/actix/actix-web.git"
documentation = "https://docs.rs/awc/" documentation = "https://docs.rs/awc/"
categories = ["network-programming", "asynchronous", categories = [
"network-programming",
"asynchronous",
"web-programming::http-client", "web-programming::http-client",
"web-programming::websocket"] "web-programming::websocket",
license = "MIT/Apache-2.0" ]
license = "MIT OR Apache-2.0"
edition = "2018" edition = "2018"
[lib] [lib]
@ -36,7 +39,7 @@ compress = ["actix-http/compress"]
[dependencies] [dependencies]
actix-codec = "0.2.0" actix-codec = "0.2.0"
actix-service = "1.0.1" actix-service = "1.0.1"
actix-http = "2.0.0-alpha.4" actix-http = "2.0.0-beta.3"
actix-rt = "1.0.0" actix-rt = "1.0.0"
base64 = "0.12" base64 = "0.12"
@ -56,7 +59,7 @@ rust-tls = { version = "0.17.0", package = "rustls", optional = true, features =
[dev-dependencies] [dev-dependencies]
actix-connect = { version = "2.0.0-alpha.2", features = ["openssl"] } actix-connect = { version = "2.0.0-alpha.2", features = ["openssl"] }
actix-web = { version = "3.0.0-alpha.3", 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-http-test = { version = "2.0.0-alpha.1", features = ["openssl"] }
actix-utils = "1.0.3" actix-utils = "1.0.3"
actix-server = "1.0.0" actix-server = "1.0.0"

View File

@ -402,14 +402,12 @@ mod tests {
fn json_eq(err: JsonPayloadError, other: JsonPayloadError) -> bool { fn json_eq(err: JsonPayloadError, other: JsonPayloadError) -> bool {
match err { match err {
JsonPayloadError::Payload(PayloadError::Overflow) => match other { JsonPayloadError::Payload(PayloadError::Overflow) => {
JsonPayloadError::Payload(PayloadError::Overflow) => true, matches!(other, JsonPayloadError::Payload(PayloadError::Overflow))
_ => false, }
}, JsonPayloadError::ContentType => {
JsonPayloadError::ContentType => match other { matches!(other, JsonPayloadError::ContentType)
JsonPayloadError::ContentType => true, }
_ => false,
},
_ => false, _ => false,
} }
} }

View File

@ -167,8 +167,7 @@ async fn test_connection_reuse() {
}) })
.and_then( .and_then(
HttpService::new(map_config( HttpService::new(map_config(
App::new() App::new().service(web::resource("/").route(web::to(HttpResponse::Ok))),
.service(web::resource("/").route(web::to(|| HttpResponse::Ok()))),
|_| AppConfig::default(), |_| AppConfig::default(),
)) ))
.tcp(), .tcp(),
@ -205,8 +204,7 @@ async fn test_connection_force_close() {
}) })
.and_then( .and_then(
HttpService::new(map_config( HttpService::new(map_config(
App::new() App::new().service(web::resource("/").route(web::to(HttpResponse::Ok))),
.service(web::resource("/").route(web::to(|| HttpResponse::Ok()))),
|_| AppConfig::default(), |_| AppConfig::default(),
)) ))
.tcp(), .tcp(),

View File

@ -32,8 +32,7 @@ async fn test_connection_window_size() {
let srv = test_server(move || { let srv = test_server(move || {
HttpService::build() HttpService::build()
.h2(map_config( .h2(map_config(
App::new() App::new().service(web::resource("/").route(web::to(HttpResponse::Ok))),
.service(web::resource("/").route(web::to(|| HttpResponse::Ok()))),
|_| AppConfig::default(), |_| AppConfig::default(),
)) ))
.openssl(ssl_acceptor()) .openssl(ssl_acceptor())

View File

@ -64,9 +64,8 @@ async fn _test_connection_reuse_h2() {
.and_then( .and_then(
HttpService::build() HttpService::build()
.h2(map_config( .h2(map_config(
App::new().service( App::new()
web::resource("/").route(web::to(|| HttpResponse::Ok())), .service(web::resource("/").route(web::to(HttpResponse::Ok))),
),
|_| AppConfig::default(), |_| AppConfig::default(),
)) ))
.openssl(ssl_acceptor()) .openssl(ssl_acceptor())

View File

@ -45,9 +45,8 @@ async fn test_connection_reuse_h2() {
.and_then( .and_then(
HttpService::build() HttpService::build()
.h2(map_config( .h2(map_config(
App::new().service( App::new()
web::resource("/").route(web::to(|| HttpResponse::Ok())), .service(web::resource("/").route(web::to(HttpResponse::Ok))),
),
|_| AppConfig::default(), |_| AppConfig::default(),
)) ))
.openssl(ssl_acceptor()) .openssl(ssl_acceptor())

2
docs/graphs/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
# do not track rendered graphs
*.png

View 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
View 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
View 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
View 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" }
}

View File

@ -1 +1 @@
1.41.1 1.42.0

View File

@ -489,7 +489,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_default_resource() { async fn test_default_resource() {
let mut srv = init_service( let mut srv = init_service(
App::new().service(web::resource("/test").to(|| HttpResponse::Ok())), App::new().service(web::resource("/test").to(HttpResponse::Ok)),
) )
.await; .await;
let req = TestRequest::with_uri("/test").to_request(); let req = TestRequest::with_uri("/test").to_request();
@ -502,13 +502,13 @@ mod tests {
let mut srv = init_service( let mut srv = init_service(
App::new() App::new()
.service(web::resource("/test").to(|| HttpResponse::Ok())) .service(web::resource("/test").to(HttpResponse::Ok))
.service( .service(
web::resource("/test2") web::resource("/test2")
.default_service(|r: ServiceRequest| { .default_service(|r: ServiceRequest| {
ok(r.into_response(HttpResponse::Created())) ok(r.into_response(HttpResponse::Created()))
}) })
.route(web::get().to(|| HttpResponse::Ok())), .route(web::get().to(HttpResponse::Ok)),
) )
.default_service(|r: ServiceRequest| { .default_service(|r: ServiceRequest| {
ok(r.into_response(HttpResponse::MethodNotAllowed())) ok(r.into_response(HttpResponse::MethodNotAllowed()))
@ -585,7 +585,7 @@ mod tests {
DefaultHeaders::new() DefaultHeaders::new()
.header(header::CONTENT_TYPE, HeaderValue::from_static("0001")), .header(header::CONTENT_TYPE, HeaderValue::from_static("0001")),
) )
.route("/test", web::get().to(|| HttpResponse::Ok())), .route("/test", web::get().to(HttpResponse::Ok)),
) )
.await; .await;
let req = TestRequest::with_uri("/test").to_request(); let req = TestRequest::with_uri("/test").to_request();
@ -601,7 +601,7 @@ mod tests {
async fn test_router_wrap() { async fn test_router_wrap() {
let mut srv = init_service( let mut srv = init_service(
App::new() App::new()
.route("/test", web::get().to(|| HttpResponse::Ok())) .route("/test", web::get().to(HttpResponse::Ok))
.wrap( .wrap(
DefaultHeaders::new() DefaultHeaders::new()
.header(header::CONTENT_TYPE, HeaderValue::from_static("0001")), .header(header::CONTENT_TYPE, HeaderValue::from_static("0001")),
@ -632,7 +632,7 @@ mod tests {
Ok(res) Ok(res)
} }
}) })
.service(web::resource("/test").to(|| HttpResponse::Ok())), .service(web::resource("/test").to(HttpResponse::Ok)),
) )
.await; .await;
let req = TestRequest::with_uri("/test").to_request(); let req = TestRequest::with_uri("/test").to_request();
@ -648,7 +648,7 @@ mod tests {
async fn test_router_wrap_fn() { async fn test_router_wrap_fn() {
let mut srv = init_service( let mut srv = init_service(
App::new() App::new()
.route("/test", web::get().to(|| HttpResponse::Ok())) .route("/test", web::get().to(HttpResponse::Ok))
.wrap_fn(|req, srv| { .wrap_fn(|req, srv| {
let fut = srv.call(req); let fut = srv.call(req);
async { async {
@ -679,10 +679,9 @@ mod tests {
.route( .route(
"/test", "/test",
web::get().to(|req: HttpRequest| { web::get().to(|req: HttpRequest| {
HttpResponse::Ok().body(format!( HttpResponse::Ok().body(
"{}", req.url_for("youtube", &["12345"]).unwrap().to_string(),
req.url_for("youtube", &["12345"]).unwrap() )
))
}), }),
), ),
) )

View File

@ -10,6 +10,7 @@ use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url};
use actix_service::boxed::{self, BoxService, BoxServiceFactory}; use actix_service::boxed::{self, BoxService, BoxServiceFactory};
use actix_service::{fn_service, Service, ServiceFactory}; use actix_service::{fn_service, Service, ServiceFactory};
use futures_util::future::{join_all, ok, FutureExt, LocalBoxFuture}; use futures_util::future::{join_all, ok, FutureExt, LocalBoxFuture};
use tinyvec::tiny_vec;
use crate::config::{AppConfig, AppService}; use crate::config::{AppConfig, AppService};
use crate::data::{DataFactory, FnDataFactory}; use crate::data::{DataFactory, FnDataFactory};
@ -245,7 +246,7 @@ where
inner.path.reset(); inner.path.reset();
inner.head = head; inner.head = head;
inner.payload = payload; inner.payload = payload;
inner.app_data.push(self.data.clone()); inner.app_data = tiny_vec![self.data.clone()];
req req
} else { } else {
HttpRequest::new( HttpRequest::new(
@ -474,7 +475,7 @@ mod tests {
let mut app = init_service( let mut app = init_service(
App::new() App::new()
.data(DropData(data.clone())) .data(DropData(data.clone()))
.service(web::resource("/test").to(|| HttpResponse::Ok())), .service(web::resource("/test").to(HttpResponse::Ok)),
) )
.await; .await;
let req = TestRequest::with_uri("/test").to_request(); let req = TestRequest::with_uri("/test").to_request();

View File

@ -311,10 +311,9 @@ mod tests {
.route( .route(
"/test", "/test",
web::get().to(|req: HttpRequest| { web::get().to(|req: HttpRequest| {
HttpResponse::Ok().body(format!( HttpResponse::Ok().body(
"{}", req.url_for("youtube", &["12345"]).unwrap().to_string(),
req.url_for("youtube", &["12345"]).unwrap() )
))
}), }),
), ),
) )
@ -330,9 +329,9 @@ mod tests {
async fn test_service() { async fn test_service() {
let mut srv = init_service(App::new().configure(|cfg| { let mut srv = init_service(App::new().configure(|cfg| {
cfg.service( 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; .await;

View File

@ -200,13 +200,13 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_route_data_extractor() { async fn test_route_data_extractor() {
let mut srv = let mut srv = init_service(
init_service(App::new().service(web::resource("/").data(10usize).route( App::new().service(
web::get().to(|data: web::Data<usize>| { web::resource("/")
let _ = data.clone(); .data(10usize)
HttpResponse::Ok() .route(web::get().to(|_data: web::Data<usize>| HttpResponse::Ok())),
}), ),
))) )
.await; .await;
let req = TestRequest::default().to_request(); let req = TestRequest::default().to_request();
@ -233,7 +233,6 @@ mod tests {
web::resource("/").data(10usize).route(web::get().to( web::resource("/").data(10usize).route(web::get().to(
|data: web::Data<usize>| { |data: web::Data<usize>| {
assert_eq!(**data, 10); assert_eq!(**data, 10);
let _ = data.clone();
HttpResponse::Ok() HttpResponse::Ok()
}, },
)), )),

View File

@ -9,8 +9,8 @@
//! use actix_web::{get, web, App, HttpServer, Responder}; //! use actix_web::{get, web, App, HttpServer, Responder};
//! //!
//! #[get("/{id}/{name}/index.html")] //! #[get("/{id}/{name}/index.html")]
//! async fn index(info: web::Path<(u32, String)>) -> impl Responder { //! async fn index(web::Path((id, name)): web::Path<(u32, String)>) -> impl Responder {
//! format!("Hello {}! id:{}", info.1, info.0) //! format!("Hello {}! id:{}", name, id)
//! } //! }
//! //!
//! #[actix_web::main] //! #[actix_web::main]
@ -213,9 +213,7 @@ pub mod client {
//! } //! }
//! ``` //! ```
pub use awc::error::{ pub use awc::error::*;
ConnectError, InvalidUrl, PayloadError, SendRequestError, WsClientError,
};
pub use awc::{ pub use awc::{
test, Client, ClientBuilder, ClientRequest, ClientResponse, Connector, test, Client, ClientBuilder, ClientRequest, ClientResponse, Connector,
}; };

View File

@ -626,7 +626,7 @@ mod tests {
Ok(()) Ok(())
}; };
let s = format!("{}", FormatDisplay(&render)); 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] #[actix_rt::test]

View File

@ -129,7 +129,7 @@ mod tests {
let mut app = init_service( let mut app = init_service(
App::new() App::new()
.wrap(NormalizePath::default()) .wrap(NormalizePath::default())
.service(web::resource("/v1/something/").to(|| HttpResponse::Ok())), .service(web::resource("/v1/something/").to(HttpResponse::Ok)),
) )
.await; .await;

View File

@ -276,6 +276,7 @@ impl HttpMessage for HttpRequest {
impl Drop for HttpRequest { impl Drop for HttpRequest {
fn drop(&mut self) { fn drop(&mut self) {
// if possible, contribute to current worker's HttpRequest allocation pool
if Rc::strong_count(&self.0) == 1 { if Rc::strong_count(&self.0) == 1 {
let v = &mut self.0.pool.0.borrow_mut(); let v = &mut self.0.pool.0.borrow_mut();
if v.len() < 128 { 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>>>); pub(crate) struct HttpRequestPool(RefCell<Vec<Rc<HttpRequestInner>>>);
impl HttpRequestPool { impl HttpRequestPool {
/// Allocates a slab of memory for pool use.
pub(crate) fn create() -> &'static HttpRequestPool { pub(crate) fn create() -> &'static HttpRequestPool {
let pool = HttpRequestPool(RefCell::new(Vec::with_capacity(128))); let pool = HttpRequestPool(RefCell::new(Vec::with_capacity(128)));
Box::leak(Box::new(pool)) Box::leak(Box::new(pool))
} }
/// Get message from the pool /// Re-use a previously allocated (but now completed/discarded) HttpRequest object.
#[inline] #[inline]
pub(crate) fn get_request(&self) -> Option<HttpRequest> { pub(crate) fn get_request(&self) -> Option<HttpRequest> {
if let Some(inner) = self.0.borrow_mut().pop() { self.0.borrow_mut().pop().map(HttpRequest)
Some(HttpRequest(inner))
} else {
None
}
} }
/// Clears all allocated HttpRequest objects.
pub(crate) fn clear(&self) { pub(crate) fn clear(&self) {
self.0.borrow_mut().clear() self.0.borrow_mut().clear()
} }

View File

@ -607,7 +607,7 @@ mod tests {
header::CONTENT_TYPE, header::CONTENT_TYPE,
HeaderValue::from_static("0001"), HeaderValue::from_static("0001"),
)) ))
.route(web::get().to(|| HttpResponse::Ok())), .route(web::get().to(HttpResponse::Ok)),
), ),
) )
.await; .await;
@ -637,7 +637,7 @@ mod tests {
}) })
} }
}) })
.route(web::get().to(|| HttpResponse::Ok())), .route(web::get().to(HttpResponse::Ok)),
), ),
) )
.await; .await;
@ -684,9 +684,7 @@ mod tests {
async fn test_default_resource() { async fn test_default_resource() {
let mut srv = init_service( let mut srv = init_service(
App::new() App::new()
.service( .service(web::resource("/test").route(web::get().to(HttpResponse::Ok)))
web::resource("/test").route(web::get().to(|| HttpResponse::Ok())),
)
.default_service(|r: ServiceRequest| { .default_service(|r: ServiceRequest| {
ok(r.into_response(HttpResponse::BadRequest())) ok(r.into_response(HttpResponse::BadRequest()))
}), }),
@ -705,7 +703,7 @@ mod tests {
let mut srv = init_service( let mut srv = init_service(
App::new().service( App::new().service(
web::resource("/test") web::resource("/test")
.route(web::get().to(|| HttpResponse::Ok())) .route(web::get().to(HttpResponse::Ok))
.default_service(|r: ServiceRequest| { .default_service(|r: ServiceRequest| {
ok(r.into_response(HttpResponse::BadRequest())) ok(r.into_response(HttpResponse::BadRequest()))
}), }),
@ -731,17 +729,17 @@ mod tests {
.service( .service(
web::resource("/test/{p}") web::resource("/test/{p}")
.guard(guard::Get()) .guard(guard::Get())
.to(|| HttpResponse::Ok()), .to(HttpResponse::Ok),
) )
.service( .service(
web::resource("/test/{p}") web::resource("/test/{p}")
.guard(guard::Put()) .guard(guard::Put())
.to(|| HttpResponse::Created()), .to(HttpResponse::Created),
) )
.service( .service(
web::resource("/test/{p}") web::resource("/test/{p}")
.guard(guard::Delete()) .guard(guard::Delete())
.to(|| HttpResponse::NoContent()), .to(HttpResponse::NoContent),
), ),
) )
.await; .await;
@ -783,7 +781,8 @@ mod tests {
data3: web::Data<f64>| { data3: web::Data<f64>| {
assert_eq!(**data1, 10); assert_eq!(**data1, 10);
assert_eq!(**data2, '*'); assert_eq!(**data2, '*');
assert_eq!(**data3, 1.0); let error = std::f64::EPSILON;
assert!((**data3 - 1.0).abs() < error);
HttpResponse::Ok() HttpResponse::Ok()
}, },
), ),

View File

@ -480,7 +480,7 @@ pub(crate) mod tests {
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
match resp.response().body() { match resp.response().body() {
ResponseBody::Body(Body::Bytes(ref b)) => { ResponseBody::Body(Body::Bytes(ref b)) => {
let bytes: Bytes = b.clone().into(); let bytes = b.clone();
assert_eq!(bytes, Bytes::from_static(b"some")); assert_eq!(bytes, Bytes::from_static(b"some"));
} }
_ => panic!(), _ => panic!(),

View File

@ -362,7 +362,7 @@ mod tests {
App::new() App::new()
.service( .service(
web::resource("/test") web::resource("/test")
.route(web::get().to(|| HttpResponse::Ok())) .route(web::get().to(HttpResponse::Ok))
.route(web::put().to(|| async { .route(web::put().to(|| async {
Err::<HttpResponse, _>(error::ErrorBadRequest("err")) Err::<HttpResponse, _>(error::ErrorBadRequest("err"))
})) }))

View File

@ -678,12 +678,9 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_scope() { async fn test_scope() {
let mut srv = init_service( let mut srv = init_service(App::new().service(
App::new().service( web::scope("/app").service(web::resource("/path1").to(HttpResponse::Ok)),
web::scope("/app") ))
.service(web::resource("/path1").to(|| HttpResponse::Ok())),
),
)
.await; .await;
let req = TestRequest::with_uri("/app/path1").to_request(); let req = TestRequest::with_uri("/app/path1").to_request();
@ -696,8 +693,8 @@ mod tests {
let mut srv = init_service( let mut srv = init_service(
App::new().service( App::new().service(
web::scope("/app") web::scope("/app")
.service(web::resource("").to(|| HttpResponse::Ok())) .service(web::resource("").to(HttpResponse::Ok))
.service(web::resource("/").to(|| HttpResponse::Created())), .service(web::resource("/").to(HttpResponse::Created)),
), ),
) )
.await; .await;
@ -714,7 +711,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_scope_root2() { async fn test_scope_root2() {
let mut srv = init_service(App::new().service( 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; .await;
@ -730,7 +727,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_scope_root3() { async fn test_scope_root3() {
let mut srv = init_service(App::new().service( 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; .await;
@ -748,8 +745,8 @@ mod tests {
let mut srv = init_service( let mut srv = init_service(
App::new().service( App::new().service(
web::scope("app") web::scope("app")
.route("/path1", web::get().to(|| HttpResponse::Ok())) .route("/path1", web::get().to(HttpResponse::Ok))
.route("/path1", web::delete().to(|| HttpResponse::Ok())), .route("/path1", web::delete().to(HttpResponse::Ok)),
), ),
) )
.await; .await;
@ -777,8 +774,8 @@ mod tests {
App::new().service( App::new().service(
web::scope("app").service( web::scope("app").service(
web::resource("path1") web::resource("path1")
.route(web::get().to(|| HttpResponse::Ok())) .route(web::get().to(HttpResponse::Ok))
.route(web::delete().to(|| HttpResponse::Ok())), .route(web::delete().to(HttpResponse::Ok)),
), ),
), ),
) )
@ -807,7 +804,7 @@ mod tests {
App::new().service( App::new().service(
web::scope("/app") web::scope("/app")
.guard(guard::Get()) .guard(guard::Get())
.service(web::resource("/path1").to(|| HttpResponse::Ok())), .service(web::resource("/path1").to(HttpResponse::Ok)),
), ),
) )
.await; .await;
@ -842,7 +839,7 @@ mod tests {
match resp.response().body() { match resp.response().body() {
ResponseBody::Body(Body::Bytes(ref b)) => { 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")); assert_eq!(bytes, Bytes::from_static(b"project: project1"));
} }
_ => panic!(), _ => panic!(),
@ -855,14 +852,9 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_nested_scope() { async fn test_nested_scope() {
let mut srv = init_service( let mut srv = init_service(App::new().service(web::scope("/app").service(
App::new().service( web::scope("/t1").service(web::resource("/path1").to(HttpResponse::Created)),
web::scope("/app") )))
.service(web::scope("/t1").service(
web::resource("/path1").to(|| HttpResponse::Created()),
)),
),
)
.await; .await;
let req = TestRequest::with_uri("/app/t1/path1").to_request(); let req = TestRequest::with_uri("/app/t1/path1").to_request();
@ -872,14 +864,9 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_nested_scope_no_slash() { async fn test_nested_scope_no_slash() {
let mut srv = init_service( let mut srv = init_service(App::new().service(web::scope("/app").service(
App::new().service( web::scope("t1").service(web::resource("/path1").to(HttpResponse::Created)),
web::scope("/app") )))
.service(web::scope("t1").service(
web::resource("/path1").to(|| HttpResponse::Created()),
)),
),
)
.await; .await;
let req = TestRequest::with_uri("/app/t1/path1").to_request(); let req = TestRequest::with_uri("/app/t1/path1").to_request();
@ -893,8 +880,8 @@ mod tests {
App::new().service( App::new().service(
web::scope("/app").service( web::scope("/app").service(
web::scope("/t1") web::scope("/t1")
.service(web::resource("").to(|| HttpResponse::Ok())) .service(web::resource("").to(HttpResponse::Ok))
.service(web::resource("/").to(|| HttpResponse::Created())), .service(web::resource("/").to(HttpResponse::Created)),
), ),
), ),
) )
@ -916,7 +903,7 @@ mod tests {
web::scope("/app").service( web::scope("/app").service(
web::scope("/t1") web::scope("/t1")
.guard(guard::Get()) .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() { match resp.response().body() {
ResponseBody::Body(Body::Bytes(ref b)) => { 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")); assert_eq!(bytes, Bytes::from_static(b"project: project_1"));
} }
_ => panic!(), _ => panic!(),
@ -981,7 +968,7 @@ mod tests {
match resp.response().body() { match resp.response().body() {
ResponseBody::Body(Body::Bytes(ref b)) => { 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")); assert_eq!(bytes, Bytes::from_static(b"project: test - 1"));
} }
_ => panic!(), _ => panic!(),
@ -997,7 +984,7 @@ mod tests {
let mut srv = init_service( let mut srv = init_service(
App::new().service( App::new().service(
web::scope("/app") web::scope("/app")
.service(web::resource("/path1").to(|| HttpResponse::Ok())) .service(web::resource("/path1").to(HttpResponse::Ok))
.default_service(|r: ServiceRequest| { .default_service(|r: ServiceRequest| {
ok(r.into_response(HttpResponse::BadRequest())) ok(r.into_response(HttpResponse::BadRequest()))
}), }),
@ -1018,9 +1005,10 @@ mod tests {
async fn test_default_resource_propagation() { async fn test_default_resource_propagation() {
let mut srv = init_service( let mut srv = init_service(
App::new() App::new()
.service(web::scope("/app1").default_service( .service(
web::resource("").to(|| HttpResponse::BadRequest()), web::scope("/app1")
)) .default_service(web::resource("").to(HttpResponse::BadRequest)),
)
.service(web::scope("/app2")) .service(web::scope("/app2"))
.default_service(|r: ServiceRequest| { .default_service(|r: ServiceRequest| {
ok(r.into_response(HttpResponse::MethodNotAllowed())) ok(r.into_response(HttpResponse::MethodNotAllowed()))
@ -1043,17 +1031,17 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_middleware() { async fn test_middleware() {
let mut srv = let mut srv = init_service(
init_service(
App::new().service( App::new().service(
web::scope("app") web::scope("app")
.wrap(DefaultHeaders::new().header( .wrap(
DefaultHeaders::new().header(
header::CONTENT_TYPE, header::CONTENT_TYPE,
HeaderValue::from_static("0001"), HeaderValue::from_static("0001"),
)) ),
)
.service( .service(
web::resource("/test") web::resource("/test").route(web::get().to(HttpResponse::Ok)),
.route(web::get().to(|| HttpResponse::Ok())),
), ),
), ),
) )
@ -1084,7 +1072,7 @@ mod tests {
Ok(res) Ok(res)
} }
}) })
.route("/test", web::get().to(|| HttpResponse::Ok())), .route("/test", web::get().to(HttpResponse::Ok)),
), ),
) )
.await; .await;
@ -1105,7 +1093,6 @@ mod tests {
"/t", "/t",
web::get().to(|data: web::Data<usize>| { web::get().to(|data: web::Data<usize>| {
assert_eq!(**data, 10); assert_eq!(**data, 10);
let _ = data.clone();
HttpResponse::Ok() HttpResponse::Ok()
}), }),
), ),
@ -1141,7 +1128,6 @@ mod tests {
"/t", "/t",
web::get().to(|data: web::Data<usize>| { web::get().to(|data: web::Data<usize>| {
assert_eq!(**data, 10); assert_eq!(**data, 10);
let _ = data.clone();
HttpResponse::Ok() HttpResponse::Ok()
}), }),
), ),
@ -1157,7 +1143,7 @@ mod tests {
async fn test_scope_config() { async fn test_scope_config() {
let mut srv = let mut srv =
init_service(App::new().service(web::scope("/app").configure(|s| { 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; .await;
@ -1171,7 +1157,7 @@ mod tests {
let mut srv = let mut srv =
init_service(App::new().service(web::scope("/app").configure(|s| { init_service(App::new().service(web::scope("/app").configure(|s| {
s.service(web::scope("/v1").configure(|s| { s.service(web::scope("/v1").configure(|s| {
s.route("/", web::get().to(|| HttpResponse::Ok())); s.route("/", web::get().to(HttpResponse::Ok));
})); }));
}))) })))
.await; .await;
@ -1193,10 +1179,9 @@ mod tests {
s.route( s.route(
"/", "/",
web::get().to(|req: HttpRequest| async move { web::get().to(|req: HttpRequest| async move {
HttpResponse::Ok().body(format!( HttpResponse::Ok().body(
"{}", req.url_for("youtube", &["xxxxxx"]).unwrap().to_string(),
req.url_for("youtube", &["xxxxxx"]).unwrap().as_str() )
))
}), }),
); );
})); }));

View File

@ -12,7 +12,6 @@ use actix_router::{IntoPattern, Path, Resource, ResourceDef, Url};
use actix_service::{IntoServiceFactory, ServiceFactory}; use actix_service::{IntoServiceFactory, ServiceFactory};
use crate::config::{AppConfig, AppService}; use crate::config::{AppConfig, AppService};
use crate::data::Data;
use crate::dev::insert_slash; use crate::dev::insert_slash;
use crate::guard::Guard; use crate::guard::Guard;
use crate::info::ConnectionInfo; use crate::info::ConnectionInfo;
@ -226,12 +225,11 @@ impl ServiceRequest {
self.0.app_config() self.0.app_config()
} }
/// Get an application data stored with `App::data()` method during /// Counterpart to [`HttpRequest::app_data`](../struct.HttpRequest.html#method.app_data).
/// application configuration. pub fn app_data<T: 'static>(&self) -> Option<&T> {
pub fn app_data<T: 'static>(&self) -> Option<Data<T>> {
for container in (self.0).0.app_data.iter().rev() { for container in (self.0).0.app_data.iter().rev() {
if let Some(data) = container.get::<Data<T>>() { if let Some(data) = container.get::<T>() {
return Some(Data::clone(&data)); return Some(data);
} }
} }
@ -595,6 +593,28 @@ mod tests {
let resp = srv.call(req).await.unwrap(); let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), http::StatusCode::NOT_FOUND); 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] #[test]
fn test_fmt_debug() { fn test_fmt_debug() {
let req = TestRequest::get() let req = TestRequest::get()

View File

@ -23,7 +23,6 @@ use futures_util::future::ok;
use futures_util::StreamExt; use futures_util::StreamExt;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde::Serialize; use serde::Serialize;
use serde_json;
use socket2::{Domain, Protocol, Socket, Type}; use socket2::{Domain, Protocol, Socket, Type};
pub use actix_http::test::TestBuffer; pub use actix_http::test::TestBuffer;

View File

@ -407,18 +407,15 @@ mod tests {
fn eq(err: UrlencodedError, other: UrlencodedError) -> bool { fn eq(err: UrlencodedError, other: UrlencodedError) -> bool {
match err { match err {
UrlencodedError::Overflow { .. } => match other { UrlencodedError::Overflow { .. } => {
UrlencodedError::Overflow { .. } => true, matches!(other, UrlencodedError::Overflow { .. })
_ => false, }
}, UrlencodedError::UnknownLength => {
UrlencodedError::UnknownLength => match other { matches!(other, UrlencodedError::UnknownLength)
UrlencodedError::UnknownLength => true, }
_ => false, UrlencodedError::ContentType => {
}, matches!(other, UrlencodedError::ContentType)
UrlencodedError::ContentType => match other { }
UrlencodedError::ContentType => true,
_ => false,
},
_ => false, _ => false,
} }
} }

View File

@ -433,14 +433,10 @@ mod tests {
fn json_eq(err: JsonPayloadError, other: JsonPayloadError) -> bool { fn json_eq(err: JsonPayloadError, other: JsonPayloadError) -> bool {
match err { match err {
JsonPayloadError::Overflow => match other { JsonPayloadError::Overflow => matches!(other, JsonPayloadError::Overflow),
JsonPayloadError::Overflow => true, JsonPayloadError::ContentType => {
_ => false, matches!(other, JsonPayloadError::ContentType)
}, }
JsonPayloadError::ContentType => match other {
JsonPayloadError::ContentType => true,
_ => false,
},
_ => false, _ => false,
} }
} }
@ -486,7 +482,7 @@ mod tests {
.to_http_parts(); .to_http_parts();
let s = Json::<MyObject>::from_request(&req, &mut pl).await; 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); assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let body = load_stream(resp.take_body()).await.unwrap(); let body = load_stream(resp.take_body()).await.unwrap();

View File

@ -25,8 +25,8 @@ use crate::FromRequest;
/// /// extract path info from "/{username}/{count}/index.html" url /// /// extract path info from "/{username}/{count}/index.html" url
/// /// {username} - deserializes to a String /// /// {username} - deserializes to a String
/// /// {count} - - deserializes to a u32 /// /// {count} - - deserializes to a u32
/// async fn index(info: web::Path<(String, u32)>) -> String { /// async fn index(web::Path((username, count)): web::Path<(String, u32)>) -> String {
/// format!("Welcome {}! {}", info.0, info.1) /// format!("Welcome {}! {}", username, count)
/// } /// }
/// ///
/// fn main() { /// fn main() {
@ -61,20 +61,18 @@ use crate::FromRequest;
/// ); /// );
/// } /// }
/// ``` /// ```
pub struct Path<T> { pub struct Path<T>(pub T);
inner: T,
}
impl<T> Path<T> { impl<T> Path<T> {
/// Deconstruct to an inner value /// Deconstruct to an inner value
pub fn into_inner(self) -> T { pub fn into_inner(self) -> T {
self.inner self.0
} }
} }
impl<T> AsRef<T> for Path<T> { impl<T> AsRef<T> for Path<T> {
fn as_ref(&self) -> &T { fn as_ref(&self) -> &T {
&self.inner &self.0
} }
} }
@ -82,31 +80,31 @@ impl<T> ops::Deref for Path<T> {
type Target = T; type Target = T;
fn deref(&self) -> &T { fn deref(&self) -> &T {
&self.inner &self.0
} }
} }
impl<T> ops::DerefMut for Path<T> { impl<T> ops::DerefMut for Path<T> {
fn deref_mut(&mut self) -> &mut T { fn deref_mut(&mut self) -> &mut T {
&mut self.inner &mut self.0
} }
} }
impl<T> From<T> for Path<T> { impl<T> From<T> for Path<T> {
fn from(inner: T) -> Path<T> { fn from(inner: T) -> Path<T> {
Path { inner } Path(inner)
} }
} }
impl<T: fmt::Debug> fmt::Debug for Path<T> { impl<T: fmt::Debug> fmt::Debug for Path<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 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> { impl<T: fmt::Display> fmt::Display for Path<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 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 /// /// extract path info from "/{username}/{count}/index.html" url
/// /// {username} - deserializes to a String /// /// {username} - deserializes to a String
/// /// {count} - - deserializes to a u32 /// /// {count} - - deserializes to a u32
/// async fn index(info: web::Path<(String, u32)>) -> String { /// async fn index(web::Path((username, count)): web::Path<(String, u32)>) -> String {
/// format!("Welcome {}! {}", info.0, info.1) /// format!("Welcome {}! {}", username, count)
/// } /// }
/// ///
/// fn main() { /// fn main() {
@ -173,7 +171,7 @@ where
ready( ready(
de::Deserialize::deserialize(PathDeserializer::new(req.match_info())) de::Deserialize::deserialize(PathDeserializer::new(req.match_info()))
.map(|inner| Path { inner }) .map(Path)
.map_err(move |e| { .map_err(move |e| {
log::debug!( log::debug!(
"Failed during Path extractor deserialization. \ "Failed during Path extractor deserialization. \
@ -290,21 +288,22 @@ mod tests {
resource.match_path(req.match_info_mut()); resource.match_path(req.match_info_mut());
let (req, mut pl) = req.into_parts(); 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 .await
.unwrap(); .unwrap();
assert_eq!((res.0).0, "name"); assert_eq!(res.0, "name");
assert_eq!((res.0).1, "user1"); 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, &req, &mut pl,
) )
.await .await
.unwrap(); .unwrap();
assert_eq!((res.0).0, "name"); assert_eq!(a.0, "name");
assert_eq!((res.0).1, "user1"); assert_eq!(a.1, "user1");
assert_eq!((res.1).0, "name"); assert_eq!(b.0, "name");
assert_eq!((res.1).1, "user1"); assert_eq!(b.1, "user1");
let () = <()>::from_request(&req, &mut pl).await.unwrap(); let () = <()>::from_request(&req, &mut pl).await.unwrap();
} }
@ -329,7 +328,7 @@ mod tests {
let s = s.into_inner(); let s = s.into_inner();
assert_eq!(s.value, "user2"); 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 .await
.unwrap(); .unwrap();
assert_eq!(s.0, "name"); assert_eq!(s.0, "name");
@ -344,7 +343,7 @@ mod tests {
assert_eq!(s.as_ref().key, "name"); assert_eq!(s.as_ref().key, "name");
assert_eq!(s.value, 32); 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 .await
.unwrap(); .unwrap();
assert_eq!(s.0, "name"); assert_eq!(s.0, "name");

View File

@ -13,10 +13,10 @@ use futures_util::future::{err, ok, Either, FutureExt, LocalBoxFuture, Ready};
use futures_util::StreamExt; use futures_util::StreamExt;
use mime::Mime; use mime::Mime;
use crate::dev;
use crate::extract::FromRequest; use crate::extract::FromRequest;
use crate::http::header; use crate::http::header;
use crate::request::HttpRequest; use crate::request::HttpRequest;
use crate::{dev, web};
/// Payload extractor returns request 's payload stream. /// Payload extractor returns request 's payload stream.
/// ///
@ -142,13 +142,8 @@ impl FromRequest for Bytes {
#[inline] #[inline]
fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
let tmp; // allow both Config and Data<Config>
let cfg = if let Some(cfg) = req.app_data::<PayloadConfig>() { let cfg = PayloadConfig::from_req(req);
cfg
} else {
tmp = PayloadConfig::default();
&tmp
};
if let Err(e) = cfg.check_mimetype(req) { if let Err(e) = cfg.check_mimetype(req) {
return Either::Right(err(e)); return Either::Right(err(e));
@ -197,13 +192,7 @@ impl FromRequest for String {
#[inline] #[inline]
fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
let tmp; let cfg = PayloadConfig::from_req(req);
let cfg = if let Some(cfg) = req.app_data::<PayloadConfig>() {
cfg
} else {
tmp = PayloadConfig::default();
&tmp
};
// check content-type // check content-type
if let Err(e) = cfg.check_mimetype(req) { 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)] #[derive(Clone)]
pub struct PayloadConfig { pub struct PayloadConfig {
limit: usize, limit: usize,
@ -284,14 +278,28 @@ impl PayloadConfig {
} }
Ok(()) 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 { impl Default for PayloadConfig {
fn default() -> Self { fn default() -> Self {
PayloadConfig { DEFAULT_PAYLOAD_CONFIG.clone()
limit: 262_144,
mimetype: None,
}
} }
} }
@ -407,8 +415,9 @@ mod tests {
use bytes::Bytes; use bytes::Bytes;
use super::*; use super::*;
use crate::http::header; use crate::http::{header, StatusCode};
use crate::test::TestRequest; use crate::test::{call_service, init_service, TestRequest};
use crate::{web, App, Responder};
#[actix_rt::test] #[actix_rt::test]
async fn test_payload_config() { async fn test_payload_config() {
@ -428,6 +437,86 @@ mod tests {
assert!(cfg.check_mimetype(&req).is_ok()); 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] #[actix_rt::test]
async fn test_bytes() { async fn test_bytes() {
let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "11") let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "11")

View File

@ -2,7 +2,7 @@
name = "actix-http-test" name = "actix-http-test"
version = "2.0.0-alpha.1" version = "2.0.0-alpha.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix http test server" description = "Actix HTTP test server"
readme = "README.md" readme = "README.md"
keywords = ["http", "web", "framework", "async", "futures"] keywords = ["http", "web", "framework", "async", "futures"]
homepage = "https://actix.rs" homepage = "https://actix.rs"
@ -11,7 +11,7 @@ documentation = "https://docs.rs/actix-http-test/"
categories = ["network-programming", "asynchronous", categories = ["network-programming", "asynchronous",
"web-programming::http-server", "web-programming::http-server",
"web-programming::websocket"] "web-programming::websocket"]
license = "MIT/Apache-2.0" license = "MIT OR Apache-2.0"
exclude = [".gitignore", ".cargo/config"] exclude = [".gitignore", ".cargo/config"]
edition = "2018" edition = "2018"
@ -53,4 +53,4 @@ open-ssl = { version = "0.10", package = "openssl", optional = true }
[dev-dependencies] [dev-dependencies]
actix-web = "3.0.0-alpha.3" actix-web = "3.0.0-alpha.3"
actix-http = "2.0.0-alpha.4" actix-http = "2.0.0-beta.3"

View File

@ -851,7 +851,7 @@ async fn test_slow_request() {
use std::net; use std::net;
let srv = test::start_with(test::config().client_timeout(200), || { 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(); let mut stream = net::TcpStream::connect(srv.addr()).unwrap();