mirror of https://github.com/fafhrd91/actix-web
Merge branch 'master' of github.com:actix/actix-web
This commit is contained in:
commit
5bd43783c6
|
@ -30,7 +30,7 @@ jobs:
|
||||||
run: echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html
|
run: echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html
|
||||||
|
|
||||||
- name: Deploy to GitHub Pages
|
- name: Deploy to GitHub Pages
|
||||||
uses: JamesIves/github-pages-deploy-action@3.5.8
|
uses: JamesIves/github-pages-deploy-action@3.7.1
|
||||||
with:
|
with:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
BRANCH: gh-pages
|
BRANCH: gh-pages
|
||||||
|
|
31
CHANGES.md
31
CHANGES.md
|
@ -1,8 +1,37 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2020-xx-xx
|
## 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
|
### Changed
|
||||||
* Upgrade `serde_urlencoded` to `0.7`.
|
* 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
|
## 3.2.0 - 2020-10-30
|
||||||
|
|
18
Cargo.toml
18
Cargo.toml
|
@ -1,8 +1,8 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-web"
|
name = "actix-web"
|
||||||
version = "3.2.0"
|
version = "3.3.2"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
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"
|
readme = "README.md"
|
||||||
keywords = ["actix", "http", "web", "framework", "async"]
|
keywords = ["actix", "http", "web", "framework", "async"]
|
||||||
homepage = "https://actix.rs"
|
homepage = "https://actix.rs"
|
||||||
|
@ -34,7 +34,7 @@ members = [
|
||||||
"actix-multipart",
|
"actix-multipart",
|
||||||
"actix-web-actors",
|
"actix-web-actors",
|
||||||
"actix-web-codegen",
|
"actix-web-codegen",
|
||||||
"test-server",
|
"actix-http-test",
|
||||||
]
|
]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
@ -85,11 +85,11 @@ actix-threadpool = "0.3.1"
|
||||||
actix-tls = "2.0.0"
|
actix-tls = "2.0.0"
|
||||||
|
|
||||||
actix-web-codegen = "0.4.0"
|
actix-web-codegen = "0.4.0"
|
||||||
actix-http = "2.1.0"
|
actix-http = "2.2.0"
|
||||||
awc = { version = "2.0.0", default-features = false }
|
awc = { version = "2.0.3", default-features = false }
|
||||||
|
|
||||||
bytes = "0.5.3"
|
bytes = "0.5.3"
|
||||||
derive_more = "0.99.2"
|
derive_more = "0.99.5"
|
||||||
encoding_rs = "0.8"
|
encoding_rs = "0.8"
|
||||||
futures-channel = { version = "0.3.5", default-features = false }
|
futures-channel = { version = "0.3.5", default-features = false }
|
||||||
futures-core = { version = "0.3.5", default-features = false }
|
futures-core = { version = "0.3.5", default-features = false }
|
||||||
|
@ -97,7 +97,7 @@ futures-util = { version = "0.3.5", default-features = false }
|
||||||
fxhash = "0.2.1"
|
fxhash = "0.2.1"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
socket2 = "0.3"
|
socket2 = "0.3.16"
|
||||||
pin-project = "1.0.0"
|
pin-project = "1.0.0"
|
||||||
regex = "1.4"
|
regex = "1.4"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
@ -127,10 +127,10 @@ codegen-units = 1
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
actix-web = { path = "." }
|
actix-web = { path = "." }
|
||||||
actix-http = { path = "actix-http" }
|
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-web-codegen = { path = "actix-web-codegen" }
|
||||||
actix-files = { path = "actix-files" }
|
|
||||||
actix-multipart = { path = "actix-multipart" }
|
actix-multipart = { path = "actix-multipart" }
|
||||||
|
actix-files = { path = "actix-files" }
|
||||||
awc = { path = "awc" }
|
awc = { path = "awc" }
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<h1>Actix web</h1>
|
<h1>Actix web</h1>
|
||||||
<p>
|
<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>
|
||||||
<p>
|
<p>
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-web)
|
[](https://crates.io/crates/actix-web)
|
||||||
[](https://docs.rs/actix-web/3.2.0)
|
[](https://docs.rs/actix-web/3.3.2)
|
||||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html)
|
||||||

|

|
||||||
[](https://deps.rs/crate/actix-web/3.2.0)
|
[](https://deps.rs/crate/actix-web/3.3.2)
|
||||||
<br />
|
<br />
|
||||||
[](https://travis-ci.org/actix/actix-web)
|
[](https://travis-ci.org/actix/actix-web)
|
||||||
[](https://codecov.io/gh/actix/actix-web)
|
[](https://codecov.io/gh/actix/actix-web)
|
||||||
|
|
|
@ -3,6 +3,10 @@
|
||||||
## Unreleased - 2020-xx-xx
|
## 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
|
## 0.4.0 - 2020-10-06
|
||||||
* Add `Files::prefer_utf8` option that adds UTF-8 charset on certain response types. [#1714]
|
* Add `Files::prefer_utf8` option that adds UTF-8 charset on certain response types. [#1714]
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-files"
|
name = "actix-files"
|
||||||
version = "0.4.0"
|
version = "0.4.1"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Static file serving for Actix Web"
|
description = "Static file serving for Actix Web"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -21,14 +21,14 @@ actix-web = { version = "3.0.0", default-features = false }
|
||||||
actix-service = "1.0.6"
|
actix-service = "1.0.6"
|
||||||
bitflags = "1"
|
bitflags = "1"
|
||||||
bytes = "0.5.3"
|
bytes = "0.5.3"
|
||||||
futures-core = { version = "0.3.5", default-features = false }
|
futures-core = { version = "0.3.7", default-features = false }
|
||||||
futures-util = { version = "0.3.5", default-features = false }
|
futures-util = { version = "0.3.7", default-features = false }
|
||||||
derive_more = "0.99.2"
|
derive_more = "0.99.2"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
mime_guess = "2.0.1"
|
mime_guess = "2.0.1"
|
||||||
percent-encoding = "2.1"
|
percent-encoding = "2.1"
|
||||||
v_htmlescape = "0.10"
|
v_htmlescape = "0.11"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "1.0.0"
|
actix-rt = "1.0.0"
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
|
|
||||||
> Static file serving for Actix Web
|
> Static file serving for Actix Web
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-files)
|
[](https://crates.io/crates/actix-files)
|
||||||
[](https://docs.rs/actix-files)
|
[](https://docs.rs/actix-files/0.4.1)
|
||||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html)
|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-files/0.4.0)
|
[](https://deps.rs/crate/actix-files/0.4.1)
|
||||||
[](https://crates.io/crates/actix-files)
|
[](https://crates.io/crates/actix-files)
|
||||||
[](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
[](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
|
|
|
@ -65,13 +65,25 @@ impl Clone for Files {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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.
|
/// # Argument Order
|
||||||
/// By default pool with 5x threads of available cpus is used.
|
/// The first argument (`mount_path`) is the root URL at which the static files are served.
|
||||||
/// Pool size can be changed by setting ACTIX_THREADPOOL environment variable.
|
/// For example, `/assets` will serve files at `example.com/assets/...`.
|
||||||
pub fn new<T: Into<PathBuf>>(path: &str, dir: T) -> Files {
|
///
|
||||||
let orig_dir = dir.into();
|
/// 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() {
|
let dir = match orig_dir.canonicalize() {
|
||||||
Ok(canon_dir) => canon_dir,
|
Ok(canon_dir) => canon_dir,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
|
@ -81,7 +93,7 @@ impl Files {
|
||||||
};
|
};
|
||||||
|
|
||||||
Files {
|
Files {
|
||||||
path: path.to_string(),
|
path: mount_path.to_owned(),
|
||||||
directory: dir,
|
directory: dir,
|
||||||
index: None,
|
index: None,
|
||||||
show_index: false,
|
show_index: false,
|
||||||
|
|
|
@ -2,15 +2,20 @@
|
||||||
|
|
||||||
## Unreleased - 2020-xx-xx
|
## Unreleased - 2020-xx-xx
|
||||||
|
|
||||||
* add ability to set address for `TestServer` [#1645]
|
|
||||||
* Upgrade `base64` to `0.13`.
|
|
||||||
* Upgrade `serde_urlencoded` to `0.7`.
|
|
||||||
|
|
||||||
|
## 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
|
[#1645]: https://github.com/actix/actix-web/pull/1645
|
||||||
|
|
||||||
|
|
||||||
## 2.0.0 - 2020-09-11
|
## 2.0.0 - 2020-09-11
|
||||||
* Update actix-codec and actix-utils dependencies.
|
* Update actix-codec and actix-utils dependencies.
|
||||||
|
|
||||||
|
|
||||||
## 2.0.0-alpha.1 - 2020-05-23
|
## 2.0.0-alpha.1 - 2020-05-23
|
||||||
* Update the `time` dependency to 0.2.7
|
* Update the `time` dependency to 0.2.7
|
||||||
* Update `actix-connect` dependency to 2.0.0-alpha.2
|
* Update `actix-connect` dependency to 2.0.0-alpha.2
|
||||||
|
@ -20,74 +25,56 @@
|
||||||
* Update `base64` dependency to 0.12
|
* Update `base64` dependency to 0.12
|
||||||
* Update `env_logger` dependency to 0.7
|
* Update `env_logger` dependency to 0.7
|
||||||
|
|
||||||
## [1.0.0] - 2019-12-13
|
## 1.0.0 - 2019-12-13
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
* Replaced `TestServer::start()` with `test_server()`
|
* Replaced `TestServer::start()` with `test_server()`
|
||||||
|
|
||||||
|
|
||||||
## [1.0.0-alpha.3] - 2019-12-07
|
## 1.0.0-alpha.3 - 2019-12-07
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
* Migrate to `std::future`
|
* Migrate to `std::future`
|
||||||
|
|
||||||
|
|
||||||
## [0.2.5] - 2019-09-17
|
## 0.2.5 - 2019-09-17
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
* Update serde_urlencoded to "0.6.1"
|
* Update serde_urlencoded to "0.6.1"
|
||||||
* Increase TestServerRuntime timeouts from 500ms to 3000ms
|
* Increase TestServerRuntime timeouts from 500ms to 3000ms
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
* Do not override current `System`
|
* Do not override current `System`
|
||||||
|
|
||||||
|
|
||||||
## [0.2.4] - 2019-07-18
|
## 0.2.4 - 2019-07-18
|
||||||
|
|
||||||
* Update actix-server to 0.6
|
* 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`
|
* Add `delete`, `options`, `patch` methods to `TestServerRunner`
|
||||||
|
|
||||||
## [0.2.2] - 2019-06-16
|
|
||||||
|
|
||||||
|
## 0.2.2 - 2019-06-16
|
||||||
* Add .put() and .sput() methods
|
* Add .put() and .sput() methods
|
||||||
|
|
||||||
## [0.2.1] - 2019-06-05
|
|
||||||
|
|
||||||
|
## 0.2.1 - 2019-06-05
|
||||||
* Add license files
|
* Add license files
|
||||||
|
|
||||||
## [0.2.0] - 2019-05-12
|
|
||||||
|
|
||||||
|
## 0.2.0 - 2019-05-12
|
||||||
* Update awc and actix-http deps
|
* 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
|
* Always make new connection for http client
|
||||||
|
|
||||||
|
|
||||||
## [0.1.0] - 2019-04-16
|
## 0.1.0 - 2019-04-16
|
||||||
|
|
||||||
* No changes
|
* No changes
|
||||||
|
|
||||||
|
|
||||||
## [0.1.0-alpha.3] - 2019-04-02
|
## 0.1.0-alpha.3 - 2019-04-02
|
||||||
|
|
||||||
* Request functions accept path #743
|
* 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
|
* Added TestServerRuntime::load_body() method
|
||||||
|
|
||||||
* Update actix-http and awc libraries
|
* Update actix-http and awc libraries
|
||||||
|
|
||||||
|
|
||||||
## [0.1.0-alpha.1] - 2019-03-28
|
## 0.1.0-alpha.1 - 2019-03-28
|
||||||
|
|
||||||
* Initial impl
|
* Initial impl
|
|
@ -1,8 +1,8 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-http-test"
|
name = "actix-http-test"
|
||||||
version = "2.0.0"
|
version = "2.1.0"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Actix HTTP test server"
|
description = "Various helpers for Actix applications to use during testing"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
keywords = ["http", "web", "framework", "async", "futures"]
|
keywords = ["http", "web", "framework", "async", "futures"]
|
||||||
homepage = "https://actix.rs"
|
homepage = "https://actix.rs"
|
|
@ -0,0 +1,15 @@
|
||||||
|
# actix-http-test
|
||||||
|
|
||||||
|
> Various helpers for Actix applications to use during testing.
|
||||||
|
|
||||||
|
[](https://crates.io/crates/actix-http-test)
|
||||||
|
[](https://docs.rs/actix-http-test/2.1.0)
|
||||||
|

|
||||||
|
[](https://deps.rs/crate/actix-http-test/2.1.0)
|
||||||
|
[](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
|
|
@ -1,4 +1,9 @@
|
||||||
//! Various helpers for Actix applications to use during testing.
|
//! 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::sync::mpsc;
|
||||||
use std::{net, thread, time};
|
use std::{net, thread, time};
|
||||||
|
|
|
@ -1,17 +1,25 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2020-xx-xx
|
## Unreleased - 2020-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 2.2.0 - 2020-11-25
|
||||||
### Added
|
### Added
|
||||||
* HttpResponse builders for 1xx status codes. [#1768]
|
* 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]
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
* Started dropping `transfer-encoding: chunked` and `Content-Length` for 1XX and 204 responses. [#1767]
|
* Started dropping `transfer-encoding: chunked` and `Content-Length` for 1XX and 204 responses. [#1767]
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* Upgrade `serde_urlencoded` to `0.7`.
|
* 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
|
[#1767]: https://github.com/actix/actix-web/pull/1767
|
||||||
[#1768]: https://github.com/actix/actix-web/pull/1768
|
[#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
|
## 2.1.0 - 2020-10-30
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-http"
|
name = "actix-http"
|
||||||
version = "2.1.0"
|
version = "2.2.0"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "HTTP primitives for the Actix ecosystem"
|
description = "HTTP primitives for the Actix ecosystem"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
|
@ -3,14 +3,14 @@
|
||||||
> HTTP primitives for the Actix ecosystem.
|
> HTTP primitives for the Actix ecosystem.
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-http)
|
[](https://crates.io/crates/actix-http)
|
||||||
[](https://docs.rs/actix-http/2.1.0)
|
[](https://docs.rs/actix-http/2.2.0)
|
||||||

|

|
||||||
[](https://deps.rs/crate/actix-http/2.1.0)
|
[](https://deps.rs/crate/actix-http/2.2.0)
|
||||||
[](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
[](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
## Documentation & Resources
|
## 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)
|
- [Chat on Gitter](https://gitter.im/actix/actix-web)
|
||||||
- Minimum Supported Rust Version (MSRV): 1.42.0
|
- Minimum Supported Rust Version (MSRV): 1.42.0
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use mime::Mime;
|
use mime::Mime;
|
||||||
|
|
||||||
use crate::header::{qitem, QualityItem};
|
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)
|
/// `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
|
/// 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
|
/// 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
|
/// small set of desired types, as in the case of a request for an
|
||||||
/// in-line image
|
/// in-line image
|
||||||
|
@ -97,14 +99,14 @@ header! {
|
||||||
test_header!(
|
test_header!(
|
||||||
test1,
|
test1,
|
||||||
vec![b"audio/*; q=0.2, audio/basic"],
|
vec![b"audio/*; q=0.2, audio/basic"],
|
||||||
Some(HeaderField(vec![
|
Some(Accept(vec![
|
||||||
QualityItem::new("audio/*".parse().unwrap(), q(200)),
|
QualityItem::new("audio/*".parse().unwrap(), q(200)),
|
||||||
qitem("audio/basic".parse().unwrap()),
|
qitem("audio/basic".parse().unwrap()),
|
||||||
])));
|
])));
|
||||||
test_header!(
|
test_header!(
|
||||||
test2,
|
test2,
|
||||||
vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"],
|
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)),
|
QualityItem::new(mime::TEXT_PLAIN, q(500)),
|
||||||
qitem(mime::TEXT_HTML),
|
qitem(mime::TEXT_HTML),
|
||||||
QualityItem::new(
|
QualityItem::new(
|
||||||
|
@ -138,23 +140,148 @@ header! {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Accept {
|
impl Accept {
|
||||||
/// A constructor to easily create `Accept: */*`.
|
/// Construct `Accept: */*`.
|
||||||
pub fn star() -> Accept {
|
pub fn star() -> Accept {
|
||||||
Accept(vec![qitem(mime::STAR_STAR)])
|
Accept(vec![qitem(mime::STAR_STAR)])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A constructor to easily create `Accept: application/json`.
|
/// Construct `Accept: application/json`.
|
||||||
pub fn json() -> Accept {
|
pub fn json() -> Accept {
|
||||||
Accept(vec![qitem(mime::APPLICATION_JSON)])
|
Accept(vec![qitem(mime::APPLICATION_JSON)])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A constructor to easily create `Accept: text/*`.
|
/// Construct `Accept: text/*`.
|
||||||
pub fn text() -> Accept {
|
pub fn text() -> Accept {
|
||||||
Accept(vec![qitem(mime::TEXT_STAR)])
|
Accept(vec![qitem(mime::TEXT_STAR)])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A constructor to easily create `Accept: image/*`.
|
/// Construct `Accept: image/*`.
|
||||||
pub fn image() -> Accept {
|
pub fn image() -> Accept {
|
||||||
Accept(vec![qitem(mime::IMAGE_STAR)])
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -550,8 +550,7 @@ impl fmt::Display for ContentDisposition {
|
||||||
write!(f, "{}", self.disposition)?;
|
write!(f, "{}", self.disposition)?;
|
||||||
self.parameters
|
self.parameters
|
||||||
.iter()
|
.iter()
|
||||||
.map(|param| write!(f, "; {}", param))
|
.try_for_each(|param| write!(f, "; {}", param))
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -370,9 +370,7 @@ impl fmt::Display for ExtendedValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Percent encode a sequence of bytes with a character set defined in
|
/// Percent encode a sequence of bytes with a character set defined in
|
||||||
/// [https://tools.ietf.org/html/rfc5987#section-3.2][url]
|
/// <https://tools.ietf.org/html/rfc5987#section-3.2>
|
||||||
///
|
|
||||||
/// [url]: https://tools.ietf.org/html/rfc5987#section-3.2
|
|
||||||
pub fn http_percent_encode(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result {
|
pub fn http_percent_encode(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result {
|
||||||
let encoded = percent_encoding::percent_encode(bytes, HTTP_VALUE);
|
let encoded = percent_encoding::percent_encode(bytes, HTTP_VALUE);
|
||||||
fmt::Display::fmt(&encoded, f)
|
fmt::Display::fmt(&encoded, f)
|
||||||
|
|
|
@ -7,9 +7,7 @@ use self::Charset::*;
|
||||||
///
|
///
|
||||||
/// The string representation is normalized to upper case.
|
/// The string representation is normalized to upper case.
|
||||||
///
|
///
|
||||||
/// See [http://www.iana.org/assignments/character-sets/character-sets.xhtml][url].
|
/// See <http://www.iana.org/assignments/character-sets/character-sets.xhtml>.
|
||||||
///
|
|
||||||
/// [url]: http://www.iana.org/assignments/character-sets/character-sets.xhtml
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
pub enum Charset {
|
pub enum Charset {
|
||||||
|
|
|
@ -7,10 +7,12 @@ use crate::header::{HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer};
|
||||||
/// 1. `%x21`, or
|
/// 1. `%x21`, or
|
||||||
/// 2. in the range `%x23` to `%x7E`, or
|
/// 2. in the range `%x23` to `%x7E`, or
|
||||||
/// 3. above `%x80`
|
/// 3. above `%x80`
|
||||||
|
fn entity_validate_char(c: u8) -> bool {
|
||||||
|
c == 0x21 || (0x23..=0x7e).contains(&c) || (c >= 0x80)
|
||||||
|
}
|
||||||
|
|
||||||
fn check_slice_validity(slice: &str) -> bool {
|
fn check_slice_validity(slice: &str) -> bool {
|
||||||
slice
|
slice.bytes().all(entity_validate_char)
|
||||||
.bytes()
|
|
||||||
.all(|c| c == b'\x21' || (c >= b'\x23' && c <= b'\x7e') | (c >= b'\x80'))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An entity tag, defined in [RFC7232](https://tools.ietf.org/html/rfc7232#section-2.3)
|
/// An entity tag, defined in [RFC7232](https://tools.ietf.org/html/rfc7232#section-2.3)
|
||||||
|
|
|
@ -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.
|
/// Represents a quality used in quality values.
|
||||||
///
|
///
|
||||||
/// Can be created with the `q` function.
|
/// Can be created with the [`q`] function.
|
||||||
///
|
///
|
||||||
/// # Implementation notes
|
/// # 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)
|
/// [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.
|
/// 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);
|
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 {
|
impl Default for Quality {
|
||||||
fn default() -> 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> {
|
impl<T: fmt::Display> fmt::Display for QualityItem<T> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
fmt::Display::fmt(&self.item, f)?;
|
fmt::Display::fmt(&self.item, f)?;
|
||||||
|
|
||||||
match self.quality.0 {
|
match self.quality.0 {
|
||||||
1000 => Ok(()),
|
MAX_QUALITY => Ok(()),
|
||||||
0 => f.write_str("; q=0"),
|
0 => f.write_str("; q=0"),
|
||||||
x => write!(f, "; q=0.{}", format!("{:03}", x).trim_end_matches('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> {
|
impl<T: str::FromStr> str::FromStr for QualityItem<T> {
|
||||||
type Err = crate::error::ParseError;
|
type Err = crate::error::ParseError;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<QualityItem<T>, crate::error::ParseError> {
|
fn from_str(qitem_str: &str) -> Result<QualityItem<T>, crate::error::ParseError> {
|
||||||
if !s.is_ascii() {
|
if !qitem_str.is_ascii() {
|
||||||
return Err(crate::error::ParseError::Header);
|
return Err(crate::error::ParseError::Header);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set defaults used if parsing fails.
|
// Set defaults used if parsing fails.
|
||||||
let mut raw_item = s;
|
let mut raw_item = qitem_str;
|
||||||
let mut quality = 1f32;
|
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 {
|
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 {
|
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);
|
return Err(crate::error::ParseError::Header);
|
||||||
}
|
}
|
||||||
|
|
||||||
let start = &parts[0][0..2];
|
let start = &parts[0][0..2];
|
||||||
|
|
||||||
if start == "q=" || start == "Q=" {
|
if start == "q=" || start == "Q=" {
|
||||||
let q_part = &parts[0][2..parts[0].len()];
|
let q_val = &parts[0][2..];
|
||||||
if q_part.len() > 5 {
|
if q_val.len() > 5 {
|
||||||
|
// longer than 5 indicates an over-precise q-factor
|
||||||
return Err(crate::error::ParseError::Header);
|
return Err(crate::error::ParseError::Header);
|
||||||
}
|
}
|
||||||
match q_part.parse::<f32>() {
|
|
||||||
Ok(q_value) => {
|
let q_value = q_val
|
||||||
if 0f32 <= q_value && q_value <= 1f32 {
|
.parse::<f32>()
|
||||||
quality = q_value;
|
.map_err(|_| crate::error::ParseError::Header)?;
|
||||||
raw_item = parts[1];
|
|
||||||
} else {
|
if (0f32..=1f32).contains(&q_value) {
|
||||||
return Err(crate::error::ParseError::Header);
|
quality = q_value;
|
||||||
}
|
raw_item = parts[1];
|
||||||
}
|
} else {
|
||||||
Err(_) => return Err(crate::error::ParseError::Header),
|
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]
|
let item = raw_item
|
||||||
fn from_f32(f: f32) -> Quality {
|
.parse::<T>()
|
||||||
// this function is only used internally. A check that `f` is within range
|
.map_err(|_| crate::error::ParseError::Header)?;
|
||||||
// should be done before calling this method. Just in case, this
|
|
||||||
// debug_assert should catch if we were forgetful
|
// we already checked above that the quality is within range
|
||||||
debug_assert!(
|
Ok(QualityItem::new(item, Quality::from_f32(quality)))
|
||||||
f >= 0f32 && f <= 1f32,
|
}
|
||||||
"q value must be between 0.0 and 1.0"
|
|
||||||
);
|
|
||||||
Quality((f * 1000f32) as u16)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience function to wrap a value in a `QualityItem`
|
/// Convenience function to wrap a value in a `QualityItem`
|
||||||
/// Sets `q` to the default 1.0
|
/// Sets `q` to the default 1.0
|
||||||
pub fn qitem<T>(item: T) -> QualityItem<T> {
|
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.
|
/// Convenience function to create a `Quality` from a float or integer.
|
||||||
///
|
///
|
||||||
/// Implemented for `u16` and `f32`. Panics if value is out of range.
|
/// Implemented for `u16` and `f32`. Panics if value is out of range.
|
||||||
pub fn q<T: IntoQuality>(val: T) -> Quality {
|
pub fn q<T>(val: T) -> Quality
|
||||||
val.into_quality()
|
where
|
||||||
}
|
T: TryInto<Quality>,
|
||||||
|
T::Error: fmt::Debug,
|
||||||
mod internal {
|
{
|
||||||
use super::Quality;
|
// TODO: on next breaking change, handle unwrap differently
|
||||||
|
val.try_into().unwrap()
|
||||||
// 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 {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -270,15 +294,13 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic] // FIXME - 32-bit msvc unwinding broken
|
#[should_panic]
|
||||||
#[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)]
|
|
||||||
fn test_quality_invalid() {
|
fn test_quality_invalid() {
|
||||||
q(-1.0);
|
q(-1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic] // FIXME - 32-bit msvc unwinding broken
|
#[should_panic]
|
||||||
#[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)]
|
|
||||||
fn test_quality_invalid2() {
|
fn test_quality_invalid2() {
|
||||||
q(2.0);
|
q(2.0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,19 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2020-xx-xx
|
## Unreleased - 2020-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 2.0.3 - 2020-11-29
|
||||||
|
### Fixed
|
||||||
|
* Ensure `actix-http` dependency uses same `serde_urlencoded`.
|
||||||
|
|
||||||
|
|
||||||
|
## 2.0.2 - 2020-11-25
|
||||||
### Changed
|
### Changed
|
||||||
* Upgrade `serde_urlencoded` to `0.7`.
|
* Upgrade `serde_urlencoded` to `0.7`. [#1773]
|
||||||
|
|
||||||
|
[#1773]: https://github.com/actix/actix-web/pull/1773
|
||||||
|
|
||||||
|
|
||||||
## 2.0.1 - 2020-10-30
|
## 2.0.1 - 2020-10-30
|
||||||
### Changed
|
### Changed
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "awc"
|
name = "awc"
|
||||||
version = "2.0.1"
|
version = "2.0.3"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Async HTTP and WebSocket client library built on the Actix ecosystem"
|
description = "Async HTTP and WebSocket client library built on the Actix ecosystem"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -39,7 +39,7 @@ compress = ["actix-http/compress"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-codec = "0.3.0"
|
actix-codec = "0.3.0"
|
||||||
actix-service = "1.0.6"
|
actix-service = "1.0.6"
|
||||||
actix-http = "2.0.0"
|
actix-http = "2.2.0"
|
||||||
actix-rt = "1.0.0"
|
actix-rt = "1.0.0"
|
||||||
|
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
|
|
|
@ -3,14 +3,14 @@
|
||||||
> Async HTTP and WebSocket client library.
|
> Async HTTP and WebSocket client library.
|
||||||
|
|
||||||
[](https://crates.io/crates/awc)
|
[](https://crates.io/crates/awc)
|
||||||
[](https://docs.rs/awc/2.0.1)
|
[](https://docs.rs/awc/2.0.3)
|
||||||

|

|
||||||
[](https://deps.rs/crate/awc/2.0.1)
|
[](https://deps.rs/crate/awc/2.0.3)
|
||||||
[](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
[](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
## Documentation & Resources
|
## 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)
|
- [Example Project](https://github.com/actix/examples/tree/HEAD/awc_https)
|
||||||
- [Chat on Gitter](https://gitter.im/actix/actix-web)
|
- [Chat on Gitter](https://gitter.im/actix/actix-web)
|
||||||
- Minimum Supported Rust Version (MSRV): 1.42.0
|
- Minimum Supported Rust Version (MSRV): 1.42.0
|
||||||
|
|
|
@ -70,9 +70,14 @@ impl WebsocketsRequest {
|
||||||
<Uri as TryFrom<U>>::Error: Into<HttpError>,
|
<Uri as TryFrom<U>>::Error: Into<HttpError>,
|
||||||
{
|
{
|
||||||
let mut err = None;
|
let mut err = None;
|
||||||
let mut head = RequestHead::default();
|
|
||||||
head.method = Method::GET;
|
#[allow(clippy::field_reassign_with_default)]
|
||||||
head.version = Version::HTTP_11;
|
let mut head = {
|
||||||
|
let mut head = RequestHead::default();
|
||||||
|
head.method = Method::GET;
|
||||||
|
head.version = Version::HTTP_11;
|
||||||
|
head
|
||||||
|
};
|
||||||
|
|
||||||
match Uri::try_from(uri) {
|
match Uri::try_from(uri) {
|
||||||
Ok(uri) => head.uri = uri,
|
Ok(uri) => head.uri = uri,
|
||||||
|
|
11
codecov.yml
11
codecov.yml
|
@ -1,4 +1,13 @@
|
||||||
ignore: # ignore codecoverage on following paths
|
coverage:
|
||||||
|
status:
|
||||||
|
project:
|
||||||
|
default:
|
||||||
|
threshold: 10% # make CI green
|
||||||
|
patch:
|
||||||
|
default:
|
||||||
|
threshold: 10% # make CI green
|
||||||
|
|
||||||
|
ignore: # ignore code coverage on following paths
|
||||||
- "**/tests"
|
- "**/tests"
|
||||||
- "test-server"
|
- "test-server"
|
||||||
- "**/benches"
|
- "**/benches"
|
||||||
|
|
|
@ -2,29 +2,31 @@ digraph {
|
||||||
subgraph cluster_web {
|
subgraph cluster_web {
|
||||||
label="actix/actix-web"
|
label="actix/actix-web"
|
||||||
"awc"
|
"awc"
|
||||||
"actix-web"
|
"web"
|
||||||
"actix-files"
|
"files"
|
||||||
"actix-http"
|
"http"
|
||||||
"actix-multipart"
|
"multipart"
|
||||||
"actix-web-actors"
|
"web-actors"
|
||||||
"actix-web-codegen"
|
"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" }
|
"web" -> { "codec" "service" "utils" "router" "rt" "server" "testing" "macros" "threadpool" "tls" "codegen" "http" "awc" }
|
||||||
"awc" -> { "actix-codec" "actix-service" "actix-http" "actix-rt" }
|
"awc" -> { "codec" "service" "http" "rt" }
|
||||||
"actix-web-actors" -> { "actix" "actix-web" "actix-http" "actix-codec" }
|
"web-actors" -> { "actix" "web" "http" "codec" }
|
||||||
"actix-multipart" -> { "actix-web" "actix-service" "actix-utils" }
|
"multipart" -> { "web" "service" "utils" }
|
||||||
"actix-http" -> { "actix-service" "actix-codec" "actix-connect" "actix-utils" "actix-rt" "actix-threadpool" }
|
"http" -> { "service" "codec" "connect" "utils" "rt" "threadpool" }
|
||||||
"actix-http" -> { "actix" "actix-tls" }[color=blue] // optional
|
"http" -> { "actix" "tls" }[color=blue] // optional
|
||||||
"actix-files" -> { "actix-web" "actix-http" }
|
"files" -> { "web" }
|
||||||
|
"http-test" -> { "service" "codec" "connect" "utils" "rt" "server" "testing" "awc" }
|
||||||
|
|
||||||
// net
|
// net
|
||||||
|
|
||||||
"actix-utils" -> { "actix-service" "actix-rt" "actix-codec" }
|
"utils" -> { "service" "rt" "codec" }
|
||||||
"actix-tracing" -> { "actix-service" }
|
"tracing" -> { "service" }
|
||||||
"actix-tls" -> { "actix-service" "actix-codec" "actix-utils" }
|
"tls" -> { "service" "codec" "utils" }
|
||||||
"actix-testing" -> { "actix-rt" "actix-macros" "actix-server" "actix-service" }
|
"testing" -> { "rt" "macros" "server" "service" }
|
||||||
"actix-server" -> { "actix-service" "actix-rt" "actix-codec" "actix-utils" }
|
"server" -> { "service" "rt" "codec" "utils" }
|
||||||
"actix-rt" -> { "actix-macros" "actix-threadpool" }
|
"rt" -> { "macros" "threadpool" }
|
||||||
"actix-connect" -> { "actix-service" "actix-codec" "actix-utils" "actix-rt" }
|
"connect" -> { "service" "codec" "utils" "rt" }
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,14 @@ digraph {
|
||||||
"actix-multipart"
|
"actix-multipart"
|
||||||
"actix-web-actors"
|
"actix-web-actors"
|
||||||
"actix-web-codegen"
|
"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" }
|
"awc" -> { "actix-http" }
|
||||||
"actix-web-actors" -> { "actix" "actix-web" "actix-http" }
|
"actix-web-actors" -> { "actix" "actix-web" "actix-http" }
|
||||||
"actix-multipart" -> { "actix-web" }
|
"actix-multipart" -> { "actix-web" }
|
||||||
"actix-http" -> { "actix" }[color=blue] // optional
|
"actix-http" -> { "actix" }[color=blue] // optional
|
||||||
"actix-files" -> { "actix-web" "actix-http" }
|
"actix-files" -> { "actix-web" }
|
||||||
|
"actix-http-test" -> { "awc" }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
//! Error and Result module
|
//! Error and Result module
|
||||||
|
|
||||||
pub use actix_http::error::*;
|
pub use actix_http::error::*;
|
||||||
use derive_more::{Display, From};
|
use derive_more::{Display, From};
|
||||||
use serde_json::error::Error as JsonError;
|
use serde_json::error::Error as JsonError;
|
||||||
|
|
|
@ -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
|
//! ## Example
|
||||||
//!
|
//!
|
||||||
|
@ -102,10 +102,11 @@ pub use crate::app::App;
|
||||||
pub use crate::extract::FromRequest;
|
pub use crate::extract::FromRequest;
|
||||||
pub use crate::request::HttpRequest;
|
pub use crate::request::HttpRequest;
|
||||||
pub use crate::resource::Resource;
|
pub use crate::resource::Resource;
|
||||||
pub use crate::responder::{Either, Responder};
|
pub use crate::responder::Responder;
|
||||||
pub use crate::route::Route;
|
pub use crate::route::Route;
|
||||||
pub use crate::scope::Scope;
|
pub use crate::scope::Scope;
|
||||||
pub use crate::server::HttpServer;
|
pub use crate::server::HttpServer;
|
||||||
|
pub use crate::types::{Either, EitherExtractError};
|
||||||
|
|
||||||
pub mod dev {
|
pub mod dev {
|
||||||
//! The `actix-web` prelude for library developers
|
//! The `actix-web` prelude for library developers
|
||||||
|
|
|
@ -192,10 +192,7 @@ impl AcceptEncoding {
|
||||||
};
|
};
|
||||||
let quality = match parts.len() {
|
let quality = match parts.len() {
|
||||||
1 => encoding.quality(),
|
1 => encoding.quality(),
|
||||||
_ => match f64::from_str(parts[1]) {
|
_ => f64::from_str(parts[1]).unwrap_or(0.0),
|
||||||
Ok(q) => q,
|
|
||||||
Err(_) => 0.0,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
Some(AcceptEncoding { encoding, quality })
|
Some(AcceptEncoding { encoding, quality })
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,6 +105,7 @@ mod tests {
|
||||||
use crate::test::{self, TestRequest};
|
use crate::test::{self, TestRequest};
|
||||||
use crate::HttpResponse;
|
use crate::HttpResponse;
|
||||||
|
|
||||||
|
#[allow(clippy::unnecessary_wraps)]
|
||||||
fn render_500<B>(mut res: ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
|
fn render_500<B>(mut res: ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
|
||||||
res.response_mut()
|
res.response_mut()
|
||||||
.headers_mut()
|
.headers_mut()
|
||||||
|
|
|
@ -154,6 +154,7 @@ mod tests {
|
||||||
use crate::test::{self, TestRequest};
|
use crate::test::{self, TestRequest};
|
||||||
use crate::HttpResponse;
|
use crate::HttpResponse;
|
||||||
|
|
||||||
|
#[allow(clippy::unnecessary_wraps)]
|
||||||
fn render_500<B>(mut res: ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
|
fn render_500<B>(mut res: ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
|
||||||
res.response_mut()
|
res.response_mut()
|
||||||
.headers_mut()
|
.headers_mut()
|
||||||
|
|
|
@ -137,9 +137,9 @@ where
|
||||||
// so the change can not be deduced from the length comparison
|
// so the change can not be deduced from the length comparison
|
||||||
if path != original_path {
|
if path != original_path {
|
||||||
let mut parts = head.uri.clone().into_parts();
|
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))
|
Bytes::from(format!("{}?{}", path, q))
|
||||||
} else {
|
} else {
|
||||||
Bytes::copy_from_slice(path.as_bytes())
|
Bytes::copy_from_slice(path.as_bytes())
|
||||||
|
|
|
@ -675,4 +675,40 @@ mod tests {
|
||||||
let res = call_service(&mut srv, req).await;
|
let res = call_service(&mut srv, req).await;
|
||||||
assert_eq!(res.status(), StatusCode::OK);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
impl<T> Responder for InternalError<T>
|
||||||
where
|
where
|
||||||
T: std::fmt::Debug + std::fmt::Display + 'static,
|
T: std::fmt::Debug + std::fmt::Display + 'static,
|
||||||
|
|
|
@ -86,7 +86,7 @@ impl ResourceMap {
|
||||||
if let Some(plen) = pattern.is_prefix_match(path) {
|
if let Some(plen) = pattern.is_prefix_match(path) {
|
||||||
return rmap.has_resource(&path[plen..]);
|
return rmap.has_resource(&path[plen..]);
|
||||||
}
|
}
|
||||||
} else if pattern.is_match(path) {
|
} else if pattern.is_match(path) || pattern.pattern() == "" && path == "/" {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
44
src/route.rs
44
src/route.rs
|
@ -16,24 +16,24 @@ use crate::responder::Responder;
|
||||||
use crate::service::{ServiceRequest, ServiceResponse};
|
use crate::service::{ServiceRequest, ServiceResponse};
|
||||||
use crate::HttpResponse;
|
use crate::HttpResponse;
|
||||||
|
|
||||||
type BoxedRouteService<Req, Res> = Box<
|
type BoxedRouteService = Box<
|
||||||
dyn Service<
|
dyn Service<
|
||||||
Request = Req,
|
Request = ServiceRequest,
|
||||||
Response = Res,
|
Response = ServiceResponse,
|
||||||
Error = Error,
|
Error = Error,
|
||||||
Future = LocalBoxFuture<'static, Result<Res, Error>>,
|
Future = LocalBoxFuture<'static, Result<ServiceResponse, Error>>,
|
||||||
>,
|
>,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
type BoxedRouteNewService<Req, Res> = Box<
|
type BoxedRouteNewService = Box<
|
||||||
dyn ServiceFactory<
|
dyn ServiceFactory<
|
||||||
Config = (),
|
Config = (),
|
||||||
Request = Req,
|
Request = ServiceRequest,
|
||||||
Response = Res,
|
Response = ServiceResponse,
|
||||||
Error = Error,
|
Error = Error,
|
||||||
InitError = (),
|
InitError = (),
|
||||||
Service = BoxedRouteService<Req, Res>,
|
Service = BoxedRouteService,
|
||||||
Future = LocalBoxFuture<'static, Result<BoxedRouteService<Req, Res>, ()>>,
|
Future = LocalBoxFuture<'static, Result<BoxedRouteService, ()>>,
|
||||||
>,
|
>,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ type BoxedRouteNewService<Req, Res> = Box<
|
||||||
/// Route uses builder-like pattern for configuration.
|
/// Route uses builder-like pattern for configuration.
|
||||||
/// If handler is not explicitly set, default *404 Not Found* handler is used.
|
/// If handler is not explicitly set, default *404 Not Found* handler is used.
|
||||||
pub struct Route {
|
pub struct Route {
|
||||||
service: BoxedRouteNewService<ServiceRequest, ServiceResponse>,
|
service: BoxedRouteNewService,
|
||||||
guards: Rc<Vec<Box<dyn Guard>>>,
|
guards: Rc<Vec<Box<dyn Guard>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,15 +80,8 @@ impl ServiceFactory for Route {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type RouteFuture = LocalBoxFuture<
|
|
||||||
'static,
|
|
||||||
Result<BoxedRouteService<ServiceRequest, ServiceResponse>, ()>,
|
|
||||||
>;
|
|
||||||
|
|
||||||
#[pin_project::pin_project]
|
|
||||||
pub struct CreateRouteService {
|
pub struct CreateRouteService {
|
||||||
#[pin]
|
fut: LocalBoxFuture<'static, Result<BoxedRouteService, ()>>,
|
||||||
fut: RouteFuture,
|
|
||||||
guards: Rc<Vec<Box<dyn Guard>>>,
|
guards: Rc<Vec<Box<dyn Guard>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,9 +89,9 @@ impl Future for CreateRouteService {
|
||||||
type Output = Result<RouteService, ()>;
|
type Output = Result<RouteService, ()>;
|
||||||
|
|
||||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
let this = self.project();
|
let this = self.get_mut();
|
||||||
|
|
||||||
match this.fut.poll(cx)? {
|
match this.fut.as_mut().poll(cx)? {
|
||||||
Poll::Ready(service) => Poll::Ready(Ok(RouteService {
|
Poll::Ready(service) => Poll::Ready(Ok(RouteService {
|
||||||
service,
|
service,
|
||||||
guards: this.guards.clone(),
|
guards: this.guards.clone(),
|
||||||
|
@ -109,7 +102,7 @@ impl Future for CreateRouteService {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RouteService {
|
pub struct RouteService {
|
||||||
service: BoxedRouteService<ServiceRequest, ServiceResponse>,
|
service: BoxedRouteService,
|
||||||
guards: Rc<Vec<Box<dyn Guard>>>,
|
guards: Rc<Vec<Box<dyn Guard>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,7 +128,7 @@ impl Service for RouteService {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
||||||
self.service.call(req).boxed_local()
|
self.service.call(req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,12 +268,12 @@ where
|
||||||
T::Service: 'static,
|
T::Service: 'static,
|
||||||
<T::Service as Service>::Future: 'static,
|
<T::Service as Service>::Future: 'static,
|
||||||
{
|
{
|
||||||
type Config = ();
|
|
||||||
type Request = ServiceRequest;
|
type Request = ServiceRequest;
|
||||||
type Response = ServiceResponse;
|
type Response = ServiceResponse;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
type Config = ();
|
||||||
|
type Service = BoxedRouteService;
|
||||||
type InitError = ();
|
type InitError = ();
|
||||||
type Service = BoxedRouteService<ServiceRequest, Self::Response>;
|
|
||||||
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
||||||
|
|
||||||
fn new_service(&self, _: ()) -> Self::Future {
|
fn new_service(&self, _: ()) -> Self::Future {
|
||||||
|
@ -288,8 +281,7 @@ where
|
||||||
.new_service(())
|
.new_service(())
|
||||||
.map(|result| match result {
|
.map(|result| match result {
|
||||||
Ok(service) => {
|
Ok(service) => {
|
||||||
let service: BoxedRouteService<_, _> =
|
let service = Box::new(RouteServiceWrapper { service }) as _;
|
||||||
Box::new(RouteServiceWrapper { service });
|
|
||||||
Ok(service)
|
Ok(service)
|
||||||
}
|
}
|
||||||
Err(_) => Err(()),
|
Err(_) => Err(()),
|
||||||
|
|
|
@ -269,8 +269,9 @@ where
|
||||||
{
|
{
|
||||||
let body = read_body(res).await;
|
let body = read_body(res).await;
|
||||||
|
|
||||||
serde_json::from_slice(&body)
|
serde_json::from_slice(&body).unwrap_or_else(|e| {
|
||||||
.unwrap_or_else(|_| panic!("read_response_json failed during deserialization"))
|
panic!("read_response_json failed during deserialization: {}", e)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn load_stream<S>(mut stream: S) -> Result<Bytes, Error>
|
pub async fn load_stream<S>(mut stream: S) -> Result<Bytes, Error>
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
//! Helper types
|
//! Helper types
|
||||||
|
|
||||||
|
mod either;
|
||||||
pub(crate) mod form;
|
pub(crate) mod form;
|
||||||
pub(crate) mod json;
|
pub(crate) mod json;
|
||||||
mod path;
|
mod path;
|
||||||
|
@ -7,6 +8,7 @@ pub(crate) mod payload;
|
||||||
mod query;
|
mod query;
|
||||||
pub(crate) mod readlines;
|
pub(crate) mod readlines;
|
||||||
|
|
||||||
|
pub use self::either::{Either, EitherExtractError};
|
||||||
pub use self::form::{Form, FormConfig};
|
pub use self::form::{Form, FormConfig};
|
||||||
pub use self::json::{Json, JsonConfig};
|
pub use self::json::{Json, JsonConfig};
|
||||||
pub use self::path::{Path, PathConfig};
|
pub use self::path::{Path, PathConfig};
|
||||||
|
|
|
@ -241,9 +241,10 @@ pub struct PayloadConfig {
|
||||||
impl PayloadConfig {
|
impl PayloadConfig {
|
||||||
/// Create `PayloadConfig` instance and set max size of payload.
|
/// Create `PayloadConfig` instance and set max size of payload.
|
||||||
pub fn new(limit: usize) -> Self {
|
pub fn new(limit: usize) -> Self {
|
||||||
let mut cfg = Self::default();
|
Self {
|
||||||
cfg.limit = limit;
|
limit,
|
||||||
cfg
|
..Default::default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change max size of payload. By default max size is 256Kb
|
/// Change max size of payload. By default max size is 256Kb
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
# Actix http test server [](https://travis-ci.org/actix/actix-web) [](https://codecov.io/gh/actix/actix-web) [](https://crates.io/crates/actix-http-test) [](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
|
|
Loading…
Reference in New Issue