merge master to branch

This commit is contained in:
fakeshadow 2020-12-05 13:44:33 +08:00
commit 4ffcaa70ce
40 changed files with 765 additions and 306 deletions

View File

@ -1,8 +1,15 @@
blank_issues_enabled: true
contact_links:
- name: Gitter channel (actix-web)
- name: GitHub Discussions
url: https://github.com/actix/actix-web/discussions
about: Actix Web Q&A
- name: Gitter chat (actix-web)
url: https://gitter.im/actix/actix-web
about: Please ask and answer questions about the actix-web here.
- name: Gitter channel (actix)
about: Actix Web Q&A
- name: Gitter chat (actix)
url: https://gitter.im/actix/actix
about: Please ask and answer questions about the actix here.
about: Actix (actor framework) Q&A
- name: Actix Discord
url: https://discord.gg/NWpN5mmg3x
about: Actix developer discussion and community chat

View File

@ -16,7 +16,7 @@ jobs:
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable-x86_64-unknown-linux-gnu
toolchain: nightly-x86_64-unknown-linux-gnu
profile: minimal
override: true
@ -30,7 +30,7 @@ jobs:
run: echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html
- name: Deploy to GitHub Pages
uses: JamesIves/github-pages-deploy-action@3.5.8
uses: JamesIves/github-pages-deploy-action@3.7.1
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH: gh-pages

View File

@ -1,8 +1,36 @@
# Changes
## Unreleased - 2020-xx-xx
### Fixed
* added the actual parsing error to `test::read_body_json` [#1812]
[#1812]: https://github.com/actix/actix-web/pull/1812
## 3.3.2 - 2020-12-01
### Fixed
* Removed an occasional `unwrap` on `None` panic in `NormalizePathNormalization`. [#1762]
* Fix `match_pattern()` returning `None` for scope with empty path resource. [#1798]
* Increase minimum `socket2` version. [#1803]
[#1762]: https://github.com/actix/actix-web/pull/1762
[#1798]: https://github.com/actix/actix-web/pull/1798
[#1803]: https://github.com/actix/actix-web/pull/1803
## 3.3.1 - 2020-11-29
* Ensure `actix-http` dependency uses same `serde_urlencoded`.
## 3.3.0 - 2020-11-25
### Added
* Add `Either<A, B>` extractor helper. [#1788]
### Changed
* Upgrade `bytes` to `0.6`.
* Upgrade `serde_urlencoded` to `0.7`. [#1773]
[#1773]: https://github.com/actix/actix-web/pull/1773
[#1788]: https://github.com/actix/actix-web/pull/1788
## 3.2.0 - 2020-10-30

View File

@ -1,8 +1,8 @@
[package]
name = "actix-web"
version = "3.2.0"
version = "3.3.2"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix web is a powerful, pragmatic, and extremely fast web framework for Rust"
description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust"
readme = "README.md"
keywords = ["actix", "http", "web", "framework", "async"]
homepage = "https://actix.rs"
@ -34,7 +34,7 @@ members = [
"actix-multipart",
"actix-web-actors",
"actix-web-codegen",
"test-server",
"actix-http-test",
]
[features]
@ -85,11 +85,11 @@ actix-threadpool = "0.3.1"
actix-tls = "2.0.0"
actix-web-codegen = "0.4.0"
actix-http = "2.1.0"
awc = { version = "2.0.0", default-features = false }
actix-http = "2.2.0"
awc = { version = "2.0.3", default-features = false }
bytes = "0.6"
derive_more = "0.99.2"
derive_more = "0.99.5"
encoding_rs = "0.8"
futures-channel = { version = "0.3.5", default-features = false }
futures-core = { version = "0.3.5", default-features = false }
@ -97,12 +97,12 @@ futures-util = { version = "0.3.5", default-features = false }
fxhash = "0.2.1"
log = "0.4"
mime = "0.3"
socket2 = "0.3"
socket2 = "0.3.16"
pin-project = "1.0.0"
regex = "1.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_urlencoded = "0.6.1"
serde_urlencoded = "0.7"
time = { version = "0.2.7", default-features = false, features = ["std"] }
url = "2.1"
open-ssl = { package = "openssl", version = "0.10", optional = true }
@ -127,10 +127,10 @@ codegen-units = 1
[patch.crates-io]
actix-web = { path = "." }
actix-http = { path = "actix-http" }
actix-http-test = { path = "test-server" }
actix-http-test = { path = "actix-http-test" }
actix-web-codegen = { path = "actix-web-codegen" }
actix-files = { path = "actix-files" }
actix-multipart = { path = "actix-multipart" }
actix-files = { path = "actix-files" }
awc = { path = "awc" }
# FIXME: remove these override

View File

@ -1,15 +1,15 @@
<div align="center">
<h1>Actix web</h1>
<p>
<strong>Actix web is a powerful, pragmatic, and extremely fast web framework for Rust</strong>
<strong>Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust</strong>
</p>
<p>
[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web)
[![Documentation](https://docs.rs/actix-web/badge.svg?version=3.2.0)](https://docs.rs/actix-web/3.2.0)
[![Documentation](https://docs.rs/actix-web/badge.svg?version=3.3.2)](https://docs.rs/actix-web/3.3.2)
[![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)
[![Dependency Status](https://deps.rs/crate/actix-web/3.2.0/status.svg)](https://deps.rs/crate/actix-web/3.2.0)
[![Dependency Status](https://deps.rs/crate/actix-web/3.3.2/status.svg)](https://deps.rs/crate/actix-web/3.3.2)
<br />
[![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web)
[![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web)

View File

@ -3,6 +3,10 @@
## Unreleased - 2020-xx-xx
## 0.4.1 - 2020-11-24
* Clarify order of parameters in `Files::new` and improve docs.
## 0.4.0 - 2020-10-06
* Add `Files::prefer_utf8` option that adds UTF-8 charset on certain response types. [#1714]

View File

@ -1,6 +1,6 @@
[package]
name = "actix-files"
version = "0.4.0"
version = "0.4.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Static file serving for Actix Web"
readme = "README.md"
@ -21,14 +21,14 @@ actix-web = { version = "3.0.0", default-features = false }
actix-service = "1.0.6"
bitflags = "1"
bytes = "0.5.3"
futures-core = { version = "0.3.5", default-features = false }
futures-util = { version = "0.3.5", default-features = false }
futures-core = { version = "0.3.7", default-features = false }
futures-util = { version = "0.3.7", default-features = false }
derive_more = "0.99.2"
log = "0.4"
mime = "0.3"
mime_guess = "2.0.1"
percent-encoding = "2.1"
v_htmlescape = "0.10"
v_htmlescape = "0.11"
[dev-dependencies]
actix-rt = "1.0.0"

View File

@ -2,12 +2,12 @@
> Static file serving for Actix Web
[![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-files)
[![Documentation](https://docs.rs/actix-files/badge.svg)](https://docs.rs/actix-files)
[![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files)
[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.4.1)](https://docs.rs/actix-files/0.4.1)
[![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-files.svg)
<br />
[![dependency status](https://deps.rs/crate/actix-files/0.4.0/status.svg)](https://deps.rs/crate/actix-files/0.4.0)
[![dependency status](https://deps.rs/crate/actix-files/0.4.1/status.svg)](https://deps.rs/crate/actix-files/0.4.1)
[![Download](https://img.shields.io/crates/d/actix-files.svg)](https://crates.io/crates/actix-files)
[![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

View File

@ -65,13 +65,25 @@ impl Clone for Files {
}
impl Files {
/// Create new `Files` instance for specified base directory.
/// Create new `Files` instance for a specified base directory.
///
/// `File` uses `ThreadPool` for blocking filesystem operations.
/// By default pool with 5x threads of available cpus is used.
/// Pool size can be changed by setting ACTIX_THREADPOOL environment variable.
pub fn new<T: Into<PathBuf>>(path: &str, dir: T) -> Files {
let orig_dir = dir.into();
/// # Argument Order
/// The first argument (`mount_path`) is the root URL at which the static files are served.
/// For example, `/assets` will serve files at `example.com/assets/...`.
///
/// The second argument (`serve_from`) is the location on disk at which files are loaded.
/// This can be a relative path. For example, `./` would serve files from the current
/// working directory.
///
/// # Implementation Notes
/// If the mount path is set as the root path `/`, services registered after this one will
/// be inaccessible. Register more specific handlers and services first.
///
/// `Files` uses a threadpool for blocking filesystem operations. By default, the pool uses a
/// number of threads equal to 5x the number of available logical CPUs. Pool size can be changed
/// by setting ACTIX_THREADPOOL environment variable.
pub fn new<T: Into<PathBuf>>(mount_path: &str, serve_from: T) -> Files {
let orig_dir = serve_from.into();
let dir = match orig_dir.canonicalize() {
Ok(canon_dir) => canon_dir,
Err(_) => {
@ -81,7 +93,7 @@ impl Files {
};
Files {
path: path.to_string(),
path: mount_path.to_owned(),
directory: dir,
index: None,
show_index: false,

View File

@ -193,7 +193,7 @@ impl NamedFile {
/// image, and video content types, and `attachment` otherwise, and
/// the filename is taken from the path provided in the `open` method
/// after converting it to UTF-8 using.
/// [to_string_lossy](https://doc.rust-lang.org/std/ffi/struct.OsStr.html#method.to_string_lossy).
/// [`std::ffi::OsStr::to_string_lossy`]
#[inline]
pub fn set_content_disposition(mut self, cd: header::ContentDisposition) -> Self {
self.content_disposition = cd;

View File

@ -2,14 +2,20 @@
## Unreleased - 2020-xx-xx
* add ability to set address for `TestServer` [#1645]
* Upgrade `base64` to `0.13`.
## 2.1.0 - 2020-11-25
* Add ability to set address for `TestServer`. [#1645]
* Upgrade `base64` to `0.13`.
* Upgrade `serde_urlencoded` to `0.7`. [#1773]
[#1773]: https://github.com/actix/actix-web/pull/1773
[#1645]: https://github.com/actix/actix-web/pull/1645
## 2.0.0 - 2020-09-11
* Update actix-codec and actix-utils dependencies.
## 2.0.0-alpha.1 - 2020-05-23
* Update the `time` dependency to 0.2.7
* Update `actix-connect` dependency to 2.0.0-alpha.2
@ -19,74 +25,56 @@
* Update `base64` dependency to 0.12
* Update `env_logger` dependency to 0.7
## [1.0.0] - 2019-12-13
### Changed
## 1.0.0 - 2019-12-13
* Replaced `TestServer::start()` with `test_server()`
## [1.0.0-alpha.3] - 2019-12-07
### Changed
## 1.0.0-alpha.3 - 2019-12-07
* Migrate to `std::future`
## [0.2.5] - 2019-09-17
### Changed
## 0.2.5 - 2019-09-17
* Update serde_urlencoded to "0.6.1"
* Increase TestServerRuntime timeouts from 500ms to 3000ms
### Fixed
* Do not override current `System`
## [0.2.4] - 2019-07-18
## 0.2.4 - 2019-07-18
* Update actix-server to 0.6
## [0.2.3] - 2019-07-16
## 0.2.3 - 2019-07-16
* Add `delete`, `options`, `patch` methods to `TestServerRunner`
## [0.2.2] - 2019-06-16
## 0.2.2 - 2019-06-16
* Add .put() and .sput() methods
## [0.2.1] - 2019-06-05
## 0.2.1 - 2019-06-05
* Add license files
## [0.2.0] - 2019-05-12
## 0.2.0 - 2019-05-12
* Update awc and actix-http deps
## [0.1.1] - 2019-04-24
## 0.1.1 - 2019-04-24
* Always make new connection for http client
## [0.1.0] - 2019-04-16
## 0.1.0 - 2019-04-16
* No changes
## [0.1.0-alpha.3] - 2019-04-02
## 0.1.0-alpha.3 - 2019-04-02
* Request functions accept path #743
## [0.1.0-alpha.2] - 2019-03-29
## 0.1.0-alpha.2 - 2019-03-29
* Added TestServerRuntime::load_body() method
* Update actix-http and awc libraries
## [0.1.0-alpha.1] - 2019-03-28
## 0.1.0-alpha.1 - 2019-03-28
* Initial impl

View File

@ -1,8 +1,8 @@
[package]
name = "actix-http-test"
version = "2.0.0"
version = "2.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix HTTP test server"
description = "Various helpers for Actix applications to use during testing"
readme = "README.md"
keywords = ["http", "web", "framework", "async", "futures"]
homepage = "https://actix.rs"
@ -47,7 +47,7 @@ socket2 = "0.3"
serde = "1.0"
serde_json = "1.0"
slab = "0.4"
serde_urlencoded = "0.6.1"
serde_urlencoded = "0.7"
time = { version = "0.2.7", default-features = false, features = ["std"] }
open-ssl = { version = "0.10", package = "openssl", optional = true }

15
actix-http-test/README.md Normal file
View File

@ -0,0 +1,15 @@
# actix-http-test
> Various helpers for Actix applications to use during testing.
[![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test)
[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=2.1.0)](https://docs.rs/actix-http-test/2.1.0)
![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-http-test)
[![Dependency Status](https://deps.rs/crate/actix-http-test/2.1.0/status.svg)](https://deps.rs/crate/actix-http-test/2.1.0)
[![Join the chat at https://gitter.im/actix/actix-web](https://badges.gitter.im/actix/actix-web.svg)](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
## Documentation & Resources
- [API Documentation](https://docs.rs/actix-http-test)
- [Chat on Gitter](https://gitter.im/actix/actix-web)
- Minimum Supported Rust Version (MSRV): 1.42.0

View File

@ -1,4 +1,9 @@
//! Various helpers for Actix applications to use during testing.
#![deny(rust_2018_idioms)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
use std::sync::mpsc;
use std::{net, thread, time};

View File

@ -1,8 +1,13 @@
# Changes
## Unreleased - 2020-xx-xx
## 2.2.0 - 2020-11-25
### Added
* HttpResponse builders for 1xx status codes. [#1768]
* `Accept::mime_precedence` and `Accept::mime_preference`. [#1793]
* `TryFrom<u16>` and `TryFrom<f32>` for `http::header::Quality`. [#1797]
### Changed
* Upgrade `bytes` to `0.6`.
@ -10,8 +15,14 @@
### Fixed
* Started dropping `transfer-encoding: chunked` and `Content-Length` for 1XX and 204 responses. [#1767]
### Changed
* Upgrade `serde_urlencoded` to `0.7`. [#1773]
[#1773]: https://github.com/actix/actix-web/pull/1773
[#1767]: https://github.com/actix/actix-web/pull/1767
[#1768]: https://github.com/actix/actix-web/pull/1768
[#1793]: https://github.com/actix/actix-web/pull/1793
[#1797]: https://github.com/actix/actix-web/pull/1797
## 2.1.0 - 2020-10-30

View File

@ -1,6 +1,6 @@
[package]
name = "actix-http"
version = "2.1.0"
version = "2.2.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "HTTP primitives for the Actix ecosystem"
readme = "README.md"
@ -79,7 +79,7 @@ serde = "1.0"
serde_json = "1.0"
sha-1 = "0.9"
slab = "0.4"
serde_urlencoded = "0.6.1"
serde_urlencoded = "0.7"
time = { version = "0.2.7", default-features = false, features = ["std"] }
# compression

View File

@ -3,14 +3,14 @@
> HTTP primitives for the Actix ecosystem.
[![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http)
[![Documentation](https://docs.rs/actix-http/badge.svg?version=2.1.0)](https://docs.rs/actix-http/2.1.0)
[![Documentation](https://docs.rs/actix-http/badge.svg?version=2.2.0)](https://docs.rs/actix-http/2.2.0)
![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-http)
[![Dependency Status](https://deps.rs/crate/actix-http/2.1.0/status.svg)](https://deps.rs/crate/actix-http/2.1.0)
[![Dependency Status](https://deps.rs/crate/actix-http/2.2.0/status.svg)](https://deps.rs/crate/actix-http/2.2.0)
[![Join the chat at https://gitter.im/actix/actix-web](https://badges.gitter.im/actix/actix-web.svg)](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
## Documentation & Resources
- [API Documentation](https://docs.rs/actix-http/2.1.0)
- [API Documentation](https://docs.rs/actix-http)
- [Chat on Gitter](https://gitter.im/actix/actix-web)
- Minimum Supported Rust Version (MSRV): 1.42.0

View File

@ -1,3 +1,5 @@
use std::cmp::Ordering;
use mime::Mime;
use crate::header::{qitem, QualityItem};
@ -7,7 +9,7 @@ header! {
/// `Accept` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.2)
///
/// The `Accept` header field can be used by user agents to specify
/// response media types that are acceptable. Accept header fields can
/// response media types that are acceptable. Accept header fields can
/// be used to indicate that the request is specifically limited to a
/// small set of desired types, as in the case of a request for an
/// in-line image
@ -97,14 +99,14 @@ header! {
test_header!(
test1,
vec![b"audio/*; q=0.2, audio/basic"],
Some(HeaderField(vec![
Some(Accept(vec![
QualityItem::new("audio/*".parse().unwrap(), q(200)),
qitem("audio/basic".parse().unwrap()),
])));
test_header!(
test2,
vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"],
Some(HeaderField(vec![
Some(Accept(vec![
QualityItem::new(mime::TEXT_PLAIN, q(500)),
qitem(mime::TEXT_HTML),
QualityItem::new(
@ -138,23 +140,148 @@ header! {
}
impl Accept {
/// A constructor to easily create `Accept: */*`.
/// Construct `Accept: */*`.
pub fn star() -> Accept {
Accept(vec![qitem(mime::STAR_STAR)])
}
/// A constructor to easily create `Accept: application/json`.
/// Construct `Accept: application/json`.
pub fn json() -> Accept {
Accept(vec![qitem(mime::APPLICATION_JSON)])
}
/// A constructor to easily create `Accept: text/*`.
/// Construct `Accept: text/*`.
pub fn text() -> Accept {
Accept(vec![qitem(mime::TEXT_STAR)])
}
/// A constructor to easily create `Accept: image/*`.
/// Construct `Accept: image/*`.
pub fn image() -> Accept {
Accept(vec![qitem(mime::IMAGE_STAR)])
}
/// Construct `Accept: text/html`.
pub fn html() -> Accept {
Accept(vec![qitem(mime::TEXT_HTML)])
}
/// Returns a sorted list of mime types from highest to lowest preference, accounting for
/// [q-factor weighting] and specificity.
///
/// [q-factor weighting]: https://tools.ietf.org/html/rfc7231#section-5.3.2
pub fn mime_precedence(&self) -> Vec<Mime> {
let mut types = self.0.clone();
// use stable sort so items with equal q-factor and specificity retain listed order
types.sort_by(|a, b| {
// sort by q-factor descending
b.quality.cmp(&a.quality).then_with(|| {
// use specificity rules on mime types with
// same q-factor (eg. text/html > text/* > */*)
// subtypes are not comparable if main type is star, so return
match (a.item.type_(), b.item.type_()) {
(mime::STAR, mime::STAR) => return Ordering::Equal,
// a is sorted after b
(mime::STAR, _) => return Ordering::Greater,
// a is sorted before b
(_, mime::STAR) => return Ordering::Less,
_ => {}
}
// in both these match expressions, the returned ordering appears
// inverted because sort is high-to-low ("descending") precedence
match (a.item.subtype(), b.item.subtype()) {
(mime::STAR, mime::STAR) => Ordering::Equal,
// a is sorted after b
(mime::STAR, _) => Ordering::Greater,
// a is sorted before b
(_, mime::STAR) => Ordering::Less,
_ => Ordering::Equal,
}
})
});
types.into_iter().map(|qitem| qitem.item).collect()
}
/// Extracts the most preferable mime type, accounting for [q-factor weighting].
///
/// If no q-factors are provided, the first mime type is chosen. Note that items without
/// q-factors are given the maximum preference value.
///
/// Returns `None` if contained list is empty.
///
/// [q-factor weighting]: https://tools.ietf.org/html/rfc7231#section-5.3.2
pub fn mime_preference(&self) -> Option<Mime> {
let types = self.mime_precedence();
types.first().cloned()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::header::q;
#[test]
fn test_mime_precedence() {
let test = Accept(vec![]);
assert!(test.mime_precedence().is_empty());
let test = Accept(vec![qitem(mime::APPLICATION_JSON)]);
assert_eq!(test.mime_precedence(), vec!(mime::APPLICATION_JSON));
let test = Accept(vec![
qitem(mime::TEXT_HTML),
"application/xhtml+xml".parse().unwrap(),
QualityItem::new("application/xml".parse().unwrap(), q(0.9)),
QualityItem::new(mime::STAR_STAR, q(0.8)),
]);
assert_eq!(
test.mime_precedence(),
vec![
mime::TEXT_HTML,
"application/xhtml+xml".parse().unwrap(),
"application/xml".parse().unwrap(),
mime::STAR_STAR,
]
);
let test = Accept(vec![
qitem(mime::STAR_STAR),
qitem(mime::IMAGE_STAR),
qitem(mime::IMAGE_PNG),
]);
assert_eq!(
test.mime_precedence(),
vec![mime::IMAGE_PNG, mime::IMAGE_STAR, mime::STAR_STAR]
);
}
#[test]
fn test_mime_preference() {
let test = Accept(vec![
qitem(mime::TEXT_HTML),
"application/xhtml+xml".parse().unwrap(),
QualityItem::new("application/xml".parse().unwrap(), q(0.9)),
QualityItem::new(mime::STAR_STAR, q(0.8)),
]);
assert_eq!(test.mime_preference(), Some(mime::TEXT_HTML));
let test = Accept(vec![
QualityItem::new("video/*".parse().unwrap(), q(0.8)),
qitem(mime::IMAGE_PNG),
QualityItem::new(mime::STAR_STAR, q(0.5)),
qitem(mime::IMAGE_SVG),
QualityItem::new(mime::IMAGE_STAR, q(0.8)),
]);
assert_eq!(test.mime_preference(), Some(mime::IMAGE_PNG));
}
}

View File

@ -370,9 +370,7 @@ impl fmt::Display for ExtendedValue {
}
/// Percent encode a sequence of bytes with a character set defined in
/// [https://tools.ietf.org/html/rfc5987#section-3.2][url]
///
/// [url]: https://tools.ietf.org/html/rfc5987#section-3.2
/// <https://tools.ietf.org/html/rfc5987#section-3.2>
pub fn http_percent_encode(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result {
let encoded = percent_encoding::percent_encode(bytes, HTTP_VALUE);
fmt::Display::fmt(&encoded, f)

View File

@ -7,9 +7,7 @@ use self::Charset::*;
///
/// The string representation is normalized to upper case.
///
/// See [http://www.iana.org/assignments/character-sets/character-sets.xhtml][url].
///
/// [url]: http://www.iana.org/assignments/character-sets/character-sets.xhtml
/// See <http://www.iana.org/assignments/character-sets/character-sets.xhtml>.
#[derive(Clone, Debug, PartialEq)]
#[allow(non_camel_case_types)]
pub enum Charset {

View File

@ -1,10 +1,17 @@
use std::{cmp, fmt, str};
use std::{
cmp,
convert::{TryFrom, TryInto},
fmt, str,
};
use self::internal::IntoQuality;
use derive_more::{Display, Error};
const MAX_QUALITY: u16 = 1000;
const MAX_FLOAT_QUALITY: f32 = 1.0;
/// Represents a quality used in quality values.
///
/// Can be created with the `q` function.
/// Can be created with the [`q`] function.
///
/// # Implementation notes
///
@ -18,12 +25,54 @@ use self::internal::IntoQuality;
///
/// [RFC7231 Section 5.3.1](https://tools.ietf.org/html/rfc7231#section-5.3.1)
/// gives more information on quality values in HTTP header fields.
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Quality(u16);
impl Quality {
/// # Panics
/// Panics in debug mode when value is not in the range 0.0 <= n <= 1.0.
fn from_f32(value: f32) -> Self {
// Check that `value` is within range should be done before calling this method.
// Just in case, this debug_assert should catch if we were forgetful.
debug_assert!(
(0.0f32..=1.0f32).contains(&value),
"q value must be between 0.0 and 1.0"
);
Quality((value * MAX_QUALITY as f32) as u16)
}
}
impl Default for Quality {
fn default() -> Quality {
Quality(1000)
Quality(MAX_QUALITY)
}
}
#[derive(Debug, Clone, Display, Error)]
pub struct QualityOutOfBounds;
impl TryFrom<u16> for Quality {
type Error = QualityOutOfBounds;
fn try_from(value: u16) -> Result<Self, Self::Error> {
if (0..=MAX_QUALITY).contains(&value) {
Ok(Quality(value))
} else {
Err(QualityOutOfBounds)
}
}
}
impl TryFrom<f32> for Quality {
type Error = QualityOutOfBounds;
fn try_from(value: f32) -> Result<Self, Self::Error> {
if (0.0..=MAX_FLOAT_QUALITY).contains(&value) {
Ok(Quality::from_f32(value))
} else {
Err(QualityOutOfBounds)
}
}
}
@ -55,8 +104,9 @@ impl<T: PartialEq> cmp::PartialOrd for QualityItem<T> {
impl<T: fmt::Display> fmt::Display for QualityItem<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.item, f)?;
match self.quality.0 {
1000 => Ok(()),
MAX_QUALITY => Ok(()),
0 => f.write_str("; q=0"),
x => write!(f, "; q=0.{}", format!("{:03}", x).trim_end_matches('0')),
}
@ -66,105 +116,79 @@ impl<T: fmt::Display> fmt::Display for QualityItem<T> {
impl<T: str::FromStr> str::FromStr for QualityItem<T> {
type Err = crate::error::ParseError;
fn from_str(s: &str) -> Result<QualityItem<T>, crate::error::ParseError> {
if !s.is_ascii() {
fn from_str(qitem_str: &str) -> Result<QualityItem<T>, crate::error::ParseError> {
if !qitem_str.is_ascii() {
return Err(crate::error::ParseError::Header);
}
// Set defaults used if parsing fails.
let mut raw_item = s;
let mut raw_item = qitem_str;
let mut quality = 1f32;
let parts: Vec<&str> = s.rsplitn(2, ';').map(|x| x.trim()).collect();
let parts: Vec<_> = qitem_str.rsplitn(2, ';').map(str::trim).collect();
if parts.len() == 2 {
// example for item with q-factor:
//
// gzip; q=0.65
// ^^^^^^ parts[0]
// ^^ start
// ^^^^ q_val
// ^^^^ parts[1]
if parts[0].len() < 2 {
// Can't possibly be an attribute since an attribute needs at least a name followed
// by an equals sign. And bare identifiers are forbidden.
return Err(crate::error::ParseError::Header);
}
let start = &parts[0][0..2];
if start == "q=" || start == "Q=" {
let q_part = &parts[0][2..parts[0].len()];
if q_part.len() > 5 {
let q_val = &parts[0][2..];
if q_val.len() > 5 {
// longer than 5 indicates an over-precise q-factor
return Err(crate::error::ParseError::Header);
}
match q_part.parse::<f32>() {
Ok(q_value) => {
if 0f32 <= q_value && q_value <= 1f32 {
quality = q_value;
raw_item = parts[1];
} else {
return Err(crate::error::ParseError::Header);
}
}
Err(_) => return Err(crate::error::ParseError::Header),
let q_value = q_val
.parse::<f32>()
.map_err(|_| crate::error::ParseError::Header)?;
if (0f32..=1f32).contains(&q_value) {
quality = q_value;
raw_item = parts[1];
} else {
return Err(crate::error::ParseError::Header);
}
}
}
match raw_item.parse::<T>() {
// we already checked above that the quality is within range
Ok(item) => Ok(QualityItem::new(item, from_f32(quality))),
Err(_) => Err(crate::error::ParseError::Header),
}
}
}
#[inline]
fn from_f32(f: f32) -> Quality {
// this function is only used internally. A check that `f` is within range
// should be done before calling this method. Just in case, this
// debug_assert should catch if we were forgetful
debug_assert!(
f >= 0f32 && f <= 1f32,
"q value must be between 0.0 and 1.0"
);
Quality((f * 1000f32) as u16)
let item = raw_item
.parse::<T>()
.map_err(|_| crate::error::ParseError::Header)?;
// we already checked above that the quality is within range
Ok(QualityItem::new(item, Quality::from_f32(quality)))
}
}
/// Convenience function to wrap a value in a `QualityItem`
/// Sets `q` to the default 1.0
pub fn qitem<T>(item: T) -> QualityItem<T> {
QualityItem::new(item, Default::default())
QualityItem::new(item, Quality::default())
}
/// Convenience function to create a `Quality` from a float or integer.
///
/// Implemented for `u16` and `f32`. Panics if value is out of range.
pub fn q<T: IntoQuality>(val: T) -> Quality {
val.into_quality()
}
mod internal {
use super::Quality;
// TryFrom is probably better, but it's not stable. For now, we want to
// keep the functionality of the `q` function, while allowing it to be
// generic over `f32` and `u16`.
//
// `q` would panic before, so keep that behavior. `TryFrom` can be
// introduced later for a non-panicking conversion.
pub trait IntoQuality: Sealed + Sized {
fn into_quality(self) -> Quality;
}
impl IntoQuality for f32 {
fn into_quality(self) -> Quality {
assert!(
self >= 0f32 && self <= 1f32,
"float must be between 0.0 and 1.0"
);
super::from_f32(self)
}
}
impl IntoQuality for u16 {
fn into_quality(self) -> Quality {
assert!(self <= 1000, "u16 must be between 0 and 1000");
Quality(self)
}
}
pub trait Sealed {}
impl Sealed for u16 {}
impl Sealed for f32 {}
pub fn q<T>(val: T) -> Quality
where
T: TryInto<Quality>,
T::Error: fmt::Debug,
{
// TODO: on next breaking change, handle unwrap differently
val.try_into().unwrap()
}
#[cfg(test)]
@ -270,15 +294,13 @@ mod tests {
}
#[test]
#[should_panic] // FIXME - 32-bit msvc unwinding broken
#[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)]
#[should_panic]
fn test_quality_invalid() {
q(-1.0);
}
#[test]
#[should_panic] // FIXME - 32-bit msvc unwinding broken
#[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)]
#[should_panic]
fn test_quality_invalid2() {
q(2.0);
}

View File

@ -4,6 +4,18 @@
### Changed
* Upgrade `bytes` to `0.6`.
## 2.0.3 - 2020-11-29
### Fixed
* Ensure `actix-http` dependency uses same `serde_urlencoded`.
## 2.0.2 - 2020-11-25
### Changed
* Upgrade `serde_urlencoded` to `0.7`. [#1773]
[#1773]: https://github.com/actix/actix-web/pull/1773
## 2.0.1 - 2020-10-30
### Changed
* Upgrade `base64` to `0.13`. [#1744]

View File

@ -1,6 +1,6 @@
[package]
name = "awc"
version = "2.0.1"
version = "2.0.3"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Async HTTP and WebSocket client library built on the Actix ecosystem"
readme = "README.md"
@ -39,7 +39,7 @@ compress = ["actix-http/compress"]
[dependencies]
actix-codec = "0.3.0"
actix-service = "1.0.6"
actix-http = "2.0.0"
actix-http = "2.2.0"
actix-rt = "1.0.0"
base64 = "0.13"
@ -53,7 +53,7 @@ percent-encoding = "2.1"
rand = "0.7"
serde = "1.0"
serde_json = "1.0"
serde_urlencoded = "0.6.1"
serde_urlencoded = "0.7"
open-ssl = { version = "0.10", package = "openssl", optional = true }
rust-tls = { version = "0.18.0", package = "rustls", optional = true, features = ["dangerous_configuration"] }

View File

@ -3,36 +3,34 @@
> Async HTTP and WebSocket client library.
[![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc)
[![Documentation](https://docs.rs/awc/badge.svg?version=2.0.1)](https://docs.rs/awc/2.0.1)
[![Documentation](https://docs.rs/awc/badge.svg?version=2.0.3)](https://docs.rs/awc/2.0.3)
![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/awc)
[![Dependency Status](https://deps.rs/crate/awc/2.0.1/status.svg)](https://deps.rs/crate/awc/2.0.1)
[![Dependency Status](https://deps.rs/crate/awc/2.0.3/status.svg)](https://deps.rs/crate/awc/2.0.3)
[![Join the chat at https://gitter.im/actix/actix-web](https://badges.gitter.im/actix/actix-web.svg)](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
## Documentation & Resources
- [API Documentation](https://docs.rs/awc/2.0.1)
- [API Documentation](https://docs.rs/awc)
- [Example Project](https://github.com/actix/examples/tree/HEAD/awc_https)
- [Chat on Gitter](https://gitter.im/actix/actix-web)
- Minimum Supported Rust Version (MSRV): 1.42.0
## Example
```rust
use actix_rt::System;
use awc::Client;
use futures::future::{Future, lazy};
fn main() {
System::new("test").block_on(lazy(|| {
let mut client = Client::default();
System::new("test").block_on(async {
let client = Client::default();
client.get("http://www.rust-lang.org") // <- Create request builder
.header("User-Agent", "Actix-web")
.send() // <- Send http request
.and_then(|response| { // <- server http response
println!("Response: {:?}", response);
Ok(())
})
}));
let res = client
.get("http://www.rust-lang.org") // <- Create request builder
.header("User-Agent", "Actix-web")
.send() // <- Send http request
.await;
println!("Response: {:?}", res); // <- server http response
});
}
```

View File

@ -2,29 +2,31 @@ digraph {
subgraph cluster_web {
label="actix/actix-web"
"awc"
"actix-web"
"actix-files"
"actix-http"
"actix-multipart"
"actix-web-actors"
"actix-web-codegen"
"web"
"files"
"http"
"multipart"
"web-actors"
"codegen"
"http-test"
}
"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" }
"web" -> { "codec" "service" "utils" "router" "rt" "server" "testing" "macros" "threadpool" "tls" "codegen" "http" "awc" }
"awc" -> { "codec" "service" "http" "rt" }
"web-actors" -> { "actix" "web" "http" "codec" }
"multipart" -> { "web" "service" "utils" }
"http" -> { "service" "codec" "connect" "utils" "rt" "threadpool" }
"http" -> { "actix" "tls" }[color=blue] // optional
"files" -> { "web" }
"http-test" -> { "service" "codec" "connect" "utils" "rt" "server" "testing" "awc" }
// net
"actix-utils" -> { "actix-service" "actix-rt" "actix-codec" }
"actix-tracing" -> { "actix-service" }
"actix-tls" -> { "actix-service" "actix-codec" "actix-utils" }
"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" }
"utils" -> { "service" "rt" "codec" }
"tracing" -> { "service" }
"tls" -> { "service" "codec" "utils" }
"testing" -> { "rt" "macros" "server" "service" }
"server" -> { "service" "rt" "codec" "utils" }
"rt" -> { "macros" "threadpool" }
"connect" -> { "service" "codec" "utils" "rt" }
}

View File

@ -8,12 +8,14 @@ digraph {
"actix-multipart"
"actix-web-actors"
"actix-web-codegen"
"actix-http-test"
}
"actix-web" -> { "actix-web-codegen" "actix-http" "awc" }
"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" }
"actix-files" -> { "actix-web" }
"actix-http-test" -> { "awc" }
}

View File

@ -1,4 +1,5 @@
//! Error and Result module
pub use actix_http::error::*;
use derive_more::{Display, From};
use serde_json::error::Error as JsonError;

View File

@ -1,4 +1,4 @@
//! Actix web is a powerful, pragmatic, and extremely fast web framework for Rust.
//! Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust.
//!
//! ## Example
//!
@ -102,10 +102,11 @@ pub use crate::app::App;
pub use crate::extract::FromRequest;
pub use crate::request::HttpRequest;
pub use crate::resource::Resource;
pub use crate::responder::{Either, Responder};
pub use crate::responder::Responder;
pub use crate::route::Route;
pub use crate::scope::Scope;
pub use crate::server::HttpServer;
pub use crate::types::{Either, EitherExtractError};
pub mod dev {
//! The `actix-web` prelude for library developers

View File

@ -137,9 +137,9 @@ where
// so the change can not be deduced from the length comparison
if path != original_path {
let mut parts = head.uri.clone().into_parts();
let pq = parts.path_and_query.as_ref().unwrap();
let query = parts.path_and_query.as_ref().and_then(|pq| pq.query());
let path = if let Some(q) = pq.query() {
let path = if let Some(q) = query {
Bytes::from(format!("{}?{}", path, q))
} else {
Bytes::copy_from_slice(path.as_bytes())

View File

@ -675,4 +675,40 @@ mod tests {
let res = call_service(&mut srv, req).await;
assert_eq!(res.status(), StatusCode::OK);
}
#[actix_rt::test]
async fn extract_path_pattern_complex() {
let mut srv = init_service(
App::new()
.service(web::scope("/user").service(web::scope("/{id}").service(
web::resource("").to(move |req: HttpRequest| {
assert_eq!(req.match_pattern(), Some("/user/{id}".to_owned()));
HttpResponse::Ok().finish()
}),
)))
.service(web::resource("/").to(move |req: HttpRequest| {
assert_eq!(req.match_pattern(), Some("/".to_owned()));
HttpResponse::Ok().finish()
}))
.default_service(web::to(move |req: HttpRequest| {
assert!(req.match_pattern().is_none());
HttpResponse::Ok().finish()
})),
)
.await;
let req = TestRequest::get().uri("/user/test").to_request();
let res = call_service(&mut srv, req).await;
assert_eq!(res.status(), StatusCode::OK);
let req = TestRequest::get().uri("/").to_request();
let res = call_service(&mut srv, req).await;
assert_eq!(res.status(), StatusCode::OK);
let req = TestRequest::get().uri("/not-exist").to_request();
let res = call_service(&mut srv, req).await;
assert_eq!(res.status(), StatusCode::OK);
}
}

View File

@ -50,7 +50,7 @@ use crate::{dev::Payload, FromRequest, HttpRequest};
pub struct ReqData<T: Clone + 'static>(T);
impl<T: Clone + 'static> ReqData<T> {
/// Consumes the `ReqData`, returning it's wrapped data.
/// Consumes the `ReqData`, returning its wrapped data.
pub fn into_inner(self) -> T {
self.0
}

View File

@ -332,82 +332,6 @@ impl<T: Responder> Future for CustomResponderFut<T> {
}
}
/// Combines two different responder types into a single type
///
/// ```rust
/// use actix_web::{Either, Error, HttpResponse};
///
/// type RegisterResult = Either<HttpResponse, Result<HttpResponse, Error>>;
///
/// fn index() -> RegisterResult {
/// if is_a_variant() {
/// // <- choose left variant
/// Either::A(HttpResponse::BadRequest().body("Bad data"))
/// } else {
/// Either::B(
/// // <- Right variant
/// Ok(HttpResponse::Ok()
/// .content_type("text/html")
/// .body("Hello!"))
/// )
/// }
/// }
/// # fn is_a_variant() -> bool { true }
/// # fn main() {}
/// ```
#[derive(Debug, PartialEq)]
pub enum Either<A, B> {
/// First branch of the type
A(A),
/// Second branch of the type
B(B),
}
impl<A, B> Responder for Either<A, B>
where
A: Responder,
B: Responder,
{
type Error = Error;
type Future = EitherResponder<A, B>;
fn respond_to(self, req: &HttpRequest) -> Self::Future {
match self {
Either::A(a) => EitherResponder::A(a.respond_to(req)),
Either::B(b) => EitherResponder::B(b.respond_to(req)),
}
}
}
#[pin_project(project = EitherResponderProj)]
pub enum EitherResponder<A, B>
where
A: Responder,
B: Responder,
{
A(#[pin] A::Future),
B(#[pin] B::Future),
}
impl<A, B> Future for EitherResponder<A, B>
where
A: Responder,
B: Responder,
{
type Output = Result<Response, Error>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.project() {
EitherResponderProj::A(fut) => {
Poll::Ready(ready!(fut.poll(cx)).map_err(|e| e.into()))
}
EitherResponderProj::B(fut) => {
Poll::Ready(ready!(fut.poll(cx).map_err(|e| e.into())))
}
}
}
}
impl<T> Responder for InternalError<T>
where
T: std::fmt::Debug + std::fmt::Display + 'static,

View File

@ -86,7 +86,7 @@ impl ResourceMap {
if let Some(plen) = pattern.is_prefix_match(path) {
return rmap.has_resource(&path[plen..]);
}
} else if pattern.is_match(path) {
} else if pattern.is_match(path) || pattern.pattern() == "" && path == "/" {
return true;
}
}

View File

@ -269,8 +269,9 @@ where
{
let body = read_body(res).await;
serde_json::from_slice(&body)
.unwrap_or_else(|_| panic!("read_response_json failed during deserialization"))
serde_json::from_slice(&body).unwrap_or_else(|e| {
panic!("read_response_json failed during deserialization: {}", e)
})
}
pub async fn load_stream<S>(mut stream: S) -> Result<Bytes, Error>

274
src/types/either.rs Normal file
View File

@ -0,0 +1,274 @@
use std::{
future::Future,
pin::Pin,
task::{Context, Poll},
};
use actix_http::{Error, Response};
use bytes::Bytes;
use futures_util::{future::LocalBoxFuture, ready, FutureExt, TryFutureExt};
use pin_project::pin_project;
use crate::{dev, request::HttpRequest, FromRequest, Responder};
/// Combines two different responder types into a single type
///
/// ```rust
/// use actix_web::{Either, Error, HttpResponse};
///
/// type RegisterResult = Either<HttpResponse, Result<HttpResponse, Error>>;
///
/// fn index() -> RegisterResult {
/// if is_a_variant() {
/// // <- choose left variant
/// Either::A(HttpResponse::BadRequest().body("Bad data"))
/// } else {
/// Either::B(
/// // <- Right variant
/// Ok(HttpResponse::Ok()
/// .content_type("text/html")
/// .body("Hello!"))
/// )
/// }
/// }
/// # fn is_a_variant() -> bool { true }
/// # fn main() {}
/// ```
#[derive(Debug, PartialEq)]
pub enum Either<A, B> {
/// First branch of the type
A(A),
/// Second branch of the type
B(B),
}
#[cfg(test)]
impl<A, B> Either<A, B> {
pub(self) fn unwrap_left(self) -> A {
match self {
Either::A(data) => data,
Either::B(_) => {
panic!("Cannot unwrap left branch. Either contains a right branch.")
}
}
}
pub(self) fn unwrap_right(self) -> B {
match self {
Either::A(_) => {
panic!("Cannot unwrap right branch. Either contains a left branch.")
}
Either::B(data) => data,
}
}
}
impl<A, B> Responder for Either<A, B>
where
A: Responder,
B: Responder,
{
type Error = Error;
type Future = EitherResponder<A, B>;
fn respond_to(self, req: &HttpRequest) -> Self::Future {
match self {
Either::A(a) => EitherResponder::A(a.respond_to(req)),
Either::B(b) => EitherResponder::B(b.respond_to(req)),
}
}
}
#[pin_project(project = EitherResponderProj)]
pub enum EitherResponder<A, B>
where
A: Responder,
B: Responder,
{
A(#[pin] A::Future),
B(#[pin] B::Future),
}
impl<A, B> Future for EitherResponder<A, B>
where
A: Responder,
B: Responder,
{
type Output = Result<Response, Error>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.project() {
EitherResponderProj::A(fut) => {
Poll::Ready(ready!(fut.poll(cx)).map_err(|e| e.into()))
}
EitherResponderProj::B(fut) => {
Poll::Ready(ready!(fut.poll(cx).map_err(|e| e.into())))
}
}
}
}
/// A composite error resulting from failure to extract an `Either<A, B>`.
///
/// The implementation of `Into<actix_web::Error>` will return the payload buffering error or the
/// error from the primary extractor. To access the fallback error, use a match clause.
#[derive(Debug)]
pub enum EitherExtractError<A, B> {
/// Error from payload buffering, such as exceeding payload max size limit.
Bytes(Error),
/// Error from primary extractor.
Extract(A, B),
}
impl<A, B> Into<Error> for EitherExtractError<A, B>
where
A: Into<Error>,
B: Into<Error>,
{
fn into(self) -> Error {
match self {
EitherExtractError::Bytes(err) => err,
EitherExtractError::Extract(a_err, _b_err) => a_err.into(),
}
}
}
/// Provides a mechanism for trying two extractors, a primary and a fallback. Useful for
/// "polymorphic payloads" where, for example, a form might be JSON or URL encoded.
///
/// It is important to note that this extractor, by necessity, buffers the entire request payload
/// as part of its implementation. Though, it does respect a `PayloadConfig`'s maximum size limit.
impl<A, B> FromRequest for Either<A, B>
where
A: FromRequest + 'static,
B: FromRequest + 'static,
{
type Error = EitherExtractError<A::Error, B::Error>;
type Future = LocalBoxFuture<'static, Result<Self, Self::Error>>;
type Config = ();
fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
let req2 = req.clone();
Bytes::from_request(req, payload)
.map_err(EitherExtractError::Bytes)
.and_then(|bytes| bytes_to_a_or_b(req2, bytes))
.boxed_local()
}
}
async fn bytes_to_a_or_b<A, B>(
req: HttpRequest,
bytes: Bytes,
) -> Result<Either<A, B>, EitherExtractError<A::Error, B::Error>>
where
A: FromRequest + 'static,
B: FromRequest + 'static,
{
let fallback = bytes.clone();
let a_err;
let mut pl = payload_from_bytes(bytes);
match A::from_request(&req, &mut pl).await {
Ok(a_data) => return Ok(Either::A(a_data)),
// store A's error for returning if B also fails
Err(err) => a_err = err,
};
let mut pl = payload_from_bytes(fallback);
match B::from_request(&req, &mut pl).await {
Ok(b_data) => return Ok(Either::B(b_data)),
Err(b_err) => Err(EitherExtractError::Extract(a_err, b_err)),
}
}
fn payload_from_bytes(bytes: Bytes) -> dev::Payload {
let (_, mut h1_payload) = actix_http::h1::Payload::create(true);
h1_payload.unread_data(bytes);
dev::Payload::from(h1_payload)
}
#[cfg(test)]
mod tests {
use serde::{Deserialize, Serialize};
use super::*;
use crate::{
test::TestRequest,
web::{Form, Json},
};
#[derive(Debug, Clone, Serialize, Deserialize)]
struct TestForm {
hello: String,
}
#[actix_rt::test]
async fn test_either_extract_first_try() {
let (req, mut pl) = TestRequest::default()
.set_form(&TestForm {
hello: "world".to_owned(),
})
.to_http_parts();
let form = Either::<Form<TestForm>, Json<TestForm>>::from_request(&req, &mut pl)
.await
.unwrap()
.unwrap_left()
.into_inner();
assert_eq!(&form.hello, "world");
}
#[actix_rt::test]
async fn test_either_extract_fallback() {
let (req, mut pl) = TestRequest::default()
.set_json(&TestForm {
hello: "world".to_owned(),
})
.to_http_parts();
let form = Either::<Form<TestForm>, Json<TestForm>>::from_request(&req, &mut pl)
.await
.unwrap()
.unwrap_right()
.into_inner();
assert_eq!(&form.hello, "world");
}
#[actix_rt::test]
async fn test_either_extract_recursive_fallback() {
let (req, mut pl) = TestRequest::default()
.set_payload(Bytes::from_static(b"!@$%^&*()"))
.to_http_parts();
let payload =
Either::<Either<Form<TestForm>, Json<TestForm>>, Bytes>::from_request(
&req, &mut pl,
)
.await
.unwrap()
.unwrap_right();
assert_eq!(&payload.as_ref(), &b"!@$%^&*()");
}
#[actix_rt::test]
async fn test_either_extract_recursive_fallback_inner() {
let (req, mut pl) = TestRequest::default()
.set_json(&TestForm {
hello: "world".to_owned(),
})
.to_http_parts();
let form =
Either::<Either<Form<TestForm>, Json<TestForm>>, Bytes>::from_request(
&req, &mut pl,
)
.await
.unwrap()
.unwrap_left()
.unwrap_right()
.into_inner();
assert_eq!(&form.hello, "world");
}
}

View File

@ -1,5 +1,6 @@
//! Helper types
mod either;
pub(crate) mod form;
pub(crate) mod json;
mod path;
@ -7,6 +8,7 @@ pub(crate) mod payload;
mod query;
pub(crate) mod readlines;
pub use self::either::{Either, EitherExtractError};
pub use self::form::{Form, FormConfig};
pub use self::json::{Json, JsonConfig};
pub use self::path::{Path, PathConfig};

View File

@ -39,7 +39,7 @@ use crate::request::HttpRequest;
/// }
///
/// // Use `Query` extractor for query information (and destructure it within the signature).
/// // This handler gets called only if the request's query string contains a `username` field.
/// // This handler gets called only if the request's query string contains `id` and `response_type` fields.
/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"`.
/// async fn index(web::Query(info): web::Query<AuthRequest>) -> String {
/// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type)
@ -117,7 +117,7 @@ impl<T: fmt::Display> fmt::Display for Query<T> {
/// }
///
/// // Use `Query` extractor for query information.
/// // This handler get called only if request's query contains `username` field
/// // This handler get called only if request's query contains `id` and `response_type` fields.
/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"`
/// async fn index(info: web::Query<AuthRequest>) -> String {
/// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type)

View File

@ -1,9 +0,0 @@
# Actix http test server [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-http-test)](https://crates.io/crates/actix-http-test) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
## Documentation & community resources
* [User Guide](https://actix.rs/docs/)
* [API Documentation](https://docs.rs/actix-http-test/)
* [Chat on gitter](https://gitter.im/actix/actix)
* Cargo package: [actix-http-test](https://crates.io/crates/actix-http-test)
* Minimum supported Rust version: 1.40 or later