1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-03 17:41:30 +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
What kind of change does this PR make?
<!-- Thanks for considering contributing actix! -->
<!-- Please fill out the following to make our reviews easy. -->
## PR Type
<!-- What kind of change does this PR make? -->
<!-- Bug Fix / Feature / Refactor / Code Style / Other -->
INSERT_PR_TYPE
@ -13,6 +15,7 @@ Check your PR fulfills the following:
- [ ] Tests for the changes have been added / updated.
- [ ] Documentation comments have been added / updated.
- [ ] A changelog entry has been made for the appropriate packages.
- [ ] Format code with the latest stable rustfmt
## Overview

View File

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

32
.github/workflows/clippy-fmt.yml vendored Normal file
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)
on: [push, pull_request]
on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
jobs:
build_and_test:
@ -8,7 +13,7 @@ jobs:
fail-fast: false
matrix:
version:
- 1.41.1 # MSRV
- 1.42.0 # MSRV
- stable
- nightly
@ -16,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: actions/checkout@v2
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1

View File

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

View File

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

View File

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

1
.gitignore vendored
View File

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

View File

@ -1,38 +1,50 @@
# Changes
## [Unreleased]
## Unreleased - 2020-xx-xx
### Changed
* `PayloadConfig` is now also considered in `Bytes` and `String` extractors when set
using `App::data`. [#1610]
* `web::Path` now has a public representation: `web::Path(pub T)` that enables
destructuring. [#1594]
* `ServiceRequest::app_data` allows retrieval of non-Data data without splitting into parts to
access `HttpRequest` which already allows this. [#1618]
* Re-export all error types from `awc`. [#1621]
* MSRV is now 1.42.0.
### Fixed
* Memory leak of app data in pooled requests. [#1609]
[#1594]: https://github.com/actix/actix-web/pull/1594
[#1609]: https://github.com/actix/actix-web/pull/1609
[#1610]: https://github.com/actix/actix-web/pull/1610
[#1618]: https://github.com/actix/actix-web/pull/1618
[#1621]: https://github.com/actix/actix-web/pull/1621
## 3.0.0-beta.1 - 2020-07-13
### Added
* Re-export `actix_rt::main` as `actix_web::main`.
* `HttpRequest::match_pattern` and `ServiceRequest::match_pattern` for extracting the matched
resource pattern.
* `HttpRequest::match_name` and `ServiceRequest::match_name` for extracting matched resource name.
### Changed
* Fix actix_http::h1::dispatcher so it returns when HW_BUFFER_SIZE is reached. Should reduce peak memory consumption during large uploads. [#1550]
* Migrate cookie handling to `cookie` crate. Actix-web no longer requires `ring` dependency.
* MSRV is now 1.41.1
### Fixed
* `NormalizePath` improved consistency when path needs slashes added _and_ removed.
## [3.0.0-alpha.3] - 2020-05-21
## 3.0.0-alpha.3 - 2020-05-21
### Added
* Add option to create `Data<T>` from `Arc<T>` [#1509]
### Changed
* Resources and Scopes can now access non-overridden data types set on App (or containing scopes) when setting their own data. [#1486]
* Fix audit issue logging by default peer address [#1485]
* Bump minimum supported Rust version to 1.40
* Replace deprecated `net2` crate with `socket2`
[#1485]: https://github.com/actix/actix-web/pull/1485

View File

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

View File

@ -12,6 +12,26 @@
* `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a
`u64` instead of a `usize`.
* Code that was using `path.<index>` to access a `web::Path<(A, B, C)>`s elements now needs to use
destructuring or `.into_inner()`. For example:
```rust
// Previously:
async fn some_route(path: web::Path<(String, String)>) -> String {
format!("Hello, {} {}", path.0, path.1)
}
// Now (this also worked before):
async fn some_route(path: web::Path<(String, String)>) -> String {
let (first_name, last_name) = path.into_inner();
format!("Hello, {} {}", first_name, last_name)
}
// Or (this wasn't previously supported):
async fn some_route(web::Path((first_name, last_name)): web::Path<(String, String)>) -> String {
format!("Hello, {} {}", first_name, last_name)
}
```
## 2.0.0
* `HttpServer::start()` renamed to `HttpServer::run()`. It also possible to

View File

@ -7,7 +7,7 @@
[![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)
[![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)
<br />
[![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/))
* Includes an async [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html)
* Supports [Actix actor framework](https://github.com/actix/actix)
* Runs on stable Rust 1.41+
* Runs on stable Rust 1.42+
## Documentation
@ -61,8 +61,8 @@ Code:
use actix_web::{get, web, App, HttpServer, Responder};
#[get("/{id}/{name}/index.html")]
async fn index(info: web::Path<(u32, String)>) -> impl Responder {
format!("Hello {}! id:{}", info.1, info.0)
async fn index(web::Path((id, name)): web::Path<(u32, String)>) -> impl Responder {
format!("Hello {}! id:{}", name, id)
}
#[actix_web::main]

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,23 @@
# Changes
## [Unreleased] - xxx
## [2.0.0-beta.3] - 2020-08-14
### Fixed
* Memory leak of `client::pool::ConnectorPoolSupport`. [#1626]
[#1626]: https://github.com/actix/actix-web/pull/1626
## [2.0.0-beta.2] - 2020-07-21
### Fixed
* Potential UB in h1 decoder using uninitialized memory. [#1614]
### Changed
* Fix illegal chunked encoding. [#1615]
[#1614]: https://github.com/actix/actix-web/pull/1614
[#1615]: https://github.com/actix/actix-web/pull/1615
## [2.0.0-beta.1] - 2020-07-11

View File

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

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

View File

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

View File

@ -2,7 +2,7 @@ use std::cell::RefCell;
use std::collections::VecDeque;
use std::future::Future;
use std::pin::Pin;
use std::rc::{Rc, Weak};
use std::rc::Rc;
use std::task::{Context, Poll};
use std::time::{Duration, Instant};
@ -65,14 +65,11 @@ where
// start support future
actix_rt::spawn(ConnectorPoolSupport {
connector: connector_rc.clone(),
inner: Rc::downgrade(&inner_rc),
connector: Rc::clone(&connector_rc),
inner: Rc::clone(&inner_rc),
});
ConnectionPool(
connector_rc,
inner_rc,
)
ConnectionPool(connector_rc, inner_rc)
}
}
@ -85,6 +82,13 @@ where
}
}
impl<T, Io> Drop for ConnectionPool<T, Io> {
fn drop(&mut self) {
// wake up the ConnectorPoolSupport when dropping so it can exit properly.
self.1.borrow().waker.wake();
}
}
impl<T, Io> Service for ConnectionPool<T, Io>
where
Io: AsyncRead + AsyncWrite + Unpin + 'static,
@ -424,7 +428,7 @@ where
Io: AsyncRead + AsyncWrite + Unpin + 'static,
{
connector: T,
inner: Weak<RefCell<Inner<Io>>>,
inner: Rc<RefCell<Inner<Io>>>,
}
impl<T, Io> Future for ConnectorPoolSupport<T, Io>
@ -438,8 +442,13 @@ where
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
if let Some(this_inner) = this.inner.upgrade() {
let mut inner = this_inner.as_ref().borrow_mut();
if Rc::strong_count(this.inner) == 1 {
// If we are last copy of Inner<Io> it means the ConnectionPool is already gone
// and we are safe to exit.
return Poll::Ready(());
}
let mut inner = this.inner.borrow_mut();
inner.waker.register(cx.waker());
// check waiters
@ -462,7 +471,7 @@ where
if let Err(conn) = tx.send(Ok(IoConnection::new(
io,
created,
Some(Acquired(key.clone(), Some(this_inner.clone()))),
Some(Acquired(key.clone(), Some(this.inner.clone()))),
))) {
let (io, created) = conn.unwrap().into_inner();
inner.release_conn(&key, io, created);
@ -474,7 +483,7 @@ where
OpenWaitingConnection::spawn(
key.clone(),
tx,
this_inner.clone(),
this.inner.clone(),
this.connector.call(connect),
inner.config.clone(),
);
@ -484,9 +493,6 @@ where
}
Poll::Pending
} else {
Poll::Ready(())
}
}
}

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

@ -9,11 +9,13 @@
pub use self::accept_charset::AcceptCharset;
//pub use self::accept_encoding::AcceptEncoding;
pub use self::accept_language::AcceptLanguage;
pub use self::accept::Accept;
pub use self::accept_language::AcceptLanguage;
pub use self::allow::Allow;
pub use self::cache_control::{CacheControl, CacheDirective};
pub use self::content_disposition::{ContentDisposition, DispositionType, DispositionParam};
pub use self::content_disposition::{
ContentDisposition, DispositionParam, DispositionType,
};
pub use self::content_language::ContentLanguage;
pub use self::content_range::{ContentRange, ContentRangeSpec};
pub use self::content_type::ContentType;
@ -47,7 +49,7 @@ macro_rules! __hyper__deref {
&mut self.0
}
}
}
};
}
#[doc(hidden)]
@ -74,8 +76,8 @@ macro_rules! test_header {
($id:ident, $raw:expr) => {
#[test]
fn $id() {
use $crate::test;
use super::*;
use $crate::test;
let raw = $raw;
let a: Vec<Vec<u8>> = raw.iter().map(|x| x.to_vec()).collect();
@ -118,7 +120,7 @@ macro_rules! test_header {
// Test formatting
if typed.is_some() {
let raw = &($raw)[..];
let mut iter = raw.iter().map(|b|str::from_utf8(&b[..]).unwrap());
let mut iter = raw.iter().map(|b| str::from_utf8(&b[..]).unwrap());
let mut joined = String::new();
joined.push_str(iter.next().unwrap());
for s in iter {
@ -128,7 +130,7 @@ macro_rules! test_header {
assert_eq!(format!("{}", typed.unwrap()), joined);
}
}
}
};
}
#[macro_export]
@ -330,11 +332,10 @@ macro_rules! header {
};
}
mod accept_charset;
//mod accept_encoding;
mod accept_language;
mod accept;
mod accept_language;
mod allow;
mod cache_control;
mod content_disposition;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,15 +1,17 @@
# Changes
## [0.3.0-alpha.1] - 2020-05-25
## Unreleased - 2020-xx-xx
## 0.3.0-beta.1 - 2020-07-15
* Update `actix-web` to 3.0.0-beta.1
## 0.3.0-alpha.1 - 2020-05-25
* Update `actix-web` to 3.0.0-alpha.3
* Bump minimum supported Rust version to 1.40
* Minimize `futures` dependencies
* Remove the unused `time` dependency
* Fix missing `std::error::Error` implement for `MultipartError`.
## [0.2.0] - 2019-12-20

View File

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

View File

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

View File

@ -2,10 +2,13 @@
## [Unreleased] - 2020-xx-xx
## [3.0.0-beta.1] - 2020-xx-xx
* Update `actix-web` & `actix-http` dependencies to beta.1
* Bump minimum supported Rust version to 1.40
## [3.0.0-alpha.1] - 2020-05-08
## [3.0.0-alpha.1] - 2020-05-08
* Update the actix-web dependency to 3.0.0-alpha.1
* Update the actix dependency to 0.10.0-alpha.2
* Update the actix-http dependency to 2.0.0-alpha.3

View File

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

View File

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

View File

@ -1,18 +1,20 @@
# Changes
## [Unreleased] - XXXX-XX-XX
## Unreleased - 2020-xx-xx
* Add main entry-point macro that uses re-exported runtime.
## 0.3.0-beta.1 - 2020-07-14
* Add main entry-point macro that uses re-exported runtime. [#1559]
[#1559]: https://github.com/actix/actix-web/pull/1559
## [0.2.2] - 2020-05-23
* Add resource middleware on actix-web-codegen [#1467]
[#1467]: https://github.com/actix/actix-web/pull/1467
## [0.2.1] - 2020-02-25
* Add `#[allow(missing_docs)]` attribute to generated structs [#1368]
* Allow the handler function to be named as `config` [#1290]
@ -26,7 +28,6 @@
## [0.1.3] - 2019-10-14
* Bump up `syn` & `quote` to 1.0
* Provide better error message
## [0.1.2] - 2019-06-04

View File

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

View File

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

View File

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

View File

@ -1,5 +1,17 @@
# Changes
## Unreleased - 2020-xx-xx
## 2.0.0-beta.2 - 2020-07-21
### Changed
* Update `actix-http` dependency to 2.0.0-beta.2
## [2.0.0-beta.1] - 2020-07-14
### Changed
* Update `actix-http` dependency to 2.0.0-beta.1
## [2.0.0-alpha.2] - 2020-05-21
### Changed

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

@ -626,7 +626,7 @@ mod tests {
Ok(())
};
let s = format!("{}", FormatDisplay(&render));
assert!(s.contains(&format!("{}", now.format("%Y-%m-%dT%H:%M:%S"))));
assert!(s.contains(&now.format("%Y-%m-%dT%H:%M:%S")));
}
#[actix_rt::test]

View File

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

View File

@ -276,6 +276,7 @@ impl HttpMessage for HttpRequest {
impl Drop for HttpRequest {
fn drop(&mut self) {
// if possible, contribute to current worker's HttpRequest allocation pool
if Rc::strong_count(&self.0) == 1 {
let v = &mut self.0.pool.0.borrow_mut();
if v.len() < 128 {
@ -340,25 +341,32 @@ impl fmt::Debug for HttpRequest {
}
}
/// Request's objects pool
/// Slab-allocated `HttpRequest` Pool
///
/// Since request processing may yield for asynchronous events to complete, a worker may have many
/// requests in-flight at any time. Pooling requests like this amortizes the performance and memory
/// costs of allocating and de-allocating HttpRequest objects as frequently as they otherwise would.
///
/// Request objects are added when they are dropped (see `<HttpRequest as Drop>::drop`) and re-used
/// in `<AppInitService as Service>::call` when there are available objects in the list.
///
/// The pool's initial capacity is 128 items.
pub(crate) struct HttpRequestPool(RefCell<Vec<Rc<HttpRequestInner>>>);
impl HttpRequestPool {
/// Allocates a slab of memory for pool use.
pub(crate) fn create() -> &'static HttpRequestPool {
let pool = HttpRequestPool(RefCell::new(Vec::with_capacity(128)));
Box::leak(Box::new(pool))
}
/// Get message from the pool
/// Re-use a previously allocated (but now completed/discarded) HttpRequest object.
#[inline]
pub(crate) fn get_request(&self) -> Option<HttpRequest> {
if let Some(inner) = self.0.borrow_mut().pop() {
Some(HttpRequest(inner))
} else {
None
}
self.0.borrow_mut().pop().map(HttpRequest)
}
/// Clears all allocated HttpRequest objects.
pub(crate) fn clear(&self) {
self.0.borrow_mut().clear()
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,10 +13,10 @@ use futures_util::future::{err, ok, Either, FutureExt, LocalBoxFuture, Ready};
use futures_util::StreamExt;
use mime::Mime;
use crate::dev;
use crate::extract::FromRequest;
use crate::http::header;
use crate::request::HttpRequest;
use crate::{dev, web};
/// Payload extractor returns request 's payload stream.
///
@ -142,13 +142,8 @@ impl FromRequest for Bytes {
#[inline]
fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
let tmp;
let cfg = if let Some(cfg) = req.app_data::<PayloadConfig>() {
cfg
} else {
tmp = PayloadConfig::default();
&tmp
};
// allow both Config and Data<Config>
let cfg = PayloadConfig::from_req(req);
if let Err(e) = cfg.check_mimetype(req) {
return Either::Right(err(e));
@ -197,13 +192,7 @@ impl FromRequest for String {
#[inline]
fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
let tmp;
let cfg = if let Some(cfg) = req.app_data::<PayloadConfig>() {
cfg
} else {
tmp = PayloadConfig::default();
&tmp
};
let cfg = PayloadConfig::from_req(req);
// check content-type
if let Err(e) = cfg.check_mimetype(req) {
@ -237,7 +226,12 @@ impl FromRequest for String {
)
}
}
/// Payload configuration for request's payload.
/// Configuration for request's payload.
///
/// Applies to the built-in `Bytes` and `String` extractors. Note that the Payload extractor does
/// not automatically check conformance with this configuration to allow more flexibility when
/// building extractors on top of `Payload`.
#[derive(Clone)]
pub struct PayloadConfig {
limit: usize,
@ -284,14 +278,28 @@ impl PayloadConfig {
}
Ok(())
}
/// Allow payload config extraction from app data checking both `T` and `Data<T>`, in that
/// order, and falling back to the default payload config.
fn from_req(req: &HttpRequest) -> &PayloadConfig {
req.app_data::<PayloadConfig>()
.or_else(|| {
req.app_data::<web::Data<PayloadConfig>>()
.map(|d| d.as_ref())
})
.unwrap_or_else(|| &DEFAULT_PAYLOAD_CONFIG)
}
}
// Allow shared refs to default.
static DEFAULT_PAYLOAD_CONFIG: PayloadConfig = PayloadConfig {
limit: 262_144, // 2^18 bytes (~256kB)
mimetype: None,
};
impl Default for PayloadConfig {
fn default() -> Self {
PayloadConfig {
limit: 262_144,
mimetype: None,
}
DEFAULT_PAYLOAD_CONFIG.clone()
}
}
@ -407,8 +415,9 @@ mod tests {
use bytes::Bytes;
use super::*;
use crate::http::header;
use crate::test::TestRequest;
use crate::http::{header, StatusCode};
use crate::test::{call_service, init_service, TestRequest};
use crate::{web, App, Responder};
#[actix_rt::test]
async fn test_payload_config() {
@ -428,6 +437,86 @@ mod tests {
assert!(cfg.check_mimetype(&req).is_ok());
}
#[actix_rt::test]
async fn test_config_recall_locations() {
async fn bytes_handler(_: Bytes) -> impl Responder {
"payload is probably json bytes"
}
async fn string_handler(_: String) -> impl Responder {
"payload is probably json string"
}
let mut srv = init_service(
App::new()
.service(
web::resource("/bytes-app-data")
.app_data(
PayloadConfig::default().mimetype(mime::APPLICATION_JSON),
)
.route(web::get().to(bytes_handler)),
)
.service(
web::resource("/bytes-data")
.data(PayloadConfig::default().mimetype(mime::APPLICATION_JSON))
.route(web::get().to(bytes_handler)),
)
.service(
web::resource("/string-app-data")
.app_data(
PayloadConfig::default().mimetype(mime::APPLICATION_JSON),
)
.route(web::get().to(string_handler)),
)
.service(
web::resource("/string-data")
.data(PayloadConfig::default().mimetype(mime::APPLICATION_JSON))
.route(web::get().to(string_handler)),
),
)
.await;
let req = TestRequest::with_uri("/bytes-app-data").to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let req = TestRequest::with_uri("/bytes-data").to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let req = TestRequest::with_uri("/string-app-data").to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let req = TestRequest::with_uri("/string-data").to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let req = TestRequest::with_uri("/bytes-app-data")
.header(header::CONTENT_TYPE, mime::APPLICATION_JSON)
.to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/bytes-data")
.header(header::CONTENT_TYPE, mime::APPLICATION_JSON)
.to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/string-app-data")
.header(header::CONTENT_TYPE, mime::APPLICATION_JSON)
.to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/string-data")
.header(header::CONTENT_TYPE, mime::APPLICATION_JSON)
.to_request();
let resp = call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
}
#[actix_rt::test]
async fn test_bytes() {
let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "11")

View File

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

View File

@ -851,7 +851,7 @@ async fn test_slow_request() {
use std::net;
let srv = test::start_with(test::config().client_timeout(200), || {
App::new().service(web::resource("/").route(web::to(|| HttpResponse::Ok())))
App::new().service(web::resource("/").route(web::to(HttpResponse::Ok)))
});
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();