1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-08 11:56:32 +02:00

Compare commits

...

21 Commits

Author SHA1 Message Date
fe6ad816cc update dotgraphs 2020-11-25 00:54:00 +00:00
e72b787ba7 prepare actix-web and actix-http-test releases 2020-11-25 00:53:48 +00:00
efc317d3b0 prepare actix-http and awc releases 2020-11-25 00:07:56 +00:00
31057becca prepare actix-files release 0.4.1 2020-11-24 20:33:23 +00:00
f1a9b45437 improve docs for Files::new 2020-11-24 20:23:09 +00:00
5af46775b8 refactor quality and use TryFrom instead of custom trait (#1797) 2020-11-24 11:37:05 +00:00
70f4747a23 add method for getting accept type preference (#1793) 2020-11-24 10:08:57 +00:00
2f11ef089b fix rustdoc uploads 2020-11-24 00:29:13 +00:00
4100c50c70 add either extractor (#1788) 2020-11-20 18:02:41 +00:00
a929209967 actix-files intra-doc migration (#1785) 2020-11-10 23:54:38 +00:00
49e945c88f switching to nightly for intra-doc links (#1783) 2020-11-09 14:01:36 +00:00
9b42333fac Fix typo in Query extractor docs (#1777) 2020-11-06 13:34:42 +00:00
e5b86d189c Fix typo in request_data.rs (#1774) 2020-11-05 17:46:17 +00:00
4bfd5c2781 Upgrade serde_urlencoded to 0.7 (#1773) 2020-11-06 01:36:15 +09:00
9b6a089b36 fix awc doc example (#1772)
* fix awc readme example

Co-authored-by: Rob Ede <robjtede@icloud.com>
2020-11-05 06:20:01 +08:00
ceac97bb8d Update config.yml 2020-11-04 15:08:12 +00:00
61b65aa64a add common 1xx http response builders (#1768)
Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-11-02 18:23:18 +09:00
5468c3c410 Drop content length headers from 101 responses (#1767)
Co-authored-by: Sebastian Mayr <smayr@atlassian.com>
2020-11-02 17:44:14 +09:00
b6385c2b4e Remove CoC on actix-http as duplicated 2020-10-31 12:12:19 +09:00
5135c1e3a0 Update CoC contact information 2020-10-31 12:06:51 +09:00
22b451cf2d fix deps.rs badge 2020-10-31 02:39:54 +00:00
38 changed files with 789 additions and 392 deletions

View File

@ -1,8 +1,15 @@
blank_issues_enabled: true blank_issues_enabled: true
contact_links: 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 url: https://gitter.im/actix/actix-web
about: Please ask and answer questions about the actix-web here. about: Actix Web Q&A
- name: Gitter channel (actix) - name: Gitter chat (actix)
url: https://gitter.im/actix/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 - name: Install Rust
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
toolchain: stable-x86_64-unknown-linux-gnu toolchain: nightly-x86_64-unknown-linux-gnu
profile: minimal profile: minimal
override: true override: true
@ -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

View File

@ -3,6 +3,17 @@
## Unreleased - 2020-xx-xx ## Unreleased - 2020-xx-xx
## 3.3.0 - 2020-11-25
### Added
* Add `Either<A, B>` extractor helper. [#1788]
### Changed
* 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
### Added ### Added
* Implement `exclude_regex` for Logger middleware. [#1723] * Implement `exclude_regex` for Logger middleware. [#1723]

View File

@ -34,10 +34,13 @@ This Code of Conduct applies both within project spaces and in public spaces whe
## Enforcement ## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at fafhrd91@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at robjtede@icloud.com ([@robjtede]) or huyuumi@neet.club ([@JohnTitor]). The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
[@robjtede]: https://github.com/robjtede
[@JohnTitor]: https://github.com/JohnTitor
## Attribution ## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-web" name = "actix-web"
version = "3.2.0" version = "3.3.0"
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"
@ -102,7 +102,7 @@ pin-project = "1.0.0"
regex = "1.4" regex = "1.4"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
serde_urlencoded = "0.6.1" serde_urlencoded = "0.7"
time = { version = "0.2.7", default-features = false, features = ["std"] } time = { version = "0.2.7", default-features = false, features = ["std"] }
url = "2.1" url = "2.1"
open-ssl = { package = "openssl", version = "0.10", optional = true } open-ssl = { package = "openssl", version = "0.10", optional = true }

View File

@ -6,10 +6,10 @@
<p> <p>
[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) [![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.0)](https://docs.rs/actix-web/3.3.0)
[![Version](https://img.shields.io/badge/rustc-1.42+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html) [![Version](https://img.shields.io/badge/rustc-1.42+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html)
![License](https://img.shields.io/crates/l/actix-web.svg) ![License](https://img.shields.io/crates/l/actix-web.svg)
[![Dependency Status](https://deps.rs/crate/actix-web/3.2.0/status.svg)](https://deps.rs/crate/actix-web/2.2.0) [![Dependency Status](https://deps.rs/crate/actix-web/3.3.0/status.svg)](https://deps.rs/crate/actix-web/3.3.0)
<br /> <br />
[![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web)
[![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/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 ## 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]

View File

@ -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"

View File

@ -2,12 +2,12 @@
> Static file serving for Actix Web > Static file serving for Actix Web
[![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/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)](https://docs.rs/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) [![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) ![License](https://img.shields.io/crates/l/actix-files.svg)
<br /> <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) [![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) [![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 { 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,

View File

@ -193,7 +193,7 @@ impl NamedFile {
/// image, and video content types, and `attachment` otherwise, and /// image, and video content types, and `attachment` otherwise, and
/// the filename is taken from the path provided in the `open` method /// the filename is taken from the path provided in the `open` method
/// after converting it to UTF-8 using. /// 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] #[inline]
pub fn set_content_disposition(mut self, cd: header::ContentDisposition) -> Self { pub fn set_content_disposition(mut self, cd: header::ContentDisposition) -> Self {
self.content_disposition = cd; self.content_disposition = cd;

View File

@ -3,6 +3,25 @@
## Unreleased - 2020-xx-xx ## 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]
### 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 ## 2.1.0 - 2020-10-30
### Added ### Added
* Added more flexible `on_connect_ext` methods for on-connect handling. [#1754] * Added more flexible `on_connect_ext` methods for on-connect handling. [#1754]

View File

@ -1,46 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at fafhrd91@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

View File

@ -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"
@ -78,7 +78,7 @@ serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
sha-1 = "0.9" sha-1 = "0.9"
slab = "0.4" slab = "0.4"
serde_urlencoded = "0.6.1" serde_urlencoded = "0.7"
time = { version = "0.2.7", default-features = false, features = ["std"] } time = { version = "0.2.7", default-features = false, features = ["std"] }
# compression # compression

View File

@ -3,14 +3,14 @@
> HTTP primitives for the Actix ecosystem. > HTTP primitives for the Actix ecosystem.
[![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) [![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) ![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) [![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 ## 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

View File

@ -64,14 +64,17 @@ pub(crate) trait MessageType: Sized {
// Content length // Content length
if let Some(status) = self.status() { if let Some(status) = self.status() {
match status { match status {
StatusCode::NO_CONTENT StatusCode::CONTINUE
| StatusCode::CONTINUE | StatusCode::SWITCHING_PROTOCOLS
| StatusCode::PROCESSING => length = BodySize::None, | StatusCode::PROCESSING
StatusCode::SWITCHING_PROTOCOLS => { | StatusCode::NO_CONTENT => {
// skip content-length and transfer-encoding headers
// See https://tools.ietf.org/html/rfc7230#section-3.3.1
// and https://tools.ietf.org/html/rfc7230#section-3.3.2
skip_len = true; skip_len = true;
length = BodySize::Stream; length = BodySize::None
} }
_ => (), _ => {}
} }
} }
match length { match length {
@ -676,4 +679,28 @@ mod tests {
assert!(data.contains("authorization: another authorization\r\n")); assert!(data.contains("authorization: another authorization\r\n"));
assert!(data.contains("date: date\r\n")); assert!(data.contains("date: date\r\n"));
} }
#[test]
fn test_no_content_length() {
let mut bytes = BytesMut::with_capacity(2048);
let mut res: Response<()> =
Response::new(StatusCode::SWITCHING_PROTOCOLS).into_body::<()>();
res.headers_mut()
.insert(DATE, HeaderValue::from_static(&""));
res.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::from_static(&"0"));
let _ = res.encode_headers(
&mut bytes,
Version::HTTP_11,
BodySize::Stream,
ConnectionType::Upgrade,
&ServiceConfig::default(),
);
let data =
String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
assert!(!data.contains("content-length: 0\r\n"));
assert!(!data.contains("transfer-encoding: chunked\r\n"));
}
} }

View File

@ -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));
}
} }

View File

@ -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)

View File

@ -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 {

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. /// 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);
} }

View File

@ -1,10 +1,12 @@
//! Basic http responses //! Status code based HTTP response builders.
#![allow(non_upper_case_globals)] #![allow(non_upper_case_globals)]
use http::StatusCode; use http::StatusCode;
use crate::response::{Response, ResponseBuilder}; use crate::response::{Response, ResponseBuilder};
macro_rules! STATIC_RESP { macro_rules! static_resp {
($name:ident, $status:expr) => { ($name:ident, $status:expr) => {
#[allow(non_snake_case, missing_docs)] #[allow(non_snake_case, missing_docs)]
pub fn $name() -> ResponseBuilder { pub fn $name() -> ResponseBuilder {
@ -14,63 +16,67 @@ macro_rules! STATIC_RESP {
} }
impl Response { impl Response {
STATIC_RESP!(Ok, StatusCode::OK); static_resp!(Continue, StatusCode::CONTINUE);
STATIC_RESP!(Created, StatusCode::CREATED); static_resp!(SwitchingProtocols, StatusCode::SWITCHING_PROTOCOLS);
STATIC_RESP!(Accepted, StatusCode::ACCEPTED); static_resp!(Processing, StatusCode::PROCESSING);
STATIC_RESP!(
static_resp!(Ok, StatusCode::OK);
static_resp!(Created, StatusCode::CREATED);
static_resp!(Accepted, StatusCode::ACCEPTED);
static_resp!(
NonAuthoritativeInformation, NonAuthoritativeInformation,
StatusCode::NON_AUTHORITATIVE_INFORMATION StatusCode::NON_AUTHORITATIVE_INFORMATION
); );
STATIC_RESP!(NoContent, StatusCode::NO_CONTENT); static_resp!(NoContent, StatusCode::NO_CONTENT);
STATIC_RESP!(ResetContent, StatusCode::RESET_CONTENT); static_resp!(ResetContent, StatusCode::RESET_CONTENT);
STATIC_RESP!(PartialContent, StatusCode::PARTIAL_CONTENT); static_resp!(PartialContent, StatusCode::PARTIAL_CONTENT);
STATIC_RESP!(MultiStatus, StatusCode::MULTI_STATUS); static_resp!(MultiStatus, StatusCode::MULTI_STATUS);
STATIC_RESP!(AlreadyReported, StatusCode::ALREADY_REPORTED); static_resp!(AlreadyReported, StatusCode::ALREADY_REPORTED);
STATIC_RESP!(MultipleChoices, StatusCode::MULTIPLE_CHOICES); static_resp!(MultipleChoices, StatusCode::MULTIPLE_CHOICES);
STATIC_RESP!(MovedPermanently, StatusCode::MOVED_PERMANENTLY); static_resp!(MovedPermanently, StatusCode::MOVED_PERMANENTLY);
STATIC_RESP!(Found, StatusCode::FOUND); static_resp!(Found, StatusCode::FOUND);
STATIC_RESP!(SeeOther, StatusCode::SEE_OTHER); static_resp!(SeeOther, StatusCode::SEE_OTHER);
STATIC_RESP!(NotModified, StatusCode::NOT_MODIFIED); static_resp!(NotModified, StatusCode::NOT_MODIFIED);
STATIC_RESP!(UseProxy, StatusCode::USE_PROXY); static_resp!(UseProxy, StatusCode::USE_PROXY);
STATIC_RESP!(TemporaryRedirect, StatusCode::TEMPORARY_REDIRECT); static_resp!(TemporaryRedirect, StatusCode::TEMPORARY_REDIRECT);
STATIC_RESP!(PermanentRedirect, StatusCode::PERMANENT_REDIRECT); static_resp!(PermanentRedirect, StatusCode::PERMANENT_REDIRECT);
STATIC_RESP!(BadRequest, StatusCode::BAD_REQUEST); static_resp!(BadRequest, StatusCode::BAD_REQUEST);
STATIC_RESP!(NotFound, StatusCode::NOT_FOUND); static_resp!(NotFound, StatusCode::NOT_FOUND);
STATIC_RESP!(Unauthorized, StatusCode::UNAUTHORIZED); static_resp!(Unauthorized, StatusCode::UNAUTHORIZED);
STATIC_RESP!(PaymentRequired, StatusCode::PAYMENT_REQUIRED); static_resp!(PaymentRequired, StatusCode::PAYMENT_REQUIRED);
STATIC_RESP!(Forbidden, StatusCode::FORBIDDEN); static_resp!(Forbidden, StatusCode::FORBIDDEN);
STATIC_RESP!(MethodNotAllowed, StatusCode::METHOD_NOT_ALLOWED); static_resp!(MethodNotAllowed, StatusCode::METHOD_NOT_ALLOWED);
STATIC_RESP!(NotAcceptable, StatusCode::NOT_ACCEPTABLE); static_resp!(NotAcceptable, StatusCode::NOT_ACCEPTABLE);
STATIC_RESP!( static_resp!(
ProxyAuthenticationRequired, ProxyAuthenticationRequired,
StatusCode::PROXY_AUTHENTICATION_REQUIRED StatusCode::PROXY_AUTHENTICATION_REQUIRED
); );
STATIC_RESP!(RequestTimeout, StatusCode::REQUEST_TIMEOUT); static_resp!(RequestTimeout, StatusCode::REQUEST_TIMEOUT);
STATIC_RESP!(Conflict, StatusCode::CONFLICT); static_resp!(Conflict, StatusCode::CONFLICT);
STATIC_RESP!(Gone, StatusCode::GONE); static_resp!(Gone, StatusCode::GONE);
STATIC_RESP!(LengthRequired, StatusCode::LENGTH_REQUIRED); static_resp!(LengthRequired, StatusCode::LENGTH_REQUIRED);
STATIC_RESP!(PreconditionFailed, StatusCode::PRECONDITION_FAILED); static_resp!(PreconditionFailed, StatusCode::PRECONDITION_FAILED);
STATIC_RESP!(PreconditionRequired, StatusCode::PRECONDITION_REQUIRED); static_resp!(PreconditionRequired, StatusCode::PRECONDITION_REQUIRED);
STATIC_RESP!(PayloadTooLarge, StatusCode::PAYLOAD_TOO_LARGE); static_resp!(PayloadTooLarge, StatusCode::PAYLOAD_TOO_LARGE);
STATIC_RESP!(UriTooLong, StatusCode::URI_TOO_LONG); static_resp!(UriTooLong, StatusCode::URI_TOO_LONG);
STATIC_RESP!(UnsupportedMediaType, StatusCode::UNSUPPORTED_MEDIA_TYPE); static_resp!(UnsupportedMediaType, StatusCode::UNSUPPORTED_MEDIA_TYPE);
STATIC_RESP!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE); static_resp!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE);
STATIC_RESP!(ExpectationFailed, StatusCode::EXPECTATION_FAILED); static_resp!(ExpectationFailed, StatusCode::EXPECTATION_FAILED);
STATIC_RESP!(UnprocessableEntity, StatusCode::UNPROCESSABLE_ENTITY); static_resp!(UnprocessableEntity, StatusCode::UNPROCESSABLE_ENTITY);
STATIC_RESP!(TooManyRequests, StatusCode::TOO_MANY_REQUESTS); static_resp!(TooManyRequests, StatusCode::TOO_MANY_REQUESTS);
STATIC_RESP!(InternalServerError, StatusCode::INTERNAL_SERVER_ERROR); static_resp!(InternalServerError, StatusCode::INTERNAL_SERVER_ERROR);
STATIC_RESP!(NotImplemented, StatusCode::NOT_IMPLEMENTED); static_resp!(NotImplemented, StatusCode::NOT_IMPLEMENTED);
STATIC_RESP!(BadGateway, StatusCode::BAD_GATEWAY); static_resp!(BadGateway, StatusCode::BAD_GATEWAY);
STATIC_RESP!(ServiceUnavailable, StatusCode::SERVICE_UNAVAILABLE); static_resp!(ServiceUnavailable, StatusCode::SERVICE_UNAVAILABLE);
STATIC_RESP!(GatewayTimeout, StatusCode::GATEWAY_TIMEOUT); static_resp!(GatewayTimeout, StatusCode::GATEWAY_TIMEOUT);
STATIC_RESP!(VersionNotSupported, StatusCode::HTTP_VERSION_NOT_SUPPORTED); static_resp!(VersionNotSupported, StatusCode::HTTP_VERSION_NOT_SUPPORTED);
STATIC_RESP!(VariantAlsoNegotiates, StatusCode::VARIANT_ALSO_NEGOTIATES); static_resp!(VariantAlsoNegotiates, StatusCode::VARIANT_ALSO_NEGOTIATES);
STATIC_RESP!(InsufficientStorage, StatusCode::INSUFFICIENT_STORAGE); static_resp!(InsufficientStorage, StatusCode::INSUFFICIENT_STORAGE);
STATIC_RESP!(LoopDetected, StatusCode::LOOP_DETECTED); static_resp!(LoopDetected, StatusCode::LOOP_DETECTED);
} }
#[cfg(test)] #[cfg(test)]

View File

@ -164,7 +164,6 @@ pub fn handshake_with_protocols(
let mut response = HttpResponse::build(StatusCode::SWITCHING_PROTOCOLS) let mut response = HttpResponse::build(StatusCode::SWITCHING_PROTOCOLS)
.upgrade("websocket") .upgrade("websocket")
.header(header::TRANSFER_ENCODING, "chunked")
.header(header::SEC_WEBSOCKET_ACCEPT, key.as_str()) .header(header::SEC_WEBSOCKET_ACCEPT, key.as_str())
.take(); .take();
@ -664,10 +663,10 @@ mod tests {
) )
.to_http_request(); .to_http_request();
assert_eq!( let resp = handshake(&req).unwrap().finish();
StatusCode::SWITCHING_PROTOCOLS, assert_eq!(StatusCode::SWITCHING_PROTOCOLS, resp.status());
handshake(&req).unwrap().finish().status() assert_eq!(None, resp.headers().get(&header::CONTENT_LENGTH));
); assert_eq!(None, resp.headers().get(&header::TRANSFER_ENCODING));
let req = TestRequest::default() let req = TestRequest::default()
.header( .header(

View File

@ -3,6 +3,13 @@
## Unreleased - 2020-xx-xx ## Unreleased - 2020-xx-xx
## 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 ## 2.0.1 - 2020-10-30
### Changed ### Changed
* Upgrade `base64` to `0.13`. [#1744] * Upgrade `base64` to `0.13`. [#1744]

View File

@ -1,6 +1,6 @@
[package] [package]
name = "awc" name = "awc"
version = "2.0.1" version = "2.0.2"
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"
@ -53,7 +53,7 @@ percent-encoding = "2.1"
rand = "0.7" rand = "0.7"
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
serde_urlencoded = "0.6.1" serde_urlencoded = "0.7"
open-ssl = { version = "0.10", package = "openssl", optional = true } open-ssl = { version = "0.10", package = "openssl", optional = true }
rust-tls = { version = "0.18.0", package = "rustls", optional = true, features = ["dangerous_configuration"] } 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. > Async HTTP and WebSocket client library.
[![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) [![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.2)](https://docs.rs/awc/2.0.2)
![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/awc) ![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.2/status.svg)](https://deps.rs/crate/awc/2.0.2)
[![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) [![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 ## 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
## Example ## Example
```rust ```rust
use actix_rt::System; use actix_rt::System;
use awc::Client; use awc::Client;
use futures::future::{Future, lazy};
fn main() { fn main() {
System::new("test").block_on(lazy(|| { System::new("test").block_on(async {
let mut client = Client::default(); let client = Client::default();
client.get("http://www.rust-lang.org") // <- Create request builder let res = client
.header("User-Agent", "Actix-web") .get("http://www.rust-lang.org") // <- Create request builder
.send() // <- Send http request .header("User-Agent", "Actix-web")
.and_then(|response| { // <- server http response .send() // <- Send http request
println!("Response: {:?}", response); .await;
Ok(())
}) println!("Response: {:?}", res); // <- server http response
})); });
} }
``` ```

View File

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

View File

@ -8,6 +8,7 @@ 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" }
@ -16,4 +17,5 @@ digraph {
"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" }
"actix-http-test" -> { "awc" }
} }

View File

@ -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;

View File

@ -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

View File

@ -50,7 +50,7 @@ use crate::{dev::Payload, FromRequest, HttpRequest};
pub struct ReqData<T: Clone + 'static>(T); pub struct ReqData<T: Clone + 'static>(T);
impl<T: Clone + 'static> ReqData<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 { pub fn into_inner(self) -> T {
self.0 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> impl<T> Responder for InternalError<T>
where where
T: std::fmt::Debug + std::fmt::Display + 'static, T: std::fmt::Debug + std::fmt::Display + 'static,

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 //! 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};

View File

@ -39,7 +39,7 @@ use crate::request::HttpRequest;
/// } /// }
/// ///
/// // Use `Query` extractor for query information (and destructure it within the signature). /// // 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"`. /// // 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 { /// async fn index(web::Query(info): web::Query<AuthRequest>) -> String {
/// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type) /// 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. /// // 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"` /// // The correct request for this handler would be `/index.html?id=64&response_type=Code"`
/// async fn index(info: web::Query<AuthRequest>) -> String { /// async fn index(info: web::Query<AuthRequest>) -> String {
/// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type) /// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type)

View File

@ -2,14 +2,20 @@
## Unreleased - 2020-xx-xx ## 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 [#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
@ -19,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

View File

@ -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"
@ -47,7 +47,7 @@ socket2 = "0.3"
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
slab = "0.4" slab = "0.4"
serde_urlencoded = "0.6.1" serde_urlencoded = "0.7"
time = { version = "0.2.7", default-features = false, features = ["std"] } time = { version = "0.2.7", default-features = false, features = ["std"] }
open-ssl = { version = "0.10", package = "openssl", optional = true } open-ssl = { version = "0.10", package = "openssl", optional = true }

View File

@ -1,9 +1,15 @@
# 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) # actix-http-test
## Documentation & community resources > Various helpers for Actix applications to use during testing.
* [User Guide](https://actix.rs/docs/) [![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test)
* [API Documentation](https://docs.rs/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)
* [Chat on gitter](https://gitter.im/actix/actix) ![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-http-test)
* Cargo package: [actix-http-test](https://crates.io/crates/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)
* Minimum supported Rust version: 1.40 or later [![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. //! 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};