mirror of
https://github.com/actix/actix-extras.git
synced 2024-11-28 01:32:57 +01:00
Merge branch 'master' into master
This commit is contained in:
commit
b931dda1fe
43
.travis.yml
43
.travis.yml
@ -1,14 +1,27 @@
|
|||||||
language: rust
|
language: rust
|
||||||
|
sudo: false
|
||||||
rust:
|
|
||||||
- 1.20.0
|
|
||||||
- stable
|
|
||||||
- beta
|
|
||||||
- nightly-2018-01-03
|
|
||||||
|
|
||||||
sudo: required
|
|
||||||
dist: trusty
|
dist: trusty
|
||||||
|
|
||||||
|
cache:
|
||||||
|
cargo: true
|
||||||
|
apt: true
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- rust: 1.20.0
|
||||||
|
- rust: stable
|
||||||
|
- rust: beta
|
||||||
|
- rust: nightly
|
||||||
|
allow_failures:
|
||||||
|
- rust: nightly
|
||||||
|
- rust: beta
|
||||||
|
|
||||||
|
#rust:
|
||||||
|
# - 1.20.0
|
||||||
|
# - stable
|
||||||
|
# - beta
|
||||||
|
# - nightly-2018-01-03
|
||||||
|
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
- RUSTFLAGS="-C link-dead-code"
|
- RUSTFLAGS="-C link-dead-code"
|
||||||
@ -29,22 +42,22 @@ before_script:
|
|||||||
|
|
||||||
script:
|
script:
|
||||||
- |
|
- |
|
||||||
if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then
|
if [[ "$TRAVIS_RUST_VERSION" == "1.20.0" ]]; then
|
||||||
|
cargo clean
|
||||||
USE_SKEPTIC=1 cargo test --features=alpn
|
USE_SKEPTIC=1 cargo test --features=alpn
|
||||||
else
|
else
|
||||||
cargo test --features=alpn
|
cargo clean
|
||||||
|
cargo test
|
||||||
|
# --features=alpn
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- |
|
- |
|
||||||
if [[ "$TRAVIS_RUST_VERSION" == "1.20.0" ]]; then
|
if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then
|
||||||
cd examples/basics && cargo check && cd ../..
|
cd examples/basics && cargo check && cd ../..
|
||||||
cd examples/hello-world && cargo check && cd ../..
|
cd examples/hello-world && cargo check && cd ../..
|
||||||
cd examples/multipart && cargo check && cd ../..
|
cd examples/multipart && cargo check && cd ../..
|
||||||
cd examples/json && cargo check && cd ../..
|
cd examples/json && cargo check && cd ../..
|
||||||
cd examples/template_tera && cargo check && cd ../..
|
cd examples/template_tera && cargo check && cd ../..
|
||||||
fi
|
|
||||||
- |
|
|
||||||
if [[ "$TRAVIS_RUST_VERSION" == "beta" ]]; then
|
|
||||||
cd examples/diesel && cargo check && cd ../..
|
cd examples/diesel && cargo check && cd ../..
|
||||||
cd examples/tls && cargo check && cd ../..
|
cd examples/tls && cargo check && cd ../..
|
||||||
cd examples/websocket-chat && cargo check && cd ../..
|
cd examples/websocket-chat && cargo check && cd ../..
|
||||||
@ -58,7 +71,7 @@ script:
|
|||||||
# Upload docs
|
# Upload docs
|
||||||
after_success:
|
after_success:
|
||||||
- |
|
- |
|
||||||
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "nightly-2018-01-03" ]]; then
|
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "1.20.0" ]]; then
|
||||||
cargo doc --features alpn --no-deps &&
|
cargo doc --features alpn --no-deps &&
|
||||||
echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html &&
|
echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html &&
|
||||||
cargo install mdbook &&
|
cargo install mdbook &&
|
||||||
|
41
CHANGES.md
41
CHANGES.md
@ -1,7 +1,46 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
|
## 0.3.4 (2018-..-..)
|
||||||
|
|
||||||
## 0.3.0 (2017-xx-xx)
|
* Fix request json loader
|
||||||
|
|
||||||
|
* Added HttpRequest::mime_type() method
|
||||||
|
|
||||||
|
|
||||||
|
## 0.3.3 (2018-01-25)
|
||||||
|
|
||||||
|
* Stop processing any events after context stop
|
||||||
|
|
||||||
|
* Re-enable write back-pressure for h1 connections
|
||||||
|
|
||||||
|
* Refactor HttpServer::start_ssl() method
|
||||||
|
|
||||||
|
* Upgrade openssl to 0.10
|
||||||
|
|
||||||
|
|
||||||
|
## 0.3.2 (2018-01-21)
|
||||||
|
|
||||||
|
* Fix HEAD requests handling
|
||||||
|
|
||||||
|
* Log request processing errors
|
||||||
|
|
||||||
|
* Always enable content encoding if encoding explicitly selected
|
||||||
|
|
||||||
|
* Allow multiple Applications on a single server with different state #49
|
||||||
|
|
||||||
|
* CORS middleware: allowed_headers is defaulting to None #50
|
||||||
|
|
||||||
|
|
||||||
|
## 0.3.1 (2018-01-13)
|
||||||
|
|
||||||
|
* Fix directory entry path #47
|
||||||
|
|
||||||
|
* Do not enable chunked encoding for HTTP/1.0
|
||||||
|
|
||||||
|
* Allow explicitly disable chunked encoding
|
||||||
|
|
||||||
|
|
||||||
|
## 0.3.0 (2018-01-12)
|
||||||
|
|
||||||
* HTTP/2 Support
|
* HTTP/2 Support
|
||||||
|
|
||||||
|
46
CODE_OF_CONDUCT.md
Normal file
46
CODE_OF_CONDUCT.md
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# 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/
|
38
Cargo.toml
38
Cargo.toml
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-web"
|
name = "actix-web"
|
||||||
version = "0.3.0"
|
version = "0.3.3"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Actix web framework"
|
description = "Actix web framework"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@ -11,7 +11,8 @@ documentation = "https://docs.rs/actix-web/"
|
|||||||
categories = ["network-programming", "asynchronous",
|
categories = ["network-programming", "asynchronous",
|
||||||
"web-programming::http-server", "web-programming::websocket"]
|
"web-programming::http-server", "web-programming::websocket"]
|
||||||
license = "MIT/Apache-2.0"
|
license = "MIT/Apache-2.0"
|
||||||
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
|
exclude = [".gitignore", ".travis.yml", ".cargo/config",
|
||||||
|
"appveyor.yml", "/examples/**"]
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
[badges]
|
[badges]
|
||||||
@ -33,31 +34,29 @@ tls = ["native-tls", "tokio-tls"]
|
|||||||
alpn = ["openssl", "openssl/v102", "openssl/v110", "tokio-openssl"]
|
alpn = ["openssl", "openssl/v102", "openssl/v110", "tokio-openssl"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
log = "0.3"
|
log = "0.4"
|
||||||
failure = "0.1"
|
failure = "0.1"
|
||||||
failure_derive = "0.1"
|
failure_derive = "0.1"
|
||||||
time = "0.1"
|
h2 = "0.1"
|
||||||
http = "^0.1.2"
|
http = "^0.1.2"
|
||||||
httparse = "0.1"
|
httparse = "1.2"
|
||||||
http-range = "0.1"
|
http-range = "0.1"
|
||||||
|
time = "0.1"
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
mime_guess = "1.8"
|
mime_guess = "1.8"
|
||||||
regex = "0.2"
|
regex = "0.2"
|
||||||
sha1 = "0.2"
|
sha1 = "0.4"
|
||||||
url = "1.5"
|
url = "1.6"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
flate2 = "0.2"
|
|
||||||
brotli2 = "^0.3.2"
|
brotli2 = "^0.3.2"
|
||||||
percent-encoding = "1.0"
|
percent-encoding = "1.0"
|
||||||
smallvec = "0.6"
|
smallvec = "0.6"
|
||||||
bitflags = "1.0"
|
bitflags = "1.0"
|
||||||
num_cpus = "1.0"
|
num_cpus = "1.0"
|
||||||
|
flate2 = "1.0"
|
||||||
# temp solution
|
cookie = { version="0.10", features=["percent-encode", "secure"] }
|
||||||
# cookie = { version="0.10", features=["percent-encode", "secure"] }
|
|
||||||
cookie = { git="https://github.com/alexcrichton/cookie-rs.git", features=["percent-encode", "secure"] }
|
|
||||||
|
|
||||||
# io
|
# io
|
||||||
mio = "0.6"
|
mio = "0.6"
|
||||||
@ -67,25 +66,19 @@ futures = "0.1"
|
|||||||
tokio-io = "0.1"
|
tokio-io = "0.1"
|
||||||
tokio-core = "0.1"
|
tokio-core = "0.1"
|
||||||
|
|
||||||
h2 = { git = 'https://github.com/carllerche/h2' }
|
|
||||||
|
|
||||||
# native-tls
|
# native-tls
|
||||||
native-tls = { version="0.1", optional = true }
|
native-tls = { version="0.1", optional = true }
|
||||||
tokio-tls = { version="0.1", optional = true }
|
tokio-tls = { version="0.1", optional = true }
|
||||||
|
|
||||||
# openssl
|
# openssl
|
||||||
tokio-openssl = { version="0.1", optional = true }
|
openssl = { version="0.10", optional = true }
|
||||||
|
tokio-openssl = { version="0.2", optional = true }
|
||||||
|
|
||||||
[dependencies.actix]
|
[dependencies.actix]
|
||||||
#version = "^0.4.2"
|
version = "^0.4.5"
|
||||||
git = "https://github.com/actix/actix.git"
|
|
||||||
|
|
||||||
[dependencies.openssl]
|
|
||||||
version = "0.9"
|
|
||||||
optional = true
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.4"
|
env_logger = "0.5"
|
||||||
reqwest = "0.8"
|
reqwest = "0.8"
|
||||||
skeptic = "0.13"
|
skeptic = "0.13"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
@ -97,7 +90,6 @@ version_check = "0.1"
|
|||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
# debug = true
|
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
|
14
README.md
14
README.md
@ -1,6 +1,6 @@
|
|||||||
# Actix web [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![Build status](https://ci.appveyor.com/api/projects/status/kkdb4yce7qhm5w85/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](http://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![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 web [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![Build status](https://ci.appveyor.com/api/projects/status/kkdb4yce7qhm5w85/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](http://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![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 web is a small, fast, down-to-earth, open source rust web framework.
|
Actix web is a small, fast, pragmatic, open source rust web framework.
|
||||||
|
|
||||||
```rust,ignore
|
```rust,ignore
|
||||||
extern crate actix_web;
|
extern crate actix_web;
|
||||||
@ -24,6 +24,7 @@ fn main() {
|
|||||||
* [User Guide](http://actix.github.io/actix-web/guide/)
|
* [User Guide](http://actix.github.io/actix-web/guide/)
|
||||||
* [API Documentation (Development)](http://actix.github.io/actix-web/actix_web/)
|
* [API Documentation (Development)](http://actix.github.io/actix-web/actix_web/)
|
||||||
* [API Documentation (Releases)](https://docs.rs/actix-web/)
|
* [API Documentation (Releases)](https://docs.rs/actix-web/)
|
||||||
|
* [Chat on gitter](https://gitter.im/actix/actix)
|
||||||
* Cargo package: [actix-web](https://crates.io/crates/actix-web)
|
* Cargo package: [actix-web](https://crates.io/crates/actix-web)
|
||||||
* Minimum supported Rust version: 1.20 or later
|
* Minimum supported Rust version: 1.20 or later
|
||||||
|
|
||||||
@ -39,7 +40,8 @@ fn main() {
|
|||||||
* Multipart streams
|
* Multipart streams
|
||||||
* Middlewares ([Logger](https://actix.github.io/actix-web/guide/qs_10.html#logging),
|
* Middlewares ([Logger](https://actix.github.io/actix-web/guide/qs_10.html#logging),
|
||||||
[Session](https://actix.github.io/actix-web/guide/qs_10.html#user-sessions),
|
[Session](https://actix.github.io/actix-web/guide/qs_10.html#user-sessions),
|
||||||
[DefaultHeaders](https://actix.github.io/actix-web/guide/qs_10.html#default-headers))
|
[DefaultHeaders](https://actix.github.io/actix-web/guide/qs_10.html#default-headers),
|
||||||
|
[CORS](https://actix.github.io/actix-web/actix_web/middleware/cors/index.html))
|
||||||
* Built on top of [Actix](https://github.com/actix/actix).
|
* Built on top of [Actix](https://github.com/actix/actix).
|
||||||
|
|
||||||
## Benchmarks
|
## Benchmarks
|
||||||
@ -48,7 +50,7 @@ Some basic benchmarks could be found in this [respository](https://github.com/fa
|
|||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
* [Basic](https://github.com/actix/actix-web/tree/master/examples/basic/)
|
* [Basics](https://github.com/actix/actix-web/tree/master/examples/basics/)
|
||||||
* [Stateful](https://github.com/actix/actix-web/tree/master/examples/state/)
|
* [Stateful](https://github.com/actix/actix-web/tree/master/examples/state/)
|
||||||
* [Mulitpart streams](https://github.com/actix/actix-web/tree/master/examples/multipart/)
|
* [Mulitpart streams](https://github.com/actix/actix-web/tree/master/examples/multipart/)
|
||||||
* [Simple websocket session](https://github.com/actix/actix-web/tree/master/examples/websocket/)
|
* [Simple websocket session](https://github.com/actix/actix-web/tree/master/examples/websocket/)
|
||||||
@ -68,4 +70,8 @@ This project is licensed under either of
|
|||||||
|
|
||||||
at your option.
|
at your option.
|
||||||
|
|
||||||
[![Analytics](https://ga-beacon.appspot.com/UA-110322332-2/actix-web/readme?flat&useReferer)](https://github.com/igrigorik/ga-beacon)
|
## Code of Conduct
|
||||||
|
|
||||||
|
Contribution to the actix-web crate is organized under the terms of the
|
||||||
|
Contributor Covenant, the maintainer of actix-web, @fafhrd91, promises to
|
||||||
|
intervene to uphold that code of conduct.
|
||||||
|
1
build.rs
1
build.rs
@ -25,6 +25,7 @@ fn main() {
|
|||||||
"guide/src/qs_10.md",
|
"guide/src/qs_10.md",
|
||||||
"guide/src/qs_12.md",
|
"guide/src/qs_12.md",
|
||||||
"guide/src/qs_13.md",
|
"guide/src/qs_13.md",
|
||||||
|
"guide/src/qs_14.md",
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
let _ = fs::File::create(f);
|
let _ = fs::File::create(f);
|
||||||
|
@ -6,6 +6,6 @@ workspace = "../.."
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
futures = "*"
|
futures = "*"
|
||||||
env_logger = "0.4"
|
env_logger = "0.5"
|
||||||
actix = "0.4"
|
actix = "0.4"
|
||||||
actix-web = { git = "https://github.com/actix/actix-web" }
|
actix-web = { path="../.." }
|
||||||
|
@ -17,3 +17,4 @@ cargo run
|
|||||||
- [http://localhost:8080/user/bob/](http://localhost:8080/user/bob/) plain/text download
|
- [http://localhost:8080/user/bob/](http://localhost:8080/user/bob/) plain/text download
|
||||||
- [http://localhost:8080/test](http://localhost:8080/test) (return status switch GET or POST or other)
|
- [http://localhost:8080/test](http://localhost:8080/test) (return status switch GET or POST or other)
|
||||||
- [http://localhost:8080/static/index.html](http://localhost:8080/static/index.html)
|
- [http://localhost:8080/static/index.html](http://localhost:8080/static/index.html)
|
||||||
|
- [http://localhost:8080/static/notexit](http://localhost:8080/static/notexit) display 404 page
|
||||||
|
@ -7,6 +7,7 @@ extern crate env_logger;
|
|||||||
extern crate futures;
|
extern crate futures;
|
||||||
use futures::Stream;
|
use futures::Stream;
|
||||||
|
|
||||||
|
use std::{io, env};
|
||||||
use actix_web::*;
|
use actix_web::*;
|
||||||
use actix_web::middleware::RequestSession;
|
use actix_web::middleware::RequestSession;
|
||||||
use futures::future::{FutureResult, result};
|
use futures::future::{FutureResult, result};
|
||||||
@ -56,17 +57,17 @@ fn index(mut req: HttpRequest) -> Result<HttpResponse> {
|
|||||||
fn p404(req: HttpRequest) -> Result<HttpResponse> {
|
fn p404(req: HttpRequest) -> Result<HttpResponse> {
|
||||||
|
|
||||||
// html
|
// html
|
||||||
let html = format!(r#"<!DOCTYPE html><html><head><title>actix - basics</title><link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" /></head>
|
let html = r#"<!DOCTYPE html><html><head><title>actix - basics</title><link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" /></head>
|
||||||
<body>
|
<body>
|
||||||
<a href="index.html">back to home</a>
|
<a href="index.html">back to home</a>
|
||||||
<h1>404</h1>
|
<h1>404</h1>
|
||||||
</body>
|
</body>
|
||||||
</html>"#);
|
</html>"#;
|
||||||
|
|
||||||
// response
|
// response
|
||||||
Ok(HttpResponse::build(StatusCode::NOT_FOUND)
|
Ok(HttpResponse::build(StatusCode::NOT_FOUND)
|
||||||
.content_type("text/html; charset=utf-8")
|
.content_type("text/html; charset=utf-8")
|
||||||
.body(&html).unwrap())
|
.body(html).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -92,8 +93,9 @@ fn with_param(req: HttpRequest) -> Result<HttpResponse>
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
::std::env::set_var("RUST_LOG", "actix_web=info");
|
env::set_var("RUST_LOG", "actix_web=debug");
|
||||||
let _ = env_logger::init();
|
env::set_var("RUST_BACKTRACE", "1");
|
||||||
|
env_logger::init();
|
||||||
let sys = actix::System::new("basic-example");
|
let sys = actix::System::new("basic-example");
|
||||||
|
|
||||||
let addr = HttpServer::new(
|
let addr = HttpServer::new(
|
||||||
@ -121,6 +123,9 @@ fn main() {
|
|||||||
_ => httpcodes::HTTPNotFound,
|
_ => httpcodes::HTTPNotFound,
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
.resource("/error.html", |r| r.f(|req| {
|
||||||
|
error::ErrorBadRequest(io::Error::new(io::ErrorKind::Other, "test"))
|
||||||
|
}))
|
||||||
// static files
|
// static files
|
||||||
.handler("/static/", fs::StaticFiles::new("../static/", true))
|
.handler("/static/", fs::StaticFiles::new("../static/", true))
|
||||||
// redirect
|
// redirect
|
||||||
|
@ -5,9 +5,9 @@ authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
|||||||
workspace = "../.."
|
workspace = "../.."
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
env_logger = "0.4"
|
env_logger = "0.5"
|
||||||
actix = "0.4"
|
actix = "0.4"
|
||||||
actix-web = { git = "https://github.com/actix/actix-web" }
|
actix-web = { path = "../../" }
|
||||||
|
|
||||||
futures = "0.1"
|
futures = "0.1"
|
||||||
uuid = { version = "0.5", features = ["serde", "v4"] }
|
uuid = { version = "0.5", features = ["serde", "v4"] }
|
||||||
|
@ -8,7 +8,7 @@ use diesel::prelude::*;
|
|||||||
use models;
|
use models;
|
||||||
use schema;
|
use schema;
|
||||||
|
|
||||||
/// This is db executor actor. We are going to run 3 of them in parallele.
|
/// This is db executor actor. We are going to run 3 of them in parallel.
|
||||||
pub struct DbExecutor(pub SqliteConnection);
|
pub struct DbExecutor(pub SqliteConnection);
|
||||||
|
|
||||||
/// This is only message that this actor can handle, but it is easy to extend number of
|
/// This is only message that this actor can handle, but it is easy to extend number of
|
||||||
|
@ -5,6 +5,6 @@ authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
|||||||
workspace = "../.."
|
workspace = "../.."
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
env_logger = "0.4"
|
env_logger = "0.5"
|
||||||
actix = "0.4"
|
actix = "0.4"
|
||||||
actix-web = { path = "../../" }
|
actix-web = { path = "../../" }
|
||||||
|
@ -15,4 +15,4 @@ serde_derive = "1.0"
|
|||||||
json = "*"
|
json = "*"
|
||||||
|
|
||||||
actix = "0.4"
|
actix = "0.4"
|
||||||
actix-web = { git = "https://github.com/actix/actix-web" }
|
actix-web = { path="../../" }
|
||||||
|
@ -12,4 +12,4 @@ path = "src/main.rs"
|
|||||||
env_logger = "*"
|
env_logger = "*"
|
||||||
futures = "0.1"
|
futures = "0.1"
|
||||||
actix = "0.4"
|
actix = "0.4"
|
||||||
actix-web = { git = "https://github.com/actix/actix-web" }
|
actix-web = { path="../../" }
|
||||||
|
@ -6,6 +6,6 @@ workspace = "../.."
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
futures = "*"
|
futures = "*"
|
||||||
env_logger = "0.4"
|
env_logger = "0.5"
|
||||||
actix = "0.4"
|
actix = "0.4"
|
||||||
actix-web = { git = "https://github.com/actix/actix-web" }
|
actix-web = { path = "../../" }
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#![cfg_attr(feature="cargo-clippy", allow(needless_pass_by_value))]
|
#![cfg_attr(feature="cargo-clippy", allow(needless_pass_by_value))]
|
||||||
//! There are two level of statfulness in actix-web. Application has state
|
//! There are two level of statefulness in actix-web. Application has state
|
||||||
//! that is shared across all handlers within same Application.
|
//! that is shared across all handlers within same Application.
|
||||||
//! And individual handler can have state.
|
//! And individual handler can have state.
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ struct MyWebSocket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Actor for MyWebSocket {
|
impl Actor for MyWebSocket {
|
||||||
type Context = HttpContext<Self, AppState>;
|
type Context = ws::WebsocketContext<Self, AppState>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handler<ws::Message> for MyWebSocket {
|
impl Handler<ws::Message> for MyWebSocket {
|
||||||
@ -43,9 +43,9 @@ impl Handler<ws::Message> for MyWebSocket {
|
|||||||
self.counter += 1;
|
self.counter += 1;
|
||||||
println!("WS({}): {:?}", self.counter, msg);
|
println!("WS({}): {:?}", self.counter, msg);
|
||||||
match msg {
|
match msg {
|
||||||
ws::Message::Ping(msg) => ws::WsWriter::pong(ctx, &msg),
|
ws::Message::Ping(msg) => ctx.pong(&msg),
|
||||||
ws::Message::Text(text) => ws::WsWriter::text(ctx, &text),
|
ws::Message::Text(text) => ctx.text(&text),
|
||||||
ws::Message::Binary(bin) => ws::WsWriter::binary(ctx, bin),
|
ws::Message::Binary(bin) => ctx.binary(bin),
|
||||||
ws::Message::Closed | ws::Message::Error => {
|
ws::Message::Closed | ws::Message::Error => {
|
||||||
ctx.stop();
|
ctx.stop();
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
|||||||
workspace = "../.."
|
workspace = "../.."
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
env_logger = "0.4"
|
env_logger = "0.5"
|
||||||
actix = "0.4"
|
actix = "0.4"
|
||||||
actix-web = { git = "https://github.com/actix/actix-web" }
|
actix-web = { path = "../../" }
|
||||||
tera = "*"
|
tera = "*"
|
||||||
|
@ -9,6 +9,7 @@ name = "server"
|
|||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
env_logger = "0.4"
|
env_logger = "0.5"
|
||||||
actix = "0.4"
|
actix = "^0.4.2"
|
||||||
actix-web = { git = "https://github.com/actix/actix-web", features=["alpn"] }
|
actix-web = { path = "../../", features=["alpn"] }
|
||||||
|
openssl = { version="0.10", features = ["v110"] }
|
||||||
|
31
examples/tls/cert.pem
Normal file
31
examples/tls/cert.pem
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIFPjCCAyYCCQDvLYiYD+jqeTANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJV
|
||||||
|
UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMRAwDgYDVQQKDAdDb21wYW55MQww
|
||||||
|
CgYDVQQLDANPcmcxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xODAxMjUx
|
||||||
|
NzQ2MDFaFw0xOTAxMjUxNzQ2MDFaMGExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJD
|
||||||
|
QTELMAkGA1UEBwwCU0YxEDAOBgNVBAoMB0NvbXBhbnkxDDAKBgNVBAsMA09yZzEY
|
||||||
|
MBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
|
||||||
|
MIICCgKCAgEA2WzIA2IpVR9Tb9EFhITlxuhE5rY2a3S6qzYNzQVgSFggxXEPn8k1
|
||||||
|
sQEcer5BfAP986Sck3H0FvB4Bt/I8PwOtUCmhwcc8KtB5TcGPR4fjXnrpC+MIK5U
|
||||||
|
NLkwuyBDKziYzTdBj8kUFX1WxmvEHEgqToPOZfBgsS71cJAR/zOWraDLSRM54jXy
|
||||||
|
voLZN4Ti9rQagQrvTQ44Vz5ycDQy7UxtbUGh1CVv69vNVr7/SOOh/Nw5FNOZWLWr
|
||||||
|
odGyoec5wh9iqRZgRqiTUc6Lt7V2RWc2X2gjwST2UfI+U46Ip3oaQ7ZD4eAkoqND
|
||||||
|
xdniBZAykVG3c/99ux4BAESTF8fsNch6UticBxYMuTu+ouvP0psfI9wwwNliJDmA
|
||||||
|
CRUTB9AgRynbL1AzhqQoDfsb98IZfjfNOpwnwuLwpMAPhbgd5KNdZaIJ4Hb6/stI
|
||||||
|
yFElOExxd3TAxF2Gshd/lq1JcNHAZ1DSXV5MvOWT/NWgXwbIzUgQ8eIi+HuDYX2U
|
||||||
|
UuaB6R8tbd52H7rbUv6HrfinuSlKWqjSYLkiKHkwUpoMw8y9UycRSzs1E9nPwPTO
|
||||||
|
vRXb0mNCQeBCV9FvStNVXdCUTT8LGPv87xSD2pmt7LijlE6mHLG8McfcWkzA69un
|
||||||
|
CEHIFAFDimTuN7EBljc119xWFTcHMyoZAfFF+oTqwSbBGImruCxnaJECAwEAATAN
|
||||||
|
BgkqhkiG9w0BAQsFAAOCAgEApavsgsn7SpPHfhDSN5iZs1ILZQRewJg0Bty0xPfk
|
||||||
|
3tynSW6bNH3nSaKbpsdmxxomthNSQgD2heOq1By9YzeOoNR+7Pk3s4FkASnf3ToI
|
||||||
|
JNTUasBFFfaCG96s4Yvs8KiWS/k84yaWuU8c3Wb1jXs5Rv1qE1Uvuwat1DSGXSoD
|
||||||
|
JNluuIkCsC4kWkyq5pWCGQrabWPRTWsHwC3PTcwSRBaFgYLJaR72SloHB1ot02zL
|
||||||
|
d2age9dmFRFLLCBzP+D7RojBvL37qS/HR+rQ4SoQwiVc/JzaeqSe7ZbvEH9sZYEu
|
||||||
|
ALowJzgbwro7oZflwTWunSeSGDSltkqKjvWvZI61pwfHKDahUTmZ5h2y67FuGEaC
|
||||||
|
CIOUI8dSVSPKITxaq3JL4ze2e9/0Lt7hj19YK2uUmtMAW5Tirz4Yx5lyGH9U8Wur
|
||||||
|
y/X8VPxTc4A9TMlJgkyz0hqvhbPOT/zSWB10zXh0glKAsSBryAOEDxV1UygmSir7
|
||||||
|
YV8Qaq+oyKUTMc1MFq5vZ07M51EPaietn85t8V2Y+k/8XYltRp32NxsypxAJuyxh
|
||||||
|
g/ko6RVTrWa1sMvz/F9LFqAdKiK5eM96lh9IU4xiLg4ob8aS/GRAA8oIFkZFhLrt
|
||||||
|
tOwjIUPmEPyHWFi8dLpNuQKYalLYhuwZftG/9xV+wqhKGZO9iPrpHSYBRTap8w2y
|
||||||
|
1QU=
|
||||||
|
-----END CERTIFICATE-----
|
Binary file not shown.
51
examples/tls/key.pem
Normal file
51
examples/tls/key.pem
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIJKAIBAAKCAgEA2WzIA2IpVR9Tb9EFhITlxuhE5rY2a3S6qzYNzQVgSFggxXEP
|
||||||
|
n8k1sQEcer5BfAP986Sck3H0FvB4Bt/I8PwOtUCmhwcc8KtB5TcGPR4fjXnrpC+M
|
||||||
|
IK5UNLkwuyBDKziYzTdBj8kUFX1WxmvEHEgqToPOZfBgsS71cJAR/zOWraDLSRM5
|
||||||
|
4jXyvoLZN4Ti9rQagQrvTQ44Vz5ycDQy7UxtbUGh1CVv69vNVr7/SOOh/Nw5FNOZ
|
||||||
|
WLWrodGyoec5wh9iqRZgRqiTUc6Lt7V2RWc2X2gjwST2UfI+U46Ip3oaQ7ZD4eAk
|
||||||
|
oqNDxdniBZAykVG3c/99ux4BAESTF8fsNch6UticBxYMuTu+ouvP0psfI9wwwNli
|
||||||
|
JDmACRUTB9AgRynbL1AzhqQoDfsb98IZfjfNOpwnwuLwpMAPhbgd5KNdZaIJ4Hb6
|
||||||
|
/stIyFElOExxd3TAxF2Gshd/lq1JcNHAZ1DSXV5MvOWT/NWgXwbIzUgQ8eIi+HuD
|
||||||
|
YX2UUuaB6R8tbd52H7rbUv6HrfinuSlKWqjSYLkiKHkwUpoMw8y9UycRSzs1E9nP
|
||||||
|
wPTOvRXb0mNCQeBCV9FvStNVXdCUTT8LGPv87xSD2pmt7LijlE6mHLG8McfcWkzA
|
||||||
|
69unCEHIFAFDimTuN7EBljc119xWFTcHMyoZAfFF+oTqwSbBGImruCxnaJECAwEA
|
||||||
|
AQKCAgAME3aoeXNCPxMrSri7u4Xnnk71YXl0Tm9vwvjRQlMusXZggP8VKN/KjP0/
|
||||||
|
9AE/GhmoxqPLrLCZ9ZE1EIjgmZ9Xgde9+C8rTtfCG2RFUL7/5J2p6NonlocmxoJm
|
||||||
|
YkxYwjP6ce86RTjQWL3RF3s09u0inz9/efJk5O7M6bOWMQ9VZXDlBiRY5BYvbqUR
|
||||||
|
6FeSzD4MnMbdyMRoVBeXE88gTvZk8xhB6DJnLzYgc0tKiRoeKT0iYv5JZw25VyRM
|
||||||
|
ycLzfTrFmXCPfB1ylb483d9Ly4fBlM8nkx37PzEnAuukIawDxsPOb9yZC+hfvNJI
|
||||||
|
7NFiMN+3maEqG2iC00w4Lep4skHY7eHUEUMl+Wjr+koAy2YGLWAwHZQTm7iXn9Ab
|
||||||
|
L6adL53zyCKelRuEQOzbeosJAqS+5fpMK0ekXyoFIuskj7bWuIoCX7K/kg6q5IW+
|
||||||
|
vC2FrlsrbQ79GztWLVmHFO1I4J9M5r666YS0qdh8c+2yyRl4FmSiHfGxb3eOKpxQ
|
||||||
|
b6uI97iZlkxPF9LYUCSc7wq0V2gGz+6LnGvTHlHrOfVXqw/5pLAKhXqxvnroDTwz
|
||||||
|
0Ay/xFF6ei/NSxBY5t8ztGCBm45wCU3l8pW0X6dXqwUipw5b4MRy1VFRu6rqlmbL
|
||||||
|
OPSCuLxqyqsigiEYsBgS/icvXz9DWmCQMPd2XM9YhsHvUq+R4QKCAQEA98EuMMXI
|
||||||
|
6UKIt1kK2t/3OeJRyDd4iv/fCMUAnuPjLBvFE4cXD/SbqCxcQYqb+pue3PYkiTIC
|
||||||
|
71rN8OQAc5yKhzmmnCE5N26br/0pG4pwEjIr6mt8kZHmemOCNEzvhhT83nfKmV0g
|
||||||
|
9lNtuGEQMiwmZrpUOF51JOMC39bzcVjYX2Cmvb7cFbIq3lR0zwM+aZpQ4P8LHCIu
|
||||||
|
bgHmwbdlkLyIULJcQmHIbo6nPFB3ZZE4mqmjwY+rA6Fh9rgBa8OFCfTtrgeYXrNb
|
||||||
|
IgZQ5U8GoYRPNC2ot0vpTinraboa/cgm6oG4M7FW1POCJTl+/ktHEnKuO5oroSga
|
||||||
|
/BSg7hCNFVaOhwKCAQEA4Kkys0HtwEbV5mY/NnvUD5KwfXX7BxoXc9lZ6seVoLEc
|
||||||
|
KjgPYxqYRVrC7dB2YDwwp3qcRTi/uBAgFNm3iYlDzI4xS5SeaudUWjglj7BSgXE2
|
||||||
|
iOEa7EwcvVPluLaTgiWjlzUKeUCNNHWSeQOt+paBOT+IgwRVemGVpAgkqQzNh/nP
|
||||||
|
tl3p9aNtgzEm1qVlPclY/XUCtf3bcOR+z1f1b4jBdn0leu5OhnxkC+Htik+2fTXD
|
||||||
|
jt6JGrMkanN25YzsjnD3Sn+v6SO26H99wnYx5oMSdmb8SlWRrKtfJHnihphjG/YY
|
||||||
|
l1cyorV6M/asSgXNQfGJm4OuJi0I4/FL2wLUHnU+JwKCAQEAzh4WipcRthYXXcoj
|
||||||
|
gMKRkMOb3GFh1OpYqJgVExtudNTJmZxq8GhFU51MR27Eo7LycMwKy2UjEfTOnplh
|
||||||
|
Us2qZiPtW7k8O8S2m6yXlYUQBeNdq9IuuYDTaYD94vsazscJNSAeGodjE+uGvb1q
|
||||||
|
1wLqE87yoE7dUInYa1cOA3+xy2/CaNuviBFJHtzOrSb6tqqenQEyQf6h9/12+DTW
|
||||||
|
t5pSIiixHrzxHiFqOoCLRKGToQB+71rSINwTf0nITNpGBWmSj5VcC3VV3TG5/XxI
|
||||||
|
fPlxV2yhD5WFDPVNGBGvwPDSh4jSMZdZMSNBZCy4XWFNSKjGEWoK4DFYed3DoSt9
|
||||||
|
5IG1YwKCAQA63ntHl64KJUWlkwNbboU583FF3uWBjee5VqoGKHhf3CkKMxhtGqnt
|
||||||
|
+oN7t5VdUEhbinhqdx1dyPPvIsHCS3K1pkjqii4cyzNCVNYa2dQ00Qq+QWZBpwwc
|
||||||
|
3GAkz8rFXsGIPMDa1vxpU6mnBjzPniKMcsZ9tmQDppCEpBGfLpio2eAA5IkK8eEf
|
||||||
|
cIDB3CM0Vo94EvI76CJZabaE9IJ+0HIJb2+jz9BJ00yQBIqvJIYoNy9gP5Xjpi+T
|
||||||
|
qV/tdMkD5jwWjHD3AYHLWKUGkNwwkAYFeqT/gX6jpWBP+ZRPOp011X3KInJFSpKU
|
||||||
|
DT5GQ1Dux7EMTCwVGtXqjO8Ym5wjwwsfAoIBAEcxlhIW1G6BiNfnWbNPWBdh3v/K
|
||||||
|
5Ln98Rcrz8UIbWyl7qNPjYb13C1KmifVG1Rym9vWMO3KuG5atK3Mz2yLVRtmWAVc
|
||||||
|
fxzR57zz9MZFDun66xo+Z1wN3fVxQB4CYpOEI4Lb9ioX4v85hm3D6RpFukNtRQEc
|
||||||
|
Gfr4scTjJX4jFWDp0h6ffMb8mY+quvZoJ0TJqV9L9Yj6Ksdvqez/bdSraev97bHQ
|
||||||
|
4gbQxaTZ6WjaD4HjpPQefMdWp97Metg0ZQSS8b8EzmNFgyJ3XcjirzwliKTAQtn6
|
||||||
|
I2sd0NCIooelrKRD8EJoDUwxoOctY7R97wpZ7/wEHU45cBCbRV3H4JILS5c=
|
||||||
|
-----END RSA PRIVATE KEY-----
|
@ -2,14 +2,13 @@
|
|||||||
extern crate actix;
|
extern crate actix;
|
||||||
extern crate actix_web;
|
extern crate actix_web;
|
||||||
extern crate env_logger;
|
extern crate env_logger;
|
||||||
|
extern crate openssl;
|
||||||
use std::fs::File;
|
|
||||||
use std::io::Read;
|
|
||||||
|
|
||||||
use actix_web::*;
|
use actix_web::*;
|
||||||
|
use openssl::ssl::{SslMethod, SslAcceptor, SslFiletype};
|
||||||
|
|
||||||
|
|
||||||
/// somple handle
|
/// simple handle
|
||||||
fn index(req: HttpRequest) -> Result<HttpResponse> {
|
fn index(req: HttpRequest) -> Result<HttpResponse> {
|
||||||
println!("{:?}", req);
|
println!("{:?}", req);
|
||||||
Ok(httpcodes::HTTPOk
|
Ok(httpcodes::HTTPOk
|
||||||
@ -20,15 +19,15 @@ fn index(req: HttpRequest) -> Result<HttpResponse> {
|
|||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
if ::std::env::var("RUST_LOG").is_err() {
|
if ::std::env::var("RUST_LOG").is_err() {
|
||||||
::std::env::set_var("RUST_LOG", "actix_web=trace");
|
::std::env::set_var("RUST_LOG", "actix_web=info");
|
||||||
}
|
}
|
||||||
let _ = env_logger::init();
|
let _ = env_logger::init();
|
||||||
let sys = actix::System::new("ws-example");
|
let sys = actix::System::new("ws-example");
|
||||||
|
|
||||||
let mut file = File::open("identity.pfx").unwrap();
|
// load ssl keys
|
||||||
let mut pkcs12 = vec![];
|
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
|
||||||
file.read_to_end(&mut pkcs12).unwrap();
|
builder.set_private_key_file("key.pem", SslFiletype::PEM).unwrap();
|
||||||
let pkcs12 = Pkcs12::from_der(&pkcs12).unwrap().parse("12345").unwrap();
|
builder.set_certificate_chain_file("cert.pem").unwrap();
|
||||||
|
|
||||||
let addr = HttpServer::new(
|
let addr = HttpServer::new(
|
||||||
|| Application::new()
|
|| Application::new()
|
||||||
@ -44,7 +43,7 @@ fn main() {
|
|||||||
.body(Body::Empty)
|
.body(Body::Empty)
|
||||||
})))
|
})))
|
||||||
.bind("127.0.0.1:8443").unwrap()
|
.bind("127.0.0.1:8443").unwrap()
|
||||||
.start_ssl(&pkcs12).unwrap();
|
.start_ssl(builder).unwrap();
|
||||||
|
|
||||||
println!("Started http server: 127.0.0.1:8443");
|
println!("Started http server: 127.0.0.1:8443");
|
||||||
let _ = sys.run();
|
let _ = sys.run();
|
||||||
|
@ -25,6 +25,5 @@ serde = "1.0"
|
|||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
|
|
||||||
#actix = "0.4"
|
actix = "^0.4.2"
|
||||||
actix = { git = "https://github.com/actix/actix" }
|
actix-web = { path="../../" }
|
||||||
actix-web = { git = "https://github.com/actix/actix-web" }
|
|
||||||
|
@ -16,8 +16,8 @@ Chat server listens for incoming tcp connections. Server can access several type
|
|||||||
* `\list` - list all available rooms
|
* `\list` - list all available rooms
|
||||||
* `\join name` - join room, if room does not exist, create new one
|
* `\join name` - join room, if room does not exist, create new one
|
||||||
* `\name name` - set session name
|
* `\name name` - set session name
|
||||||
* `some message` - just string, send messsage to all peers in same room
|
* `some message` - just string, send message to all peers in same room
|
||||||
* client has to send heartbeat `Ping` messages, if server does not receive a heartbeat message for 10 seconds connection gets droppped
|
* client has to send heartbeat `Ping` messages, if server does not receive a heartbeat message for 10 seconds connection gets dropped
|
||||||
|
|
||||||
To start server use command: `cargo run --bin server`
|
To start server use command: `cargo run --bin server`
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ struct WsChatSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Actor for WsChatSession {
|
impl Actor for WsChatSession {
|
||||||
type Context = HttpContext<Self, WsChatSessionState>;
|
type Context = ws::WebsocketContext<Self, WsChatSessionState>;
|
||||||
|
|
||||||
/// Method is called on actor start.
|
/// Method is called on actor start.
|
||||||
/// We register ws session with ChatServer
|
/// We register ws session with ChatServer
|
||||||
@ -87,7 +87,7 @@ impl Handler<session::Message> for WsChatSession {
|
|||||||
type Result = ();
|
type Result = ();
|
||||||
|
|
||||||
fn handle(&mut self, msg: session::Message, ctx: &mut Self::Context) {
|
fn handle(&mut self, msg: session::Message, ctx: &mut Self::Context) {
|
||||||
ws::WsWriter::text(ctx, &msg.0);
|
ctx.text(&msg.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,10 +98,8 @@ impl Handler<ws::Message> for WsChatSession {
|
|||||||
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
|
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
|
||||||
println!("WEBSOCKET MESSAGE: {:?}", msg);
|
println!("WEBSOCKET MESSAGE: {:?}", msg);
|
||||||
match msg {
|
match msg {
|
||||||
ws::Message::Ping(msg) =>
|
ws::Message::Ping(msg) => ctx.pong(&msg),
|
||||||
ws::WsWriter::pong(ctx, &msg),
|
ws::Message::Pong(msg) => self.hb = Instant::now(),
|
||||||
ws::Message::Pong(msg) =>
|
|
||||||
self.hb = Instant::now(),
|
|
||||||
ws::Message::Text(text) => {
|
ws::Message::Text(text) => {
|
||||||
let m = text.trim();
|
let m = text.trim();
|
||||||
// we check for /sss type of messages
|
// we check for /sss type of messages
|
||||||
@ -115,7 +113,7 @@ impl Handler<ws::Message> for WsChatSession {
|
|||||||
match res {
|
match res {
|
||||||
Ok(Ok(rooms)) => {
|
Ok(Ok(rooms)) => {
|
||||||
for room in rooms {
|
for room in rooms {
|
||||||
ws::WsWriter::text(ctx, &room);
|
ctx.text(&room);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ => println!("Something is wrong"),
|
_ => println!("Something is wrong"),
|
||||||
@ -132,20 +130,19 @@ impl Handler<ws::Message> for WsChatSession {
|
|||||||
ctx.state().addr.send(
|
ctx.state().addr.send(
|
||||||
server::Join{id: self.id, name: self.room.clone()});
|
server::Join{id: self.id, name: self.room.clone()});
|
||||||
|
|
||||||
ws::WsWriter::text(ctx, "joined");
|
ctx.text("joined");
|
||||||
} else {
|
} else {
|
||||||
ws::WsWriter::text(ctx, "!!! room name is required");
|
ctx.text("!!! room name is required");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/name" => {
|
"/name" => {
|
||||||
if v.len() == 2 {
|
if v.len() == 2 {
|
||||||
self.name = Some(v[1].to_owned());
|
self.name = Some(v[1].to_owned());
|
||||||
} else {
|
} else {
|
||||||
ws::WsWriter::text(ctx, "!!! name is required");
|
ctx.text("!!! name is required");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ => ws::WsWriter::text(
|
_ => ctx.text(&format!("!!! unknown command: {:?}", m)),
|
||||||
ctx, &format!("!!! unknown command: {:?}", m)),
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let msg = if let Some(ref name) = self.name {
|
let msg = if let Some(ref name) = self.name {
|
||||||
|
@ -16,7 +16,7 @@ use codec::{ChatRequest, ChatResponse, ChatCodec};
|
|||||||
#[derive(Message)]
|
#[derive(Message)]
|
||||||
pub struct Message(pub String);
|
pub struct Message(pub String);
|
||||||
|
|
||||||
/// `ChatSession` actor is responsible for tcp peer communitions.
|
/// `ChatSession` actor is responsible for tcp peer communications.
|
||||||
pub struct ChatSession {
|
pub struct ChatSession {
|
||||||
/// unique session id
|
/// unique session id
|
||||||
id: usize,
|
id: usize,
|
||||||
@ -30,7 +30,7 @@ pub struct ChatSession {
|
|||||||
|
|
||||||
impl Actor for ChatSession {
|
impl Actor for ChatSession {
|
||||||
/// For tcp communication we are going to use `FramedContext`.
|
/// For tcp communication we are going to use `FramedContext`.
|
||||||
/// It is convinient wrapper around `Framed` object from `tokio_io`
|
/// It is convenient wrapper around `Framed` object from `tokio_io`
|
||||||
type Context = FramedContext<Self>;
|
type Context = FramedContext<Self>;
|
||||||
|
|
||||||
fn started(&mut self, ctx: &mut Self::Context) {
|
fn started(&mut self, ctx: &mut Self::Context) {
|
||||||
@ -149,7 +149,7 @@ impl ChatSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Define tcp server that will accept incomint tcp connection and create
|
/// Define tcp server that will accept incoming tcp connection and create
|
||||||
/// chat actors.
|
/// chat actors.
|
||||||
pub struct TcpServer {
|
pub struct TcpServer {
|
||||||
chat: SyncAddress<ChatServer>,
|
chat: SyncAddress<ChatServer>,
|
||||||
|
@ -10,6 +10,5 @@ path = "src/main.rs"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
env_logger = "*"
|
env_logger = "*"
|
||||||
futures = "0.1"
|
futures = "0.1"
|
||||||
#actix = "0.4"
|
actix = "^0.4.2"
|
||||||
actix = { git = "https://github.com/actix/actix.git" }
|
|
||||||
actix-web = { git = "https://github.com/actix/actix-web.git" }
|
actix-web = { git = "https://github.com/actix/actix-web.git" }
|
||||||
|
@ -21,20 +21,20 @@ fn ws_index(r: HttpRequest) -> Result<HttpResponse> {
|
|||||||
struct MyWebSocket;
|
struct MyWebSocket;
|
||||||
|
|
||||||
impl Actor for MyWebSocket {
|
impl Actor for MyWebSocket {
|
||||||
type Context = HttpContext<Self>;
|
type Context = ws::WebsocketContext<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handler for `ws::Message`
|
/// Handler for `ws::Message`
|
||||||
impl Handler<ws::Message> for MyWebSocket {
|
impl Handler<ws::Message> for MyWebSocket {
|
||||||
type Result = ();
|
type Result = ();
|
||||||
|
|
||||||
fn handle(&mut self, msg: ws::Message, ctx: &mut HttpContext<Self>) {
|
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
|
||||||
// process websocket messages
|
// process websocket messages
|
||||||
println!("WS: {:?}", msg);
|
println!("WS: {:?}", msg);
|
||||||
match msg {
|
match msg {
|
||||||
ws::Message::Ping(msg) => ws::WsWriter::pong(ctx, &msg),
|
ws::Message::Ping(msg) => ctx.pong(&msg),
|
||||||
ws::Message::Text(text) => ws::WsWriter::text(ctx, &text),
|
ws::Message::Text(text) => ctx.text(&text),
|
||||||
ws::Message::Binary(bin) => ws::WsWriter::binary(ctx, bin),
|
ws::Message::Binary(bin) => ctx.binary(bin),
|
||||||
ws::Message::Closed | ws::Message::Error => {
|
ws::Message::Closed | ws::Message::Error => {
|
||||||
ctx.stop();
|
ctx.stop();
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ If you already have rustup installed, run this command to ensure you have the la
|
|||||||
rustup update
|
rustup update
|
||||||
```
|
```
|
||||||
|
|
||||||
Actix web framework requies rust version 1.20 and up.
|
Actix web framework requires rust version 1.20 and up.
|
||||||
|
|
||||||
## Running Examples
|
## Running Examples
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# Middlewares
|
# Middlewares
|
||||||
|
|
||||||
Actix middlewares system allows to add additional behaviour to request/response processing.
|
Actix middlewares system allows to add additional behavior to request/response processing.
|
||||||
Middleware can hook into incomnig request process and modify request or halt request
|
Middleware can hook into incoming request process and modify request or halt request
|
||||||
processing and return response early. Also it can hook into response processing.
|
processing and return response early. Also it can hook into response processing.
|
||||||
|
|
||||||
Typically middlewares involves in following actions:
|
Typically middlewares involves in following actions:
|
||||||
@ -12,9 +12,9 @@ Typically middlewares involves in following actions:
|
|||||||
* Access external services (redis, logging, sessions)
|
* Access external services (redis, logging, sessions)
|
||||||
|
|
||||||
Middlewares are registered for each application and get executed in same order as
|
Middlewares are registered for each application and get executed in same order as
|
||||||
registraton order. In general, *middleware* is a type that implements
|
registration order. In general, *middleware* is a type that implements
|
||||||
[*Middleware trait*](../actix_web/middlewares/trait.Middleware.html). Each method
|
[*Middleware trait*](../actix_web/middlewares/trait.Middleware.html). Each method
|
||||||
in this trait has default implementation. Each method can return result immidietly
|
in this trait has default implementation. Each method can return result immediately
|
||||||
or *future* object.
|
or *future* object.
|
||||||
|
|
||||||
Here is example of simple middleware that adds request and response headers:
|
Here is example of simple middleware that adds request and response headers:
|
||||||
@ -34,19 +34,19 @@ impl<S> Middleware<S> for Headers {
|
|||||||
|
|
||||||
/// Method is called when request is ready. It may return
|
/// Method is called when request is ready. It may return
|
||||||
/// future, which should resolve before next middleware get called.
|
/// future, which should resolve before next middleware get called.
|
||||||
fn start(&self, req: &mut HttpRequest<S>) -> Started {
|
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
|
||||||
req.headers_mut().insert(
|
req.headers_mut().insert(
|
||||||
header::CONTENT_TYPE, header::HeaderValue::from_static("text/plain"));
|
header::CONTENT_TYPE, header::HeaderValue::from_static("text/plain"));
|
||||||
Started::Done
|
Ok(Started::Done)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Method is called when handler returns response,
|
/// Method is called when handler returns response,
|
||||||
/// but before sending http message to peer.
|
/// but before sending http message to peer.
|
||||||
fn response(&self, req: &mut HttpRequest<S>, mut resp: HttpResponse) -> Response {
|
fn response(&self, req: &mut HttpRequest<S>, mut resp: HttpResponse) -> Result<Response> {
|
||||||
resp.headers_mut().insert(
|
resp.headers_mut().insert(
|
||||||
header::HeaderName::try_from("X-VERSION").unwrap(),
|
header::HeaderName::try_from("X-VERSION").unwrap(),
|
||||||
header::HeaderValue::from_static("0.2"));
|
header::HeaderValue::from_static("0.2"));
|
||||||
Response::Done(resp)
|
Ok(Response::Done(resp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,7 +148,7 @@ fn main() {
|
|||||||
## User sessions
|
## User sessions
|
||||||
|
|
||||||
Actix provides general solution for session management.
|
Actix provides general solution for session management.
|
||||||
[*Session storage*](../actix_web/middleware/struct.SessionStorage.html) middleare can be
|
[*Session storage*](../actix_web/middleware/struct.SessionStorage.html) middleware can be
|
||||||
use with different backend types to store session data in different backends.
|
use with different backend types to store session data in different backends.
|
||||||
By default only cookie session backend is implemented. Other backend implementations
|
By default only cookie session backend is implemented. Other backend implementations
|
||||||
could be added later.
|
could be added later.
|
||||||
@ -162,7 +162,7 @@ You need to pass a random value to the constructor of *CookieSessionBackend*.
|
|||||||
This is private key for cookie session. When this value is changed, all session data is lost.
|
This is private key for cookie session. When this value is changed, all session data is lost.
|
||||||
Note that whatever you write into your session is visible by the user (but not modifiable).
|
Note that whatever you write into your session is visible by the user (but not modifiable).
|
||||||
|
|
||||||
In general case, you cretate
|
In general case, you create
|
||||||
[*Session storage*](../actix_web/middleware/struct.SessionStorage.html) middleware
|
[*Session storage*](../actix_web/middleware/struct.SessionStorage.html) middleware
|
||||||
and initializes it with specific backend implementation, like *CookieSessionBackend*.
|
and initializes it with specific backend implementation, like *CookieSessionBackend*.
|
||||||
To access session data
|
To access session data
|
||||||
|
@ -4,7 +4,7 @@ Actix web automatically upgrades connection to *HTTP/2.0* if possible.
|
|||||||
|
|
||||||
## Negotiation
|
## Negotiation
|
||||||
|
|
||||||
*HTTP/2.0* protocol over tls without prior knowlage requires
|
*HTTP/2.0* protocol over tls without prior knowledge requires
|
||||||
[tls alpn](https://tools.ietf.org/html/rfc7301). At the moment only
|
[tls alpn](https://tools.ietf.org/html/rfc7301). At the moment only
|
||||||
`rust-openssl` has support. Turn on `alpn` feature to enable `alpn` negotiation.
|
`rust-openssl` has support. Turn on `alpn` feature to enable `alpn` negotiation.
|
||||||
With enable `alpn` feature `HttpServer` provides
|
With enable `alpn` feature `HttpServer` provides
|
||||||
@ -12,24 +12,26 @@ With enable `alpn` feature `HttpServer` provides
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = { git = "https://github.com/actix/actix-web", features=["alpn"] }
|
actix-web = { version = "0.3.3", features=["alpn"] }
|
||||||
|
openssl = { version="0.10", features = ["v110"] }
|
||||||
```
|
```
|
||||||
|
|
||||||
```rust,ignore
|
```rust,ignore
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use actix_web::*;
|
use actix_web::*;
|
||||||
|
use openssl::ssl::{SslMethod, SslAcceptor, SslFiletype};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut file = File::open("identity.pfx").unwrap();
|
// load ssl keys
|
||||||
let mut pkcs12 = vec![];
|
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
|
||||||
file.read_to_end(&mut pkcs12).unwrap();
|
builder.set_private_key_file("key.pem", SslFiletype::PEM).unwrap();
|
||||||
let pkcs12 = Pkcs12::from_der(&pkcs12).unwrap().parse("12345").unwrap();
|
builder.set_certificate_chain_file("cert.pem").unwrap();
|
||||||
|
|
||||||
HttpServer::new(
|
HttpServer::new(
|
||||||
|| Application::new()
|
|| Application::new()
|
||||||
.resource("/index.html", |r| r.f(index)))
|
.resource("/index.html", |r| r.f(index)))
|
||||||
.bind("127.0.0.1:8080").unwrap();
|
.bind("127.0.0.1:8080").unwrap();
|
||||||
.serve_ssl(pkcs12).unwrap();
|
.serve_ssl(builder).unwrap();
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -36,8 +36,9 @@ We can send `CreateUser` message to `DbExecutor` actor, and as result we get
|
|||||||
|
|
||||||
```rust,ignore
|
```rust,ignore
|
||||||
impl Handler<CreateUser> for DbExecutor {
|
impl Handler<CreateUser> for DbExecutor {
|
||||||
|
type Result = Result<User, Error>
|
||||||
|
|
||||||
fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Response<Self, CreateUser>
|
fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Self::Result
|
||||||
{
|
{
|
||||||
use self::schema::users::dsl::*;
|
use self::schema::users::dsl::*;
|
||||||
|
|
||||||
@ -59,7 +60,7 @@ impl Handler<CreateUser> for DbExecutor {
|
|||||||
.load::<models::User>(&self.0)
|
.load::<models::User>(&self.0)
|
||||||
.expect("Error loading person");
|
.expect("Error loading person");
|
||||||
|
|
||||||
Self::reply(items.pop().unwrap())
|
Ok(items.pop().unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -77,7 +78,7 @@ struct State {
|
|||||||
fn main() {
|
fn main() {
|
||||||
let sys = actix::System::new("diesel-example");
|
let sys = actix::System::new("diesel-example");
|
||||||
|
|
||||||
// Start 3 parallele db executors
|
// Start 3 parallel db executors
|
||||||
let addr = SyncArbiter::start(3, || {
|
let addr = SyncArbiter::start(3, || {
|
||||||
DbExecutor(SqliteConnection::establish("test.db").unwrap())
|
DbExecutor(SqliteConnection::establish("test.db").unwrap())
|
||||||
});
|
});
|
||||||
@ -94,7 +95,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
And finally we can use address in a requst handler. We get message response
|
And finally we can use address in a request handler. We get message response
|
||||||
asynchronously, so handler needs to return future object, also `Route::a()` needs to be
|
asynchronously, so handler needs to return future object, also `Route::a()` needs to be
|
||||||
used for async handler registration.
|
used for async handler registration.
|
||||||
|
|
||||||
|
@ -20,8 +20,8 @@ contains the following:
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix = "0.3"
|
actix = "0.4"
|
||||||
actix-web = { git = "https://github.com/actix/actix-web" }
|
actix-web = "0.3"
|
||||||
```
|
```
|
||||||
|
|
||||||
In order to implement a web server, first we need to create a request handler.
|
In order to implement a web server, first we need to create a request handler.
|
||||||
@ -69,7 +69,8 @@ Head over to ``http://localhost:8088/`` to see the results.
|
|||||||
|
|
||||||
Here is full source of main.rs file:
|
Here is full source of main.rs file:
|
||||||
|
|
||||||
```rust,ignore
|
```rust
|
||||||
|
# use std::thread;
|
||||||
# extern crate actix_web;
|
# extern crate actix_web;
|
||||||
use actix_web::*;
|
use actix_web::*;
|
||||||
|
|
||||||
@ -78,11 +79,13 @@ fn index(req: HttpRequest) -> &'static str {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
# thread::spawn(|| {
|
||||||
HttpServer::new(
|
HttpServer::new(
|
||||||
|| Application::new()
|
|| Application::new()
|
||||||
.resource("/", |r| r.f(index)))
|
.resource("/", |r| r.f(index)))
|
||||||
.bind("127.0.0.1:8088").expect("Can not bind to 127.0.0.1:8088")
|
.bind("127.0.0.1:8088").expect("Can not bind to 127.0.0.1:8088")
|
||||||
.run();
|
.run();
|
||||||
|
# });
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Actix web provides some primitives to build web servers and applications with Rust.
|
Actix web provides some primitives to build web servers and applications with Rust.
|
||||||
It provides routing, middlewares, pre-processing of requests, and post-processing of responses,
|
It provides routing, middlewares, pre-processing of requests, and post-processing of responses,
|
||||||
websocket protcol handling, multipart streams, etc.
|
websocket protocol handling, multipart streams, etc.
|
||||||
|
|
||||||
All actix web server is built around `Application` instance.
|
All actix web server is built around `Application` instance.
|
||||||
It is used for registering routes for resources, middlewares.
|
It is used for registering routes for resources, middlewares.
|
||||||
@ -10,9 +10,9 @@ Also it stores application specific state that is shared across all handlers
|
|||||||
within same application.
|
within same application.
|
||||||
|
|
||||||
Application acts as namespace for all routes, i.e all routes for specific application
|
Application acts as namespace for all routes, i.e all routes for specific application
|
||||||
has same url path prefix. Application prefix always contains laading "/" slash.
|
has same url path prefix. Application prefix always contains leading "/" slash.
|
||||||
If supplied prefix does not contain leading slash, it get inserted.
|
If supplied prefix does not contain leading slash, it get inserted.
|
||||||
Prefix should consists of valud path segments. i.e for application with prefix `/app`
|
Prefix should consists of value path segments. i.e for application with prefix `/app`
|
||||||
any request with following paths `/app`, `/app/` or `/app/test` would match,
|
any request with following paths `/app`, `/app/` or `/app/test` would match,
|
||||||
but path `/application` would not match.
|
but path `/application` would not match.
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[*HttpServer*](../actix_web/struct.HttpServer.html) type is responsible for
|
[*HttpServer*](../actix_web/struct.HttpServer.html) type is responsible for
|
||||||
serving http requests. *HttpServer* accept application factory as a parameter,
|
serving http requests. *HttpServer* accept application factory as a parameter,
|
||||||
Application factory must have `Send` + `Sync` bounderies. More about that in
|
Application factory must have `Send` + `Sync` boundaries. More about that in
|
||||||
*multi-threading* section. To bind to specific socket address `bind()` must be used.
|
*multi-threading* section. To bind to specific socket address `bind()` must be used.
|
||||||
This method could be called multiple times. To start http server one of the *start*
|
This method could be called multiple times. To start http server one of the *start*
|
||||||
methods could be used. `start()` method start simple server, `start_tls()` or `start_ssl()`
|
methods could be used. `start()` method start simple server, `start_tls()` or `start_ssl()`
|
||||||
@ -67,7 +67,7 @@ fn main() {
|
|||||||
|
|
||||||
let addr = rx.recv().unwrap();
|
let addr = rx.recv().unwrap();
|
||||||
let _ = addr.call_fut(
|
let _ = addr.call_fut(
|
||||||
dev::StopServer{graceful:true}).wait(); // <- Send `StopServer` message to server.
|
server::StopServer{graceful:true}).wait(); // <- Send `StopServer` message to server.
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -235,3 +235,12 @@ fn main() {
|
|||||||
```
|
```
|
||||||
|
|
||||||
Both methods could be combined. (i.e Async response with streaming body)
|
Both methods could be combined. (i.e Async response with streaming body)
|
||||||
|
|
||||||
|
## Tokio core handle
|
||||||
|
|
||||||
|
Any actix web handler runs within properly configured
|
||||||
|
[actix system](https://actix.github.io/actix/actix/struct.System.html)
|
||||||
|
and [arbiter](https://actix.github.io/actix/actix/struct.Arbiter.html).
|
||||||
|
You can always get access to tokio handle via
|
||||||
|
[Arbiter::handle()](https://actix.github.io/actix/actix/struct.Arbiter.html#method.handle)
|
||||||
|
method.
|
||||||
|
@ -5,7 +5,7 @@ and [`ResponseError` trait](../actix_web/error/trait.ResponseError.html)
|
|||||||
for handling handler's errors.
|
for handling handler's errors.
|
||||||
Any error that implements `ResponseError` trait can be returned as error value.
|
Any error that implements `ResponseError` trait can be returned as error value.
|
||||||
*Handler* can return *Result* object, actix by default provides
|
*Handler* can return *Result* object, actix by default provides
|
||||||
`Responder` implemenation for compatible result object. Here is implementation
|
`Responder` implementation for compatible result object. Here is implementation
|
||||||
definition:
|
definition:
|
||||||
|
|
||||||
```rust,ignore
|
```rust,ignore
|
||||||
@ -134,3 +134,18 @@ fn index(req: HttpRequest) -> Result<&'static str> {
|
|||||||
```
|
```
|
||||||
|
|
||||||
In this example *BAD REQUEST* response get generated for `MyError` error.
|
In this example *BAD REQUEST* response get generated for `MyError` error.
|
||||||
|
|
||||||
|
## Error logging
|
||||||
|
|
||||||
|
Actix logs all errors with `WARN` log level. If log level set to `DEBUG`
|
||||||
|
and `RUST_BACKTRACE` is enabled, backtrace get logged. The Error type uses
|
||||||
|
cause's error backtrace if available, if the underlying failure does not provide
|
||||||
|
a backtrace, a new backtrace is constructed pointing to that conversion point
|
||||||
|
(rather than the origin of the error). This construction only happens if there
|
||||||
|
is no underlying backtrace; if it does have a backtrace no new backtrace is constructed.
|
||||||
|
|
||||||
|
You can enable backtrace and debug logging with following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
>> RUST_BACKTRACE=1 RUST_LOG=actix_web=debug cargo run
|
||||||
|
```
|
||||||
|
@ -2,15 +2,15 @@
|
|||||||
|
|
||||||
URL dispatch provides a simple way to map URLs to `Handler` code using a simple pattern matching
|
URL dispatch provides a simple way to map URLs to `Handler` code using a simple pattern matching
|
||||||
language. *Regex* crate and it's
|
language. *Regex* crate and it's
|
||||||
[*RegexSet*](https://doc.rust-lang.org/regex/regex/struct.RegexSet.html) is beeing used for
|
[*RegexSet*](https://doc.rust-lang.org/regex/regex/struct.RegexSet.html) is being used for
|
||||||
pattern matching. If one of the patterns matches the path information associated with a request,
|
pattern matching. If one of the patterns matches the path information associated with a request,
|
||||||
a particular handler object is invoked. A handler is a specific object that implements
|
a particular handler object is invoked. A handler is a specific object that implements
|
||||||
`Handler` trait, defined in your application, that receives the request and returns
|
`Handler` trait, defined in your application, that receives the request and returns
|
||||||
a response object. More informatin is available in [handler section](../qs_4.html).
|
a response object. More information is available in [handler section](../qs_4.html).
|
||||||
|
|
||||||
## Resource configuration
|
## Resource configuration
|
||||||
|
|
||||||
Resource configuraiton is the act of adding a new resource to an application.
|
Resource configuration is the act of adding a new resource to an application.
|
||||||
A resource has a name, which acts as an identifier to be used for URL generation.
|
A resource has a name, which acts as an identifier to be used for URL generation.
|
||||||
The name also allows developers to add routes to existing resources.
|
The name also allows developers to add routes to existing resources.
|
||||||
A resource also has a pattern, meant to match against the *PATH* portion of a *URL*,
|
A resource also has a pattern, meant to match against the *PATH* portion of a *URL*,
|
||||||
@ -19,7 +19,7 @@ port, e.g., */foo/bar* in the *URL* *http://localhost:8080/foo/bar?q=value*).
|
|||||||
|
|
||||||
The [Application::resource](../actix_web/struct.Application.html#method.resource) methods
|
The [Application::resource](../actix_web/struct.Application.html#method.resource) methods
|
||||||
add a single resource to application routing table. This method accepts *path pattern*
|
add a single resource to application routing table. This method accepts *path pattern*
|
||||||
and resource configuration funnction.
|
and resource configuration function.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
# extern crate actix_web;
|
# extern crate actix_web;
|
||||||
@ -39,20 +39,20 @@ fn main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
*Configuraiton function* has following type:
|
*Configuration function* has following type:
|
||||||
|
|
||||||
```rust,ignore
|
```rust,ignore
|
||||||
FnOnce(&mut Resource<_>) -> ()
|
FnOnce(&mut Resource<_>) -> ()
|
||||||
```
|
```
|
||||||
|
|
||||||
*Configration function* can set name and register specific routes.
|
*Configuration function* can set name and register specific routes.
|
||||||
If resource does not contain any route or does not have any matching routes it
|
If resource does not contain any route or does not have any matching routes it
|
||||||
returns *NOT FOUND* http resources.
|
returns *NOT FOUND* http resources.
|
||||||
|
|
||||||
## Configuring a Route
|
## Configuring a Route
|
||||||
|
|
||||||
Resource contains set of routes. Each route in turn has set of predicates and handler.
|
Resource contains set of routes. Each route in turn has set of predicates and handler.
|
||||||
New route could be crearted with `Resource::route()` method which returns reference
|
New route could be created with `Resource::route()` method which returns reference
|
||||||
to new *Route* instance. By default *route* does not contain any predicates, so matches
|
to new *Route* instance. By default *route* does not contain any predicates, so matches
|
||||||
all requests and default handler is `HTTPNotFound`.
|
all requests and default handler is `HTTPNotFound`.
|
||||||
|
|
||||||
@ -91,17 +91,17 @@ builder-like pattern. Following configuration methods are available:
|
|||||||
any number of predicates could be registered for each route.
|
any number of predicates could be registered for each route.
|
||||||
|
|
||||||
* [*Route::f()*](../actix_web/struct.Route.html#method.f) method registers handler function
|
* [*Route::f()*](../actix_web/struct.Route.html#method.f) method registers handler function
|
||||||
for this route. Only one handler could be registered. Usually handler registeration
|
for this route. Only one handler could be registered. Usually handler registration
|
||||||
is the last config operation. Handler fanction could be function or closure and has type
|
is the last config operation. Handler function could be function or closure and has type
|
||||||
`Fn(HttpRequest<S>) -> R + 'static`
|
`Fn(HttpRequest<S>) -> R + 'static`
|
||||||
|
|
||||||
* [*Route::h()*](../actix_web/struct.Route.html#method.h) method registers handler object
|
* [*Route::h()*](../actix_web/struct.Route.html#method.h) method registers handler object
|
||||||
that implements `Handler` trait. This is similar to `f()` method, only one handler could
|
that implements `Handler` trait. This is similar to `f()` method, only one handler could
|
||||||
be registered. Handler registeration is the last config operation.
|
be registered. Handler registration is the last config operation.
|
||||||
|
|
||||||
* [*Route::a()*](../actix_web/struct.Route.html#method.a) method registers async handler
|
* [*Route::a()*](../actix_web/struct.Route.html#method.a) method registers async handler
|
||||||
function for this route. Only one handler could be registered. Handler registeration
|
function for this route. Only one handler could be registered. Handler registration
|
||||||
is the last config operation. Handler fanction could be function or closure and has type
|
is the last config operation. Handler function could be function or closure and has type
|
||||||
`Fn(HttpRequest<S>) -> Future<Item = HttpResponse, Error = Error> + 'static`
|
`Fn(HttpRequest<S>) -> Future<Item = HttpResponse, Error = Error> + 'static`
|
||||||
|
|
||||||
## Route matching
|
## Route matching
|
||||||
@ -112,7 +112,7 @@ against a URL path pattern. `path` represents the path portion of the URL that w
|
|||||||
The way that *actix* does this is very simple. When a request enters the system,
|
The way that *actix* does this is very simple. When a request enters the system,
|
||||||
for each resource configuration registration present in the system, actix checks
|
for each resource configuration registration present in the system, actix checks
|
||||||
the request's path against the pattern declared. *Regex* crate and it's
|
the request's path against the pattern declared. *Regex* crate and it's
|
||||||
[*RegexSet*](https://doc.rust-lang.org/regex/regex/struct.RegexSet.html) is beeing used for
|
[*RegexSet*](https://doc.rust-lang.org/regex/regex/struct.RegexSet.html) is being used for
|
||||||
pattern matching. If resource could not be found, *default resource* get used as matched
|
pattern matching. If resource could not be found, *default resource* get used as matched
|
||||||
resource.
|
resource.
|
||||||
|
|
||||||
@ -516,7 +516,7 @@ Predicates can have access to application's state via `HttpRequest::state()` met
|
|||||||
Also predicates can store extra information in
|
Also predicates can store extra information in
|
||||||
[requests`s extensions](../actix_web/struct.HttpRequest.html#method.extensions).
|
[requests`s extensions](../actix_web/struct.HttpRequest.html#method.extensions).
|
||||||
|
|
||||||
### Modifing predicate values
|
### Modifying predicate values
|
||||||
|
|
||||||
You can invert the meaning of any predicate value by wrapping it in a `Not` predicate.
|
You can invert the meaning of any predicate value by wrapping it in a `Not` predicate.
|
||||||
For example if you want to return "METHOD NOT ALLOWED" response for all methods
|
For example if you want to return "METHOD NOT ALLOWED" response for all methods
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
Builder-like patter is used to construct an instance of `HttpResponse`.
|
Builder-like patter is used to construct an instance of `HttpResponse`.
|
||||||
`HttpResponse` provides several method that returns `HttpResponseBuilder` instance,
|
`HttpResponse` provides several method that returns `HttpResponseBuilder` instance,
|
||||||
which is implements various convinience methods that helps build response.
|
which is implements various convenience methods that helps build response.
|
||||||
Check [documentation](../actix_web/dev/struct.HttpResponseBuilder.html)
|
Check [documentation](../actix_web/dev/struct.HttpResponseBuilder.html)
|
||||||
for type description. Methods `.body`, `.finish`, `.json` finalizes response creation and
|
for type description. Methods `.body`, `.finish`, `.json` finalizes response creation and
|
||||||
returns constructed *HttpResponse* instance. if this methods get called for the same
|
returns constructed *HttpResponse* instance. if this methods get called for the same
|
||||||
@ -91,7 +91,7 @@ fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
|||||||
# fn main() {}
|
# fn main() {}
|
||||||
```
|
```
|
||||||
|
|
||||||
Or you can manually load payload into memory and ther deserialize it.
|
Or you can manually load payload into memory and then deserialize it.
|
||||||
Here is simple example. We will deserialize *MyObj* struct. We need to load request
|
Here is simple example. We will deserialize *MyObj* struct. We need to load request
|
||||||
body first and then deserialize json into object.
|
body first and then deserialize json into object.
|
||||||
|
|
||||||
@ -200,7 +200,7 @@ fn index(req: HttpRequest) -> Box<Future<...>> {
|
|||||||
match item {
|
match item {
|
||||||
// Handle multipart Field
|
// Handle multipart Field
|
||||||
multipart::MultipartItem::Field(field) => {
|
multipart::MultipartItem::Field(field) => {
|
||||||
println!("==== FIELD ==== {:?} {:?}", field.heders(), field.content_type());
|
println!("==== FIELD ==== {:?} {:?}", field.headers(), field.content_type());
|
||||||
|
|
||||||
Either::A(
|
Either::A(
|
||||||
// Field in turn is a stream of *Bytes* objects
|
// Field in turn is a stream of *Bytes* objects
|
||||||
@ -259,7 +259,7 @@ fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
|||||||
Actix uses [*Payload*](../actix_web/payload/struct.Payload.html) object as request payload stream.
|
Actix uses [*Payload*](../actix_web/payload/struct.Payload.html) object as request payload stream.
|
||||||
*HttpRequest* provides several methods, which can be used for payload access.
|
*HttpRequest* provides several methods, which can be used for payload access.
|
||||||
At the same time *Payload* implements *Stream* trait, so it could be used with various
|
At the same time *Payload* implements *Stream* trait, so it could be used with various
|
||||||
stream combinators. Also *Payload* provides serveral convinience methods that return
|
stream combinators. Also *Payload* provides several convenience methods that return
|
||||||
future object that resolve to Bytes object.
|
future object that resolve to Bytes object.
|
||||||
|
|
||||||
* *readany()* method returns *Stream* of *Bytes* objects.
|
* *readany()* method returns *Stream* of *Bytes* objects.
|
||||||
@ -283,7 +283,7 @@ use futures::{Future, Stream};
|
|||||||
|
|
||||||
|
|
||||||
fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||||
req.payload_mut()
|
req.payload()
|
||||||
.readany()
|
.readany()
|
||||||
.from_err()
|
.from_err()
|
||||||
.fold((), |_, chunk| {
|
.fold((), |_, chunk| {
|
||||||
|
@ -3,13 +3,14 @@
|
|||||||
Actix supports WebSockets out-of-the-box. It is possible to convert request's `Payload`
|
Actix supports WebSockets out-of-the-box. It is possible to convert request's `Payload`
|
||||||
to a stream of [*ws::Message*](../actix_web/ws/enum.Message.html) with
|
to a stream of [*ws::Message*](../actix_web/ws/enum.Message.html) with
|
||||||
a [*ws::WsStream*](../actix_web/ws/struct.WsStream.html) and then use stream
|
a [*ws::WsStream*](../actix_web/ws/struct.WsStream.html) and then use stream
|
||||||
combinators to handle actual messages. But it is simplier to handle websocket communications
|
combinators to handle actual messages. But it is simpler to handle websocket communications
|
||||||
with http actor.
|
with http actor.
|
||||||
|
|
||||||
```rust
|
This is example of simple websocket echo server:
|
||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# extern crate actix;
|
||||||
|
# extern crate actix_web;
|
||||||
use actix::*;
|
use actix::*;
|
||||||
use actix_web::*;
|
use actix_web::*;
|
||||||
|
|
||||||
@ -17,18 +18,18 @@ use actix_web::*;
|
|||||||
struct Ws;
|
struct Ws;
|
||||||
|
|
||||||
impl Actor for Ws {
|
impl Actor for Ws {
|
||||||
type Context = HttpContext<Self>;
|
type Context = ws::WebsocketContext<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Define Handler for ws::Message message
|
/// Define Handler for ws::Message message
|
||||||
impl Handler<ws::Message> for Ws {
|
impl Handler<ws::Message> for Ws {
|
||||||
type Result=();
|
type Result=();
|
||||||
|
|
||||||
fn handle(&mut self, msg: ws::Message, ctx: &mut HttpContext<Self>) {
|
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
|
||||||
match msg {
|
match msg {
|
||||||
ws::Message::Ping(msg) => ws::WsWriter::pong(ctx, &msg),
|
ws::Message::Ping(msg) => ctx.pong(&msg),
|
||||||
ws::Message::Text(text) => ws::WsWriter::text(ctx, &text),
|
ws::Message::Text(text) => ctx.text(&text),
|
||||||
ws::Message::Binary(bin) => ws::WsWriter::binary(ctx, bin),
|
ws::Message::Binary(bin) => ctx.binary(bin),
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,10 +8,9 @@ use router::{Router, Pattern};
|
|||||||
use resource::Resource;
|
use resource::Resource;
|
||||||
use handler::{Handler, RouteHandler, WrapHandler};
|
use handler::{Handler, RouteHandler, WrapHandler};
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
use channel::{HttpHandler, IntoHttpHandler, HttpHandlerTask};
|
|
||||||
use pipeline::{Pipeline, PipelineHandler};
|
use pipeline::{Pipeline, PipelineHandler};
|
||||||
use middleware::Middleware;
|
use middleware::Middleware;
|
||||||
use server::ServerSettings;
|
use server::{HttpHandler, IntoHttpHandler, HttpHandlerTask, ServerSettings};
|
||||||
|
|
||||||
/// Application
|
/// Application
|
||||||
pub struct HttpApplication<S=()> {
|
pub struct HttpApplication<S=()> {
|
||||||
@ -60,9 +59,11 @@ impl<S: 'static> PipelineHandler<S> for Inner<S> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
impl<S: 'static> HttpApplication<S> {
|
impl<S: 'static> HttpApplication<S> {
|
||||||
|
#[cfg(test)]
|
||||||
pub(crate) fn run(&mut self, req: HttpRequest<S>) -> Reply {
|
pub(crate) fn run(&mut self, req: HttpRequest<S>) -> Reply {
|
||||||
self.inner.borrow_mut().handle(req)
|
self.inner.borrow_mut().handle(req)
|
||||||
}
|
}
|
||||||
|
#[cfg(test)]
|
||||||
pub(crate) fn prepare_request(&self, req: HttpRequest) -> HttpRequest<S> {
|
pub(crate) fn prepare_request(&self, req: HttpRequest) -> HttpRequest<S> {
|
||||||
req.with_state(Rc::clone(&self.state), self.router.clone())
|
req.with_state(Rc::clone(&self.state), self.router.clone())
|
||||||
}
|
}
|
||||||
@ -135,7 +136,7 @@ impl<S> Application<S> where S: 'static {
|
|||||||
/// Create application with specific state. Application can be
|
/// Create application with specific state. Application can be
|
||||||
/// configured with builder-like pattern.
|
/// configured with builder-like pattern.
|
||||||
///
|
///
|
||||||
/// State is shared with all reousrces within same application and could be
|
/// State is shared with all resources within same application and could be
|
||||||
/// accessed with `HttpRequest::state()` method.
|
/// accessed with `HttpRequest::state()` method.
|
||||||
pub fn with_state(state: S) -> Application<S> {
|
pub fn with_state(state: S) -> Application<S> {
|
||||||
Application {
|
Application {
|
||||||
@ -155,7 +156,7 @@ impl<S> Application<S> where S: 'static {
|
|||||||
/// Set application prefix
|
/// Set application prefix
|
||||||
///
|
///
|
||||||
/// Only requests that matches application's prefix get processed by this application.
|
/// Only requests that matches application's prefix get processed by this application.
|
||||||
/// Application prefix always contains laading "/" slash. If supplied prefix
|
/// Application prefix always contains leading "/" slash. If supplied prefix
|
||||||
/// does not contain leading slash, it get inserted. Prefix should
|
/// does not contain leading slash, it get inserted. Prefix should
|
||||||
/// consists valid path segments. i.e for application with
|
/// consists valid path segments. i.e for application with
|
||||||
/// prefix `/app` any request with following paths `/app`, `/app/` or `/app/test`
|
/// prefix `/app` any request with following paths `/app`, `/app/` or `/app/test`
|
||||||
@ -321,9 +322,7 @@ impl<S> Application<S> where S: 'static {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Register a middleware
|
/// Register a middleware
|
||||||
pub fn middleware<T>(mut self, mw: T) -> Application<S>
|
pub fn middleware<M: Middleware<S>>(mut self, mw: M) -> Application<S> {
|
||||||
where T: Middleware<S> + 'static
|
|
||||||
{
|
|
||||||
self.parts.as_mut().expect("Use after finish")
|
self.parts.as_mut().expect("Use after finish")
|
||||||
.middlewares.push(Box::new(mw));
|
.middlewares.push(Box::new(mw));
|
||||||
self
|
self
|
||||||
@ -359,6 +358,40 @@ impl<S> Application<S> where S: 'static {
|
|||||||
middlewares: Rc::new(parts.middlewares),
|
middlewares: Rc::new(parts.middlewares),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convenience method for creating `Box<HttpHandler>` instance.
|
||||||
|
///
|
||||||
|
/// This method is useful if you need to register several application instances
|
||||||
|
/// with different state.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use std::thread;
|
||||||
|
/// # extern crate actix_web;
|
||||||
|
/// use actix_web::*;
|
||||||
|
///
|
||||||
|
/// struct State1;
|
||||||
|
///
|
||||||
|
/// struct State2;
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// # thread::spawn(|| {
|
||||||
|
/// HttpServer::new(|| { vec![
|
||||||
|
/// Application::with_state(State1)
|
||||||
|
/// .prefix("/app1")
|
||||||
|
/// .resource("/", |r| r.h(httpcodes::HTTPOk))
|
||||||
|
/// .boxed(),
|
||||||
|
/// Application::with_state(State2)
|
||||||
|
/// .prefix("/app2")
|
||||||
|
/// .resource("/", |r| r.h(httpcodes::HTTPOk))
|
||||||
|
/// .boxed() ]})
|
||||||
|
/// .bind("127.0.0.1:8080").unwrap()
|
||||||
|
/// .run()
|
||||||
|
/// # });
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn boxed(mut self) -> Box<HttpHandler> {
|
||||||
|
Box::new(self.finish())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: 'static> IntoHttpHandler for Application<S> {
|
impl<S: 'static> IntoHttpHandler for Application<S> {
|
||||||
|
92
src/body.rs
92
src/body.rs
@ -1,4 +1,4 @@
|
|||||||
use std::fmt;
|
use std::{fmt, mem};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
@ -31,13 +31,8 @@ pub enum Binary {
|
|||||||
Bytes(Bytes),
|
Bytes(Bytes),
|
||||||
/// Static slice
|
/// Static slice
|
||||||
Slice(&'static [u8]),
|
Slice(&'static [u8]),
|
||||||
/// Shared bytes body
|
/// Shared string body
|
||||||
SharedBytes(Rc<Bytes>),
|
|
||||||
/// Shared stirng body
|
|
||||||
SharedString(Rc<String>),
|
SharedString(Rc<String>),
|
||||||
/// Shared bytes body
|
|
||||||
#[doc(hidden)]
|
|
||||||
ArcSharedBytes(Arc<Bytes>),
|
|
||||||
/// Shared string body
|
/// Shared string body
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
ArcSharedString(Arc<String>),
|
ArcSharedString(Arc<String>),
|
||||||
@ -118,8 +113,6 @@ impl Binary {
|
|||||||
match *self {
|
match *self {
|
||||||
Binary::Bytes(ref bytes) => bytes.len(),
|
Binary::Bytes(ref bytes) => bytes.len(),
|
||||||
Binary::Slice(slice) => slice.len(),
|
Binary::Slice(slice) => slice.len(),
|
||||||
Binary::SharedBytes(ref bytes) => bytes.len(),
|
|
||||||
Binary::ArcSharedBytes(ref bytes) => bytes.len(),
|
|
||||||
Binary::SharedString(ref s) => s.len(),
|
Binary::SharedString(ref s) => s.len(),
|
||||||
Binary::ArcSharedString(ref s) => s.len(),
|
Binary::ArcSharedString(ref s) => s.len(),
|
||||||
}
|
}
|
||||||
@ -129,6 +122,33 @@ impl Binary {
|
|||||||
pub fn from_slice(s: &[u8]) -> Binary {
|
pub fn from_slice(s: &[u8]) -> Binary {
|
||||||
Binary::Bytes(Bytes::from(s))
|
Binary::Bytes(Bytes::from(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert Binary to a Bytes instance
|
||||||
|
pub fn take(&mut self) -> Bytes {
|
||||||
|
mem::replace(self, Binary::Slice(b"")).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for Binary {
|
||||||
|
fn clone(&self) -> Binary {
|
||||||
|
match *self {
|
||||||
|
Binary::Bytes(ref bytes) => Binary::Bytes(bytes.clone()),
|
||||||
|
Binary::Slice(slice) => Binary::Bytes(Bytes::from(slice)),
|
||||||
|
Binary::SharedString(ref s) => Binary::Bytes(Bytes::from(s.as_str())),
|
||||||
|
Binary::ArcSharedString(ref s) => Binary::Bytes(Bytes::from(s.as_str())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<Bytes> for Binary {
|
||||||
|
fn into(self) -> Bytes {
|
||||||
|
match self {
|
||||||
|
Binary::Bytes(bytes) => bytes,
|
||||||
|
Binary::Slice(slice) => Bytes::from(slice),
|
||||||
|
Binary::SharedString(s) => Bytes::from(s.as_str()),
|
||||||
|
Binary::ArcSharedString(s) => Bytes::from(s.as_str()),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&'static str> for Binary {
|
impl From<&'static str> for Binary {
|
||||||
@ -173,30 +193,6 @@ impl From<BytesMut> for Binary {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Rc<Bytes>> for Binary {
|
|
||||||
fn from(body: Rc<Bytes>) -> Binary {
|
|
||||||
Binary::SharedBytes(body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<&'a Rc<Bytes>> for Binary {
|
|
||||||
fn from(body: &'a Rc<Bytes>) -> Binary {
|
|
||||||
Binary::SharedBytes(Rc::clone(body))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Arc<Bytes>> for Binary {
|
|
||||||
fn from(body: Arc<Bytes>) -> Binary {
|
|
||||||
Binary::ArcSharedBytes(body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<&'a Arc<Bytes>> for Binary {
|
|
||||||
fn from(body: &'a Arc<Bytes>) -> Binary {
|
|
||||||
Binary::ArcSharedBytes(Arc::clone(body))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Rc<String>> for Binary {
|
impl From<Rc<String>> for Binary {
|
||||||
fn from(body: Rc<String>) -> Binary {
|
fn from(body: Rc<String>) -> Binary {
|
||||||
Binary::SharedString(body)
|
Binary::SharedString(body)
|
||||||
@ -226,8 +222,6 @@ impl AsRef<[u8]> for Binary {
|
|||||||
match *self {
|
match *self {
|
||||||
Binary::Bytes(ref bytes) => bytes.as_ref(),
|
Binary::Bytes(ref bytes) => bytes.as_ref(),
|
||||||
Binary::Slice(slice) => slice,
|
Binary::Slice(slice) => slice,
|
||||||
Binary::SharedBytes(ref bytes) => bytes.as_ref(),
|
|
||||||
Binary::ArcSharedBytes(ref bytes) => bytes.as_ref(),
|
|
||||||
Binary::SharedString(ref s) => s.as_bytes(),
|
Binary::SharedString(ref s) => s.as_bytes(),
|
||||||
Binary::ArcSharedString(ref s) => s.as_bytes(),
|
Binary::ArcSharedString(ref s) => s.as_bytes(),
|
||||||
}
|
}
|
||||||
@ -242,7 +236,6 @@ mod tests {
|
|||||||
fn test_body_is_streaming() {
|
fn test_body_is_streaming() {
|
||||||
assert_eq!(Body::Empty.is_streaming(), false);
|
assert_eq!(Body::Empty.is_streaming(), false);
|
||||||
assert_eq!(Body::Binary(Binary::from("")).is_streaming(), false);
|
assert_eq!(Body::Binary(Binary::from("")).is_streaming(), false);
|
||||||
// assert_eq!(Body::Streaming.is_streaming(), true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -277,15 +270,6 @@ mod tests {
|
|||||||
assert_eq!(Binary::from(Bytes::from("test")).as_ref(), "test".as_bytes());
|
assert_eq!(Binary::from(Bytes::from("test")).as_ref(), "test".as_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_rc_bytes() {
|
|
||||||
let b = Rc::new(Bytes::from("test"));
|
|
||||||
assert_eq!(Binary::from(b.clone()).len(), 4);
|
|
||||||
assert_eq!(Binary::from(b.clone()).as_ref(), "test".as_bytes());
|
|
||||||
assert_eq!(Binary::from(&b).len(), 4);
|
|
||||||
assert_eq!(Binary::from(&b).as_ref(), "test".as_bytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_ref_string() {
|
fn test_ref_string() {
|
||||||
let b = Rc::new("test".to_owned());
|
let b = Rc::new("test".to_owned());
|
||||||
@ -302,15 +286,6 @@ mod tests {
|
|||||||
assert_eq!(Binary::from(&b).as_ref(), "test".as_bytes());
|
assert_eq!(Binary::from(&b).as_ref(), "test".as_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_arc_bytes() {
|
|
||||||
let b = Arc::new(Bytes::from("test"));
|
|
||||||
assert_eq!(Binary::from(b.clone()).len(), 4);
|
|
||||||
assert_eq!(Binary::from(b.clone()).as_ref(), "test".as_bytes());
|
|
||||||
assert_eq!(Binary::from(&b).len(), 4);
|
|
||||||
assert_eq!(Binary::from(&b).as_ref(), "test".as_bytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_arc_string() {
|
fn test_arc_string() {
|
||||||
let b = Arc::new("test".to_owned());
|
let b = Arc::new("test".to_owned());
|
||||||
@ -335,4 +310,13 @@ mod tests {
|
|||||||
assert_eq!(Binary::from(b.clone()).len(), 4);
|
assert_eq!(Binary::from(b.clone()).len(), 4);
|
||||||
assert_eq!(Binary::from(b).as_ref(), "test".as_bytes());
|
assert_eq!(Binary::from(b).as_ref(), "test".as_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_binary_into() {
|
||||||
|
let bytes = Bytes::from_static(b"test");
|
||||||
|
let b: Bytes = Binary::from("test").into();
|
||||||
|
assert_eq!(b, bytes);
|
||||||
|
let b: Bytes = Binary::from(bytes.clone()).into();
|
||||||
|
assert_eq!(b, bytes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
114
src/context.rs
114
src/context.rs
@ -1,15 +1,15 @@
|
|||||||
use std;
|
use std;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::collections::VecDeque;
|
|
||||||
use futures::{Async, Future, Poll};
|
use futures::{Async, Future, Poll};
|
||||||
use futures::sync::oneshot::Sender;
|
use futures::sync::oneshot::Sender;
|
||||||
use futures::unsync::oneshot;
|
use futures::unsync::oneshot;
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use actix::{Actor, ActorState, ActorContext, AsyncContext,
|
use actix::{Actor, ActorState, ActorContext, AsyncContext,
|
||||||
Handler, Subscriber, ResponseType, SpawnHandle};
|
Address, SyncAddress, Handler, Subscriber, ResponseType, SpawnHandle};
|
||||||
use actix::fut::ActorFuture;
|
use actix::fut::ActorFuture;
|
||||||
use actix::dev::{AsyncContextApi, ActorAddressCell,
|
use actix::dev::{queue, AsyncContextApi,
|
||||||
ContextImpl, Envelope, ToEnvelope, RemoteEnvelope};
|
ContextImpl, ContextProtocol, Envelope, ToEnvelope, RemoteEnvelope};
|
||||||
|
|
||||||
use body::{Body, Binary};
|
use body::{Body, Binary};
|
||||||
use error::{Error, Result, ErrorInternalServerError};
|
use error::{Error, Result, ErrorInternalServerError};
|
||||||
@ -18,38 +18,41 @@ use httprequest::HttpRequest;
|
|||||||
|
|
||||||
pub trait ActorHttpContext: 'static {
|
pub trait ActorHttpContext: 'static {
|
||||||
fn disconnected(&mut self);
|
fn disconnected(&mut self);
|
||||||
fn poll(&mut self) -> Poll<Option<Frame>, Error>;
|
fn poll(&mut self) -> Poll<Option<SmallVec<[Frame; 4]>>, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Frame {
|
pub enum Frame {
|
||||||
Payload(Option<Binary>),
|
Chunk(Option<Binary>),
|
||||||
Drain(oneshot::Sender<()>),
|
Drain(oneshot::Sender<()>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Frame {
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
match *self {
|
||||||
|
Frame::Chunk(Some(ref bin)) => bin.len(),
|
||||||
|
_ => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Http actor execution context
|
/// Http actor execution context
|
||||||
pub struct HttpContext<A, S=()> where A: Actor<Context=HttpContext<A, S>>,
|
pub struct HttpContext<A, S=()> where A: Actor<Context=HttpContext<A, S>>,
|
||||||
{
|
{
|
||||||
inner: ContextImpl<A>,
|
inner: ContextImpl<A>,
|
||||||
stream: VecDeque<Frame>,
|
stream: Option<SmallVec<[Frame; 4]>>,
|
||||||
request: HttpRequest<S>,
|
request: HttpRequest<S>,
|
||||||
disconnected: bool,
|
disconnected: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A, S> ActorContext for HttpContext<A, S> where A: Actor<Context=Self>
|
impl<A, S> ActorContext for HttpContext<A, S> where A: Actor<Context=Self>
|
||||||
{
|
{
|
||||||
/// Stop actor execution
|
|
||||||
fn stop(&mut self) {
|
fn stop(&mut self) {
|
||||||
self.stream.push_back(Frame::Payload(None));
|
|
||||||
self.inner.stop();
|
self.inner.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Terminate actor execution
|
|
||||||
fn terminate(&mut self) {
|
fn terminate(&mut self) {
|
||||||
self.inner.terminate()
|
self.inner.terminate()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Actor execution state
|
|
||||||
fn state(&self) -> ActorState {
|
fn state(&self) -> ActorState {
|
||||||
self.inner.state()
|
self.inner.state()
|
||||||
}
|
}
|
||||||
@ -57,18 +60,25 @@ impl<A, S> ActorContext for HttpContext<A, S> where A: Actor<Context=Self>
|
|||||||
|
|
||||||
impl<A, S> AsyncContext<A> for HttpContext<A, S> where A: Actor<Context=Self>
|
impl<A, S> AsyncContext<A> for HttpContext<A, S> where A: Actor<Context=Self>
|
||||||
{
|
{
|
||||||
|
#[inline]
|
||||||
fn spawn<F>(&mut self, fut: F) -> SpawnHandle
|
fn spawn<F>(&mut self, fut: F) -> SpawnHandle
|
||||||
where F: ActorFuture<Item=(), Error=(), Actor=A> + 'static
|
where F: ActorFuture<Item=(), Error=(), Actor=A> + 'static
|
||||||
{
|
{
|
||||||
self.inner.spawn(fut)
|
self.inner.spawn(fut)
|
||||||
}
|
}
|
||||||
|
#[inline]
|
||||||
fn wait<F>(&mut self, fut: F)
|
fn wait<F>(&mut self, fut: F)
|
||||||
where F: ActorFuture<Item=(), Error=(), Actor=A> + 'static
|
where F: ActorFuture<Item=(), Error=(), Actor=A> + 'static
|
||||||
{
|
{
|
||||||
self.inner.wait(fut)
|
self.inner.wait(fut)
|
||||||
}
|
}
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[inline]
|
||||||
|
fn waiting(&self) -> bool {
|
||||||
|
self.inner.waiting() || self.inner.state() == ActorState::Stopping ||
|
||||||
|
self.inner.state() == ActorState::Stopped
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
fn cancel_future(&mut self, handle: SpawnHandle) -> bool {
|
fn cancel_future(&mut self, handle: SpawnHandle) -> bool {
|
||||||
self.inner.cancel_future(handle)
|
self.inner.cancel_future(handle)
|
||||||
}
|
}
|
||||||
@ -76,26 +86,35 @@ impl<A, S> AsyncContext<A> for HttpContext<A, S> where A: Actor<Context=Self>
|
|||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
impl<A, S> AsyncContextApi<A> for HttpContext<A, S> where A: Actor<Context=Self> {
|
impl<A, S> AsyncContextApi<A> for HttpContext<A, S> where A: Actor<Context=Self> {
|
||||||
fn address_cell(&mut self) -> &mut ActorAddressCell<A> {
|
#[inline]
|
||||||
self.inner.address_cell()
|
fn unsync_sender(&mut self) -> queue::unsync::UnboundedSender<ContextProtocol<A>> {
|
||||||
|
self.inner.unsync_sender()
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn unsync_address(&mut self) -> Address<A> {
|
||||||
|
self.inner.unsync_address()
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn sync_address(&mut self) -> SyncAddress<A> {
|
||||||
|
self.inner.sync_address()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A, S: 'static> HttpContext<A, S> where A: Actor<Context=Self> {
|
impl<A, S: 'static> HttpContext<A, S> where A: Actor<Context=Self> {
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn new(req: HttpRequest<S>, actor: A) -> HttpContext<A, S> {
|
pub fn new(req: HttpRequest<S>, actor: A) -> HttpContext<A, S> {
|
||||||
HttpContext::from_request(req).actor(actor)
|
HttpContext::from_request(req).actor(actor)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_request(req: HttpRequest<S>) -> HttpContext<A, S> {
|
pub fn from_request(req: HttpRequest<S>) -> HttpContext<A, S> {
|
||||||
HttpContext {
|
HttpContext {
|
||||||
inner: ContextImpl::new(None),
|
inner: ContextImpl::new(None),
|
||||||
stream: VecDeque::new(),
|
stream: None,
|
||||||
request: req,
|
request: req,
|
||||||
disconnected: false,
|
disconnected: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[inline]
|
||||||
pub fn actor(mut self, actor: A) -> HttpContext<A, S> {
|
pub fn actor(mut self, actor: A) -> HttpContext<A, S> {
|
||||||
self.inner.set_actor(actor);
|
self.inner.set_actor(actor);
|
||||||
self
|
self
|
||||||
@ -105,45 +124,59 @@ impl<A, S: 'static> HttpContext<A, S> where A: Actor<Context=Self> {
|
|||||||
impl<A, S> HttpContext<A, S> where A: Actor<Context=Self> {
|
impl<A, S> HttpContext<A, S> where A: Actor<Context=Self> {
|
||||||
|
|
||||||
/// Shared application state
|
/// Shared application state
|
||||||
|
#[inline]
|
||||||
pub fn state(&self) -> &S {
|
pub fn state(&self) -> &S {
|
||||||
self.request.state()
|
self.request.state()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Incoming request
|
/// Incoming request
|
||||||
|
#[inline]
|
||||||
pub fn request(&mut self) -> &mut HttpRequest<S> {
|
pub fn request(&mut self) -> &mut HttpRequest<S> {
|
||||||
&mut self.request
|
&mut self.request
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write payload
|
/// Write payload
|
||||||
|
#[inline]
|
||||||
pub fn write<B: Into<Binary>>(&mut self, data: B) {
|
pub fn write<B: Into<Binary>>(&mut self, data: B) {
|
||||||
if !self.disconnected {
|
if !self.disconnected {
|
||||||
self.stream.push_back(Frame::Payload(Some(data.into())));
|
self.add_frame(Frame::Chunk(Some(data.into())));
|
||||||
} else {
|
} else {
|
||||||
warn!("Trying to write to disconnected response");
|
warn!("Trying to write to disconnected response");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Indicate end of streamimng payload. Also this method calls `Self::close`.
|
/// Indicate end of streaming payload. Also this method calls `Self::close`.
|
||||||
|
#[inline]
|
||||||
pub fn write_eof(&mut self) {
|
pub fn write_eof(&mut self) {
|
||||||
self.stop();
|
self.add_frame(Frame::Chunk(None));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns drain future
|
/// Returns drain future
|
||||||
pub fn drain(&mut self) -> Drain<A> {
|
pub fn drain(&mut self) -> Drain<A> {
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
self.inner.modify();
|
self.inner.modify();
|
||||||
self.stream.push_back(Frame::Drain(tx));
|
self.add_frame(Frame::Drain(tx));
|
||||||
Drain::new(rx)
|
Drain::new(rx)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if connection still open
|
/// Check if connection still open
|
||||||
|
#[inline]
|
||||||
pub fn connected(&self) -> bool {
|
pub fn connected(&self) -> bool {
|
||||||
!self.disconnected
|
!self.disconnected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn add_frame(&mut self, frame: Frame) {
|
||||||
|
if self.stream.is_none() {
|
||||||
|
self.stream = Some(SmallVec::new());
|
||||||
|
}
|
||||||
|
self.stream.as_mut().map(|s| s.push(frame));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A, S> HttpContext<A, S> where A: Actor<Context=Self> {
|
impl<A, S> HttpContext<A, S> where A: Actor<Context=Self> {
|
||||||
|
|
||||||
|
#[inline]
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn subscriber<M>(&mut self) -> Box<Subscriber<M>>
|
pub fn subscriber<M>(&mut self) -> Box<Subscriber<M>>
|
||||||
where A: Handler<M>, M: ResponseType + 'static
|
where A: Handler<M>, M: ResponseType + 'static
|
||||||
@ -151,6 +184,7 @@ impl<A, S> HttpContext<A, S> where A: Actor<Context=Self> {
|
|||||||
self.inner.subscriber()
|
self.inner.subscriber()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn sync_subscriber<M>(&mut self) -> Box<Subscriber<M> + Send>
|
pub fn sync_subscriber<M>(&mut self) -> Box<Subscriber<M> + Send>
|
||||||
where A: Handler<M>,
|
where A: Handler<M>,
|
||||||
@ -162,27 +196,31 @@ impl<A, S> HttpContext<A, S> where A: Actor<Context=Self> {
|
|||||||
|
|
||||||
impl<A, S> ActorHttpContext for HttpContext<A, S> where A: Actor<Context=Self>, S: 'static {
|
impl<A, S> ActorHttpContext for HttpContext<A, S> where A: Actor<Context=Self>, S: 'static {
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn disconnected(&mut self) {
|
fn disconnected(&mut self) {
|
||||||
self.disconnected = true;
|
self.disconnected = true;
|
||||||
self.stop();
|
self.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll(&mut self) -> Poll<Option<Frame>, Error> {
|
fn poll(&mut self) -> Poll<Option<SmallVec<[Frame; 4]>>, Error> {
|
||||||
let ctx: &mut HttpContext<A, S> = unsafe {
|
let ctx: &mut HttpContext<A, S> = unsafe {
|
||||||
std::mem::transmute(self as &mut HttpContext<A, S>)
|
std::mem::transmute(self as &mut HttpContext<A, S>)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if self.inner.alive() {
|
||||||
match self.inner.poll(ctx) {
|
match self.inner.poll(ctx) {
|
||||||
Ok(Async::NotReady) => {
|
Ok(Async::NotReady) | Ok(Async::Ready(())) => (),
|
||||||
// get frame
|
Err(_) => return Err(ErrorInternalServerError("error").into()),
|
||||||
if let Some(frame) = self.stream.pop_front() {
|
}
|
||||||
Ok(Async::Ready(Some(frame)))
|
}
|
||||||
} else {
|
|
||||||
|
// frames
|
||||||
|
if let Some(data) = self.stream.take() {
|
||||||
|
Ok(Async::Ready(Some(data)))
|
||||||
|
} else if self.inner.alive() {
|
||||||
Ok(Async::NotReady)
|
Ok(Async::NotReady)
|
||||||
}
|
} else {
|
||||||
}
|
Ok(Async::Ready(None))
|
||||||
Ok(Async::Ready(())) => Ok(Async::Ready(None)),
|
|
||||||
Err(_) => Err(ErrorInternalServerError("error").into()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -190,12 +228,11 @@ impl<A, S> ActorHttpContext for HttpContext<A, S> where A: Actor<Context=Self>,
|
|||||||
impl<A, S> ToEnvelope<A> for HttpContext<A, S>
|
impl<A, S> ToEnvelope<A> for HttpContext<A, S>
|
||||||
where A: Actor<Context=HttpContext<A, S>>,
|
where A: Actor<Context=HttpContext<A, S>>,
|
||||||
{
|
{
|
||||||
|
#[inline]
|
||||||
fn pack<M>(msg: M, tx: Option<Sender<Result<M::Item, M::Error>>>,
|
fn pack<M>(msg: M, tx: Option<Sender<Result<M::Item, M::Error>>>,
|
||||||
channel_on_drop: bool) -> Envelope<A>
|
channel_on_drop: bool) -> Envelope<A>
|
||||||
where A: Handler<M>,
|
where A: Handler<M>,
|
||||||
M: ResponseType + Send + 'static,
|
M: ResponseType + Send + 'static, M::Item: Send, M::Error: Send
|
||||||
M::Item: Send,
|
|
||||||
M::Error: Send
|
|
||||||
{
|
{
|
||||||
RemoteEnvelope::new(msg, tx, channel_on_drop).into()
|
RemoteEnvelope::new(msg, tx, channel_on_drop).into()
|
||||||
}
|
}
|
||||||
@ -216,7 +253,7 @@ pub struct Drain<A> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<A> Drain<A> {
|
impl<A> Drain<A> {
|
||||||
fn new(fut: oneshot::Receiver<()>) -> Self {
|
pub fn new(fut: oneshot::Receiver<()>) -> Self {
|
||||||
Drain {
|
Drain {
|
||||||
fut: fut,
|
fut: fut,
|
||||||
_a: PhantomData
|
_a: PhantomData
|
||||||
@ -229,6 +266,7 @@ impl<A: Actor> ActorFuture for Drain<A> {
|
|||||||
type Error = ();
|
type Error = ();
|
||||||
type Actor = A;
|
type Actor = A;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn poll(&mut self,
|
fn poll(&mut self,
|
||||||
_: &mut A,
|
_: &mut A,
|
||||||
_: &mut <Self::Actor as Actor>::Context) -> Poll<Self::Item, Self::Error>
|
_: &mut <Self::Actor as Actor>::Context) -> Poll<Self::Item, Self::Error>
|
||||||
|
220
src/error.rs
220
src/error.rs
@ -9,8 +9,8 @@ use std::error::Error as StdError;
|
|||||||
|
|
||||||
use cookie;
|
use cookie;
|
||||||
use httparse;
|
use httparse;
|
||||||
use failure::Fail;
|
|
||||||
use futures::Canceled;
|
use futures::Canceled;
|
||||||
|
use failure::{Fail, Backtrace};
|
||||||
use http2::Error as Http2Error;
|
use http2::Error as Http2Error;
|
||||||
use http::{header, StatusCode, Error as HttpError};
|
use http::{header, StatusCode, Error as HttpError};
|
||||||
use http::uri::InvalidUriBytes;
|
use http::uri::InvalidUriBytes;
|
||||||
@ -22,6 +22,8 @@ use url::ParseError as UrlParseError;
|
|||||||
pub use cookie::{ParseError as CookieParseError};
|
pub use cookie::{ParseError as CookieParseError};
|
||||||
|
|
||||||
use body::Body;
|
use body::Body;
|
||||||
|
use handler::Responder;
|
||||||
|
use httprequest::HttpRequest;
|
||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
use httpcodes::{HTTPBadRequest, HTTPMethodNotAllowed, HTTPExpectationFailed};
|
use httpcodes::{HTTPBadRequest, HTTPMethodNotAllowed, HTTPExpectationFailed};
|
||||||
|
|
||||||
@ -33,9 +35,9 @@ use httpcodes::{HTTPBadRequest, HTTPMethodNotAllowed, HTTPExpectationFailed};
|
|||||||
pub type Result<T, E=Error> = result::Result<T, E>;
|
pub type Result<T, E=Error> = result::Result<T, E>;
|
||||||
|
|
||||||
/// General purpose actix web error
|
/// General purpose actix web error
|
||||||
#[derive(Fail, Debug)]
|
|
||||||
pub struct Error {
|
pub struct Error {
|
||||||
cause: Box<ResponseError>,
|
cause: Box<ResponseError>,
|
||||||
|
backtrace: Option<Backtrace>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
@ -64,6 +66,16 @@ impl fmt::Display for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
if let Some(bt) = self.cause.backtrace() {
|
||||||
|
write!(f, "{:?}\n\n{:?}", &self.cause, bt)
|
||||||
|
} else {
|
||||||
|
write!(f, "{:?}\n\n{:?}", &self.cause, self.backtrace.as_ref().unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// `HttpResponse` for `Error`
|
/// `HttpResponse` for `Error`
|
||||||
impl From<Error> for HttpResponse {
|
impl From<Error> for HttpResponse {
|
||||||
fn from(err: Error) -> Self {
|
fn from(err: Error) -> Self {
|
||||||
@ -74,7 +86,12 @@ impl From<Error> for HttpResponse {
|
|||||||
/// `Error` for any error that implements `ResponseError`
|
/// `Error` for any error that implements `ResponseError`
|
||||||
impl<T: ResponseError> From<T> for Error {
|
impl<T: ResponseError> From<T> for Error {
|
||||||
fn from(err: T) -> Error {
|
fn from(err: T) -> Error {
|
||||||
Error { cause: Box::new(err) }
|
let backtrace = if err.backtrace().is_none() {
|
||||||
|
Some(Backtrace::new())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
Error { cause: Box::new(err), backtrace: backtrace }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -320,7 +337,7 @@ pub enum WsHandshakeError {
|
|||||||
/// Only get method is allowed
|
/// Only get method is allowed
|
||||||
#[fail(display="Method not allowed")]
|
#[fail(display="Method not allowed")]
|
||||||
GetMethodRequired,
|
GetMethodRequired,
|
||||||
/// Ugrade header if not set to websocket
|
/// Upgrade header if not set to websocket
|
||||||
#[fail(display="Websocket upgrade is expected")]
|
#[fail(display="Websocket upgrade is expected")]
|
||||||
NoWebsocketUpgrade,
|
NoWebsocketUpgrade,
|
||||||
/// Connection header is not set to upgrade
|
/// Connection header is not set to upgrade
|
||||||
@ -329,7 +346,7 @@ pub enum WsHandshakeError {
|
|||||||
/// Websocket version header is not set
|
/// Websocket version header is not set
|
||||||
#[fail(display="Websocket version header is required")]
|
#[fail(display="Websocket version header is required")]
|
||||||
NoVersionHeader,
|
NoVersionHeader,
|
||||||
/// Unsupported websockt version
|
/// Unsupported websocket version
|
||||||
#[fail(display="Unsupported version")]
|
#[fail(display="Unsupported version")]
|
||||||
UnsupportedVersion,
|
UnsupportedVersion,
|
||||||
/// Websocket key is not set or wrong
|
/// Websocket key is not set or wrong
|
||||||
@ -478,39 +495,10 @@ impl From<UrlParseError> for UrlGenerationError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! ERROR_WRAP {
|
/// Helper type that can wrap any error and generate custom response.
|
||||||
($type:ty, $status:expr) => {
|
|
||||||
unsafe impl<T> Sync for $type {}
|
|
||||||
unsafe impl<T> Send for $type {}
|
|
||||||
|
|
||||||
impl<T> $type {
|
|
||||||
pub fn cause(&self) -> &T {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: fmt::Debug + 'static> Fail for $type {}
|
|
||||||
impl<T: fmt::Debug + 'static> fmt::Display for $type {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(f, "{:?}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> ResponseError for $type
|
|
||||||
where T: Send + Sync + fmt::Debug + 'static,
|
|
||||||
{
|
|
||||||
fn error_response(&self) -> HttpResponse {
|
|
||||||
HttpResponse::new($status, Body::Empty)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper type that can wrap any error and generate *BAD REQUEST* response.
|
|
||||||
///
|
///
|
||||||
/// In following example any `io::Error` will be converted into "BAD REQUEST" response
|
/// In following example any `io::Error` will be converted into "BAD REQUEST" response
|
||||||
/// as oposite to *INNTERNAL SERVER ERROR* which is defined by default.
|
/// as opposite to *INNTERNAL SERVER ERROR* which is defined by default.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate actix_web;
|
/// # extern crate actix_web;
|
||||||
@ -523,59 +511,133 @@ macro_rules! ERROR_WRAP {
|
|||||||
/// }
|
/// }
|
||||||
/// # fn main() {}
|
/// # fn main() {}
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug)]
|
pub struct InternalError<T> {
|
||||||
pub struct ErrorBadRequest<T>(pub T);
|
cause: T,
|
||||||
ERROR_WRAP!(ErrorBadRequest<T>, StatusCode::BAD_REQUEST);
|
status: StatusCode,
|
||||||
|
backtrace: Backtrace,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
unsafe impl<T> Sync for InternalError<T> {}
|
||||||
/// Helper type that can wrap any error and generate *UNAUTHORIZED* response.
|
unsafe impl<T> Send for InternalError<T> {}
|
||||||
pub struct ErrorUnauthorized<T>(pub T);
|
|
||||||
ERROR_WRAP!(ErrorUnauthorized<T>, StatusCode::UNAUTHORIZED);
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
impl<T> InternalError<T> {
|
||||||
/// Helper type that can wrap any error and generate *FORBIDDEN* response.
|
pub fn new(err: T, status: StatusCode) -> Self {
|
||||||
pub struct ErrorForbidden<T>(pub T);
|
InternalError {
|
||||||
ERROR_WRAP!(ErrorForbidden<T>, StatusCode::FORBIDDEN);
|
cause: err,
|
||||||
|
status: status,
|
||||||
|
backtrace: Backtrace::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
impl<T> Fail for InternalError<T>
|
||||||
/// Helper type that can wrap any error and generate *NOT FOUND* response.
|
where T: Send + Sync + fmt::Debug + 'static
|
||||||
pub struct ErrorNotFound<T>(pub T);
|
{
|
||||||
ERROR_WRAP!(ErrorNotFound<T>, StatusCode::NOT_FOUND);
|
fn backtrace(&self) -> Option<&Backtrace> {
|
||||||
|
Some(&self.backtrace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
impl<T> fmt::Debug for InternalError<T>
|
||||||
/// Helper type that can wrap any error and generate *METHOD NOT ALLOWED* response.
|
where T: Send + Sync + fmt::Debug + 'static
|
||||||
pub struct ErrorMethodNotAllowed<T>(pub T);
|
{
|
||||||
ERROR_WRAP!(ErrorMethodNotAllowed<T>, StatusCode::METHOD_NOT_ALLOWED);
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
fmt::Debug::fmt(&self.cause, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
impl<T> fmt::Display for InternalError<T>
|
||||||
/// Helper type that can wrap any error and generate *REQUEST TIMEOUT* response.
|
where T: Send + Sync + fmt::Debug + 'static
|
||||||
pub struct ErrorRequestTimeout<T>(pub T);
|
{
|
||||||
ERROR_WRAP!(ErrorRequestTimeout<T>, StatusCode::REQUEST_TIMEOUT);
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
fmt::Debug::fmt(&self.cause, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
impl<T> ResponseError for InternalError<T>
|
||||||
/// Helper type that can wrap any error and generate *CONFLICT* response.
|
where T: Send + Sync + fmt::Debug + 'static
|
||||||
pub struct ErrorConflict<T>(pub T);
|
{
|
||||||
ERROR_WRAP!(ErrorConflict<T>, StatusCode::CONFLICT);
|
fn error_response(&self) -> HttpResponse {
|
||||||
|
HttpResponse::new(self.status, Body::Empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
impl<T> Responder for InternalError<T>
|
||||||
/// Helper type that can wrap any error and generate *GONE* response.
|
where T: Send + Sync + fmt::Debug + 'static
|
||||||
pub struct ErrorGone<T>(pub T);
|
{
|
||||||
ERROR_WRAP!(ErrorGone<T>, StatusCode::GONE);
|
type Item = HttpResponse;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
#[derive(Debug)]
|
fn respond_to(self, _: HttpRequest) -> Result<HttpResponse, Error> {
|
||||||
/// Helper type that can wrap any error and generate *PRECONDITION FAILED* response.
|
Err(self.into())
|
||||||
pub struct ErrorPreconditionFailed<T>(pub T);
|
}
|
||||||
ERROR_WRAP!(ErrorPreconditionFailed<T>, StatusCode::PRECONDITION_FAILED);
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
/// Helper function that creates wrapper of any error and generate *BAD REQUEST* response.
|
||||||
/// Helper type that can wrap any error and generate *EXPECTATION FAILED* response.
|
#[allow(non_snake_case)]
|
||||||
pub struct ErrorExpectationFailed<T>(pub T);
|
pub fn ErrorBadRequest<T>(err: T) -> InternalError<T> {
|
||||||
ERROR_WRAP!(ErrorExpectationFailed<T>, StatusCode::EXPECTATION_FAILED);
|
InternalError::new(err, StatusCode::BAD_REQUEST)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
/// Helper function that creates wrapper of any error and generate *UNAUTHORIZED* response.
|
||||||
/// Helper type that can wrap any error and generate *INTERNAL SERVER ERROR* response.
|
#[allow(non_snake_case)]
|
||||||
pub struct ErrorInternalServerError<T>(pub T);
|
pub fn ErrorUnauthorized<T>(err: T) -> InternalError<T> {
|
||||||
ERROR_WRAP!(ErrorInternalServerError<T>, StatusCode::INTERNAL_SERVER_ERROR);
|
InternalError::new(err, StatusCode::UNAUTHORIZED)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and generate *FORBIDDEN* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorForbidden<T>(err: T) -> InternalError<T> {
|
||||||
|
InternalError::new(err, StatusCode::FORBIDDEN)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and generate *NOT FOUND* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorNotFound<T>(err: T) -> InternalError<T> {
|
||||||
|
InternalError::new(err, StatusCode::NOT_FOUND)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and generate *METHOD NOT ALLOWED* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorMethodNotAllowed<T>(err: T) -> InternalError<T> {
|
||||||
|
InternalError::new(err, StatusCode::METHOD_NOT_ALLOWED)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and generate *REQUEST TIMEOUT* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorRequestTimeout<T>(err: T) -> InternalError<T> {
|
||||||
|
InternalError::new(err, StatusCode::REQUEST_TIMEOUT)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and generate *CONFLICT* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorConflict<T>(err: T) -> InternalError<T> {
|
||||||
|
InternalError::new(err, StatusCode::CONFLICT)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and generate *GONE* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorGone<T>(err: T) -> InternalError<T> {
|
||||||
|
InternalError::new(err, StatusCode::GONE)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and generate *PRECONDITION FAILED* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorPreconditionFailed<T>(err: T) -> InternalError<T> {
|
||||||
|
InternalError::new(err, StatusCode::PRECONDITION_FAILED)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and generate *EXPECTATION FAILED* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorExpectationFailed<T>(err: T) -> InternalError<T> {
|
||||||
|
InternalError::new(err, StatusCode::EXPECTATION_FAILED)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and generate *INTERNAL SERVER ERROR* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorInternalServerError<T>(err: T) -> InternalError<T> {
|
||||||
|
InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
@ -9,8 +9,10 @@ use std::path::{Path, PathBuf};
|
|||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
use mime_guess::get_mime_type;
|
use mime_guess::get_mime_type;
|
||||||
|
|
||||||
use param::FromParam;
|
use param::FromParam;
|
||||||
use handler::{Handler, Responder};
|
use handler::{Handler, Responder};
|
||||||
|
use headers::ContentEncoding;
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
use httpcodes::HTTPOk;
|
use httpcodes::HTTPOk;
|
||||||
@ -83,6 +85,7 @@ impl Responder for NamedFile {
|
|||||||
|
|
||||||
fn respond_to(mut self, _: HttpRequest) -> Result<HttpResponse, io::Error> {
|
fn respond_to(mut self, _: HttpRequest) -> Result<HttpResponse, io::Error> {
|
||||||
let mut resp = HTTPOk.build();
|
let mut resp = HTTPOk.build();
|
||||||
|
resp.content_encoding(ContentEncoding::Identity);
|
||||||
if let Some(ext) = self.path().extension() {
|
if let Some(ext) = self.path().extension() {
|
||||||
let mime = get_mime_type(&ext.to_string_lossy());
|
let mime = get_mime_type(&ext.to_string_lossy());
|
||||||
resp.content_type(format!("{}", mime).as_str());
|
resp.content_type(format!("{}", mime).as_str());
|
||||||
@ -136,7 +139,7 @@ impl Responder for Directory {
|
|||||||
for entry in self.path.read_dir()? {
|
for entry in self.path.read_dir()? {
|
||||||
if self.can_list(&entry) {
|
if self.can_list(&entry) {
|
||||||
let entry = entry.unwrap();
|
let entry = entry.unwrap();
|
||||||
let p = match entry.path().strip_prefix(&self.base) {
|
let p = match entry.path().strip_prefix(&self.path) {
|
||||||
Ok(p) => base.join(p),
|
Ok(p) => base.join(p),
|
||||||
Err(_) => continue
|
Err(_) => continue
|
||||||
};
|
};
|
||||||
|
@ -9,7 +9,7 @@ use error::Error;
|
|||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
|
|
||||||
/// Trait defines object that could be regestered as route handler
|
/// Trait defines object that could be registered as route handler
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
pub trait Handler<S>: 'static {
|
pub trait Handler<S>: 'static {
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ pub trait Responder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
/// Convinience trait that convert `Future` object into `Boxed` future
|
/// Convenience trait that convert `Future` object into `Boxed` future
|
||||||
pub trait AsyncResponder<I, E>: Sized {
|
pub trait AsyncResponder<I, E>: Sized {
|
||||||
fn responder(self) -> Box<Future<Item=I, Error=E>>;
|
fn responder(self) -> Box<Future<Item=I, Error=E>>;
|
||||||
}
|
}
|
||||||
@ -193,7 +193,7 @@ impl<I, E> Responder for Box<Future<Item=I, Error=E>>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait defines object that could be regestered as resource route
|
/// Trait defines object that could be registered as resource route
|
||||||
pub(crate) trait RouteHandler<S>: 'static {
|
pub(crate) trait RouteHandler<S>: 'static {
|
||||||
fn handle(&mut self, req: HttpRequest<S>) -> Reply;
|
fn handle(&mut self, req: HttpRequest<S>) -> Reply;
|
||||||
}
|
}
|
||||||
@ -341,7 +341,7 @@ impl Default for NormalizePath {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl NormalizePath {
|
impl NormalizePath {
|
||||||
/// Create new `NoramlizePath` instance
|
/// Create new `NormalizePath` instance
|
||||||
pub fn new(append: bool, merge: bool, redirect: StatusCode) -> NormalizePath {
|
pub fn new(append: bool, merge: bool, redirect: StatusCode) -> NormalizePath {
|
||||||
NormalizePath {
|
NormalizePath {
|
||||||
append: append,
|
append: append,
|
||||||
|
@ -66,84 +66,6 @@ impl fmt::Write for CachedDate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Internal use only! unsafe
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct SharedBytesPool(RefCell<VecDeque<Rc<BytesMut>>>);
|
|
||||||
|
|
||||||
impl SharedBytesPool {
|
|
||||||
pub fn new() -> SharedBytesPool {
|
|
||||||
SharedBytesPool(RefCell::new(VecDeque::with_capacity(128)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_bytes(&self) -> Rc<BytesMut> {
|
|
||||||
if let Some(bytes) = self.0.borrow_mut().pop_front() {
|
|
||||||
bytes
|
|
||||||
} else {
|
|
||||||
Rc::new(BytesMut::new())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn release_bytes(&self, mut bytes: Rc<BytesMut>) {
|
|
||||||
let v = &mut self.0.borrow_mut();
|
|
||||||
if v.len() < 128 {
|
|
||||||
Rc::get_mut(&mut bytes).unwrap().take();
|
|
||||||
v.push_front(bytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct SharedBytes(
|
|
||||||
Option<Rc<BytesMut>>, Option<Rc<SharedBytesPool>>);
|
|
||||||
|
|
||||||
impl Drop for SharedBytes {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
if let Some(ref pool) = self.1 {
|
|
||||||
if let Some(bytes) = self.0.take() {
|
|
||||||
if Rc::strong_count(&bytes) == 1 {
|
|
||||||
pool.release_bytes(bytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SharedBytes {
|
|
||||||
|
|
||||||
pub fn empty() -> Self {
|
|
||||||
SharedBytes(None, None)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new(bytes: Rc<BytesMut>, pool: Rc<SharedBytesPool>) -> SharedBytes {
|
|
||||||
SharedBytes(Some(bytes), Some(pool))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
#[allow(mutable_transmutes)]
|
|
||||||
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))]
|
|
||||||
pub fn get_mut(&self) -> &mut BytesMut {
|
|
||||||
let r: &BytesMut = self.0.as_ref().unwrap().as_ref();
|
|
||||||
unsafe{mem::transmute(r)}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn get_ref(&self) -> &BytesMut {
|
|
||||||
self.0.as_ref().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for SharedBytes {
|
|
||||||
fn default() -> Self {
|
|
||||||
SharedBytes(Some(Rc::new(BytesMut::new())), None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for SharedBytes {
|
|
||||||
fn clone(&self) -> SharedBytes {
|
|
||||||
SharedBytes(self.0.clone(), self.1.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Internal use only! unsafe
|
/// Internal use only! unsafe
|
||||||
pub(crate) struct SharedMessagePool(RefCell<VecDeque<Rc<HttpMessage>>>);
|
pub(crate) struct SharedMessagePool(RefCell<VecDeque<Rc<HttpMessage>>>);
|
||||||
|
|
||||||
|
@ -48,6 +48,7 @@ pub const HTTPInternalServerError: StaticResponse =
|
|||||||
StaticResponse(StatusCode::INTERNAL_SERVER_ERROR);
|
StaticResponse(StatusCode::INTERNAL_SERVER_ERROR);
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub struct StaticResponse(StatusCode);
|
pub struct StaticResponse(StatusCode);
|
||||||
|
|
||||||
impl StaticResponse {
|
impl StaticResponse {
|
||||||
|
@ -8,6 +8,7 @@ use cookie::Cookie;
|
|||||||
use futures::{Async, Future, Stream, Poll};
|
use futures::{Async, Future, Stream, Poll};
|
||||||
use http_range::HttpRange;
|
use http_range::HttpRange;
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
|
use mime::Mime;
|
||||||
use url::{Url, form_urlencoded};
|
use url::{Url, form_urlencoded};
|
||||||
use http::{header, Uri, Method, Version, HeaderMap, Extensions};
|
use http::{header, Uri, Method, Version, HeaderMap, Extensions};
|
||||||
|
|
||||||
@ -222,7 +223,7 @@ impl<S> HttpRequest<S> {
|
|||||||
self.uri().path()
|
self.uri().path()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get *ConnectionInfo* for currect request.
|
/// Get *ConnectionInfo* for correct request.
|
||||||
pub fn connection_info(&self) -> &ConnectionInfo {
|
pub fn connection_info(&self) -> &ConnectionInfo {
|
||||||
if self.as_ref().info.is_none() {
|
if self.as_ref().info.is_none() {
|
||||||
let info: ConnectionInfo<'static> = unsafe{
|
let info: ConnectionInfo<'static> = unsafe{
|
||||||
@ -278,7 +279,7 @@ impl<S> HttpRequest<S> {
|
|||||||
|
|
||||||
/// Peer socket address
|
/// Peer socket address
|
||||||
///
|
///
|
||||||
/// Peer address is actuall socket address, if proxy is used in front of
|
/// Peer address is actual socket address, if proxy is used in front of
|
||||||
/// actix http server, then peer address would be address of this proxy.
|
/// actix http server, then peer address would be address of this proxy.
|
||||||
///
|
///
|
||||||
/// To get client connection information `connection_info()` method should be used.
|
/// To get client connection information `connection_info()` method should be used.
|
||||||
@ -371,12 +372,25 @@ impl<S> HttpRequest<S> {
|
|||||||
pub fn content_type(&self) -> &str {
|
pub fn content_type(&self) -> &str {
|
||||||
if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) {
|
if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) {
|
||||||
if let Ok(content_type) = content_type.to_str() {
|
if let Ok(content_type) = content_type.to_str() {
|
||||||
return content_type
|
return content_type.split(';').next().unwrap().trim()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert the request content type to a known mime type.
|
||||||
|
pub fn mime_type(&self) -> Option<Mime> {
|
||||||
|
if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) {
|
||||||
|
if let Ok(content_type) = content_type.to_str() {
|
||||||
|
return match content_type.parse() {
|
||||||
|
Ok(mt) => Some(mt),
|
||||||
|
Err(_) => None
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
/// Check if request requires connection upgrade
|
/// Check if request requires connection upgrade
|
||||||
pub(crate) fn upgrade(&self) -> bool {
|
pub(crate) fn upgrade(&self) -> bool {
|
||||||
if let Some(conn) = self.as_ref().headers.get(header::CONNECTION) {
|
if let Some(conn) = self.as_ref().headers.get(header::CONNECTION) {
|
||||||
@ -754,6 +768,7 @@ impl Future for RequestBody {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use mime;
|
||||||
use http::Uri;
|
use http::Uri;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use router::Pattern;
|
use router::Pattern;
|
||||||
@ -768,6 +783,31 @@ mod tests {
|
|||||||
assert!(dbg.contains("HttpRequest"));
|
assert!(dbg.contains("HttpRequest"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_content_type() {
|
||||||
|
let req = TestRequest::with_header("content-type", "text/plain").finish();
|
||||||
|
assert_eq!(req.content_type(), "text/plain");
|
||||||
|
let req = TestRequest::with_header(
|
||||||
|
"content-type", "application/json; charset=utf=8").finish();
|
||||||
|
assert_eq!(req.content_type(), "application/json");
|
||||||
|
let req = HttpRequest::default();
|
||||||
|
assert_eq!(req.content_type(), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mime_type() {
|
||||||
|
let req = TestRequest::with_header("content-type", "application/json").finish();
|
||||||
|
assert_eq!(req.mime_type(), Some(mime::APPLICATION_JSON));
|
||||||
|
let req = HttpRequest::default();
|
||||||
|
assert_eq!(req.mime_type(), None);
|
||||||
|
let req = TestRequest::with_header(
|
||||||
|
"content-type", "application/json; charset=utf-8").finish();
|
||||||
|
let mt = req.mime_type().unwrap();
|
||||||
|
assert_eq!(mt.get_param(mime::CHARSET), Some(mime::UTF_8));
|
||||||
|
assert_eq!(mt.type_(), mime::APPLICATION);
|
||||||
|
assert_eq!(mt.subtype(), mime::JSON);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_no_request_cookies() {
|
fn test_no_request_cookies() {
|
||||||
let req = HttpRequest::default();
|
let req = HttpRequest::default();
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
//! Pieces pertaining to the HTTP response.
|
//! Pieces pertaining to the HTTP response.
|
||||||
use std::{mem, str, fmt};
|
use std::{mem, str, fmt};
|
||||||
|
use std::io::Write;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::convert::Into;
|
use std::convert::Into;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
use cookie::CookieJar;
|
use cookie::CookieJar;
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut, BufMut};
|
||||||
use http::{StatusCode, Version, HeaderMap, HttpTryFrom, Error as HttpError};
|
use http::{StatusCode, Version, HeaderMap, HttpTryFrom, Error as HttpError};
|
||||||
use http::header::{self, HeaderName, HeaderValue};
|
use http::header::{self, HeaderName, HeaderValue};
|
||||||
use serde_json;
|
use serde_json;
|
||||||
@ -15,7 +16,7 @@ use cookie::Cookie;
|
|||||||
use body::Body;
|
use body::Body;
|
||||||
use error::Error;
|
use error::Error;
|
||||||
use handler::Responder;
|
use handler::Responder;
|
||||||
use encoding::ContentEncoding;
|
use headers::ContentEncoding;
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
|
|
||||||
/// Represents various types of connection
|
/// Represents various types of connection
|
||||||
@ -157,14 +158,14 @@ impl HttpResponse {
|
|||||||
|
|
||||||
/// is chunked encoding enabled
|
/// is chunked encoding enabled
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn chunked(&self) -> bool {
|
pub fn chunked(&self) -> Option<bool> {
|
||||||
self.get_ref().chunked
|
self.get_ref().chunked
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Content encoding
|
/// Content encoding
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn content_encoding(&self) -> &ContentEncoding {
|
pub fn content_encoding(&self) -> ContentEncoding {
|
||||||
&self.get_ref().encoding
|
self.get_ref().encoding
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set content encoding
|
/// Set content encoding
|
||||||
@ -328,7 +329,16 @@ impl HttpResponseBuilder {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub fn chunked(&mut self) -> &mut Self {
|
pub fn chunked(&mut self) -> &mut Self {
|
||||||
if let Some(parts) = parts(&mut self.response, &self.err) {
|
if let Some(parts) = parts(&mut self.response, &self.err) {
|
||||||
parts.chunked = true;
|
parts.chunked = Some(true);
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Force disable chunked encoding
|
||||||
|
#[inline]
|
||||||
|
pub fn no_chunking(&mut self) -> &mut Self {
|
||||||
|
if let Some(parts) = parts(&mut self.response, &self.err) {
|
||||||
|
parts.chunked = Some(false);
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -347,6 +357,14 @@ impl HttpResponseBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set content length
|
||||||
|
#[inline]
|
||||||
|
pub fn content_length(&mut self, len: u64) -> &mut Self {
|
||||||
|
let mut wrt = BytesMut::new().writer();
|
||||||
|
let _ = write!(wrt, "{}", len);
|
||||||
|
self.header(header::CONTENT_LENGTH, wrt.get_mut().take().freeze())
|
||||||
|
}
|
||||||
|
|
||||||
/// Set a cookie
|
/// Set a cookie
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
@ -396,7 +414,7 @@ impl HttpResponseBuilder {
|
|||||||
|
|
||||||
/// This method calls provided closure with builder reference if value is true.
|
/// This method calls provided closure with builder reference if value is true.
|
||||||
pub fn if_true<F>(&mut self, value: bool, f: F) -> &mut Self
|
pub fn if_true<F>(&mut self, value: bool, f: F) -> &mut Self
|
||||||
where F: Fn(&mut HttpResponseBuilder) + 'static
|
where F: FnOnce(&mut HttpResponseBuilder)
|
||||||
{
|
{
|
||||||
if value {
|
if value {
|
||||||
f(self);
|
f(self);
|
||||||
@ -404,6 +422,16 @@ impl HttpResponseBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This method calls provided closure with builder reference if value is Some.
|
||||||
|
pub fn if_some<T, F>(&mut self, value: Option<T>, f: F) -> &mut Self
|
||||||
|
where F: FnOnce(T, &mut HttpResponseBuilder)
|
||||||
|
{
|
||||||
|
if let Some(val) = value {
|
||||||
|
f(val, self);
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Set a body and generate `HttpResponse`.
|
/// Set a body and generate `HttpResponse`.
|
||||||
///
|
///
|
||||||
/// `HttpResponseBuilder` can not be used after this call.
|
/// `HttpResponseBuilder` can not be used after this call.
|
||||||
@ -622,7 +650,7 @@ struct InnerHttpResponse {
|
|||||||
status: StatusCode,
|
status: StatusCode,
|
||||||
reason: Option<&'static str>,
|
reason: Option<&'static str>,
|
||||||
body: Body,
|
body: Body,
|
||||||
chunked: bool,
|
chunked: Option<bool>,
|
||||||
encoding: ContentEncoding,
|
encoding: ContentEncoding,
|
||||||
connection_type: Option<ConnectionType>,
|
connection_type: Option<ConnectionType>,
|
||||||
response_size: u64,
|
response_size: u64,
|
||||||
@ -639,7 +667,7 @@ impl InnerHttpResponse {
|
|||||||
status: status,
|
status: status,
|
||||||
reason: None,
|
reason: None,
|
||||||
body: body,
|
body: body,
|
||||||
chunked: false,
|
chunked: None,
|
||||||
encoding: ContentEncoding::Auto,
|
encoding: ContentEncoding::Auto,
|
||||||
connection_type: None,
|
connection_type: None,
|
||||||
response_size: 0,
|
response_size: 0,
|
||||||
@ -690,7 +718,7 @@ impl Pool {
|
|||||||
if v.len() < 128 {
|
if v.len() < 128 {
|
||||||
inner.headers.clear();
|
inner.headers.clear();
|
||||||
inner.version = None;
|
inner.version = None;
|
||||||
inner.chunked = false;
|
inner.chunked = None;
|
||||||
inner.reason = None;
|
inner.reason = None;
|
||||||
inner.encoding = ContentEncoding::Auto;
|
inner.encoding = ContentEncoding::Auto;
|
||||||
inner.connection_type = None;
|
inner.connection_type = None;
|
||||||
@ -784,11 +812,11 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_content_encoding() {
|
fn test_content_encoding() {
|
||||||
let resp = HttpResponse::build(StatusCode::OK).finish().unwrap();
|
let resp = HttpResponse::build(StatusCode::OK).finish().unwrap();
|
||||||
assert_eq!(*resp.content_encoding(), ContentEncoding::Auto);
|
assert_eq!(resp.content_encoding(), ContentEncoding::Auto);
|
||||||
|
|
||||||
let resp = HttpResponse::build(StatusCode::OK)
|
let resp = HttpResponse::build(StatusCode::OK)
|
||||||
.content_encoding(ContentEncoding::Br).finish().unwrap();
|
.content_encoding(ContentEncoding::Br).finish().unwrap();
|
||||||
assert_eq!(*resp.content_encoding(), ContentEncoding::Br);
|
assert_eq!(resp.content_encoding(), ContentEncoding::Br);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -870,14 +898,14 @@ mod tests {
|
|||||||
assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||||
header::HeaderValue::from_static("text/plain; charset=utf-8"));
|
header::HeaderValue::from_static("text/plain; charset=utf-8"));
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(resp.body().binary().unwrap(), &Binary::from((&"test".to_owned())));
|
assert_eq!(resp.body().binary().unwrap(), &Binary::from(&"test".to_owned()));
|
||||||
|
|
||||||
let resp: HttpResponse = (&"test".to_owned()).respond_to(req.clone()).ok().unwrap();
|
let resp: HttpResponse = (&"test".to_owned()).respond_to(req.clone()).ok().unwrap();
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||||
header::HeaderValue::from_static("text/plain; charset=utf-8"));
|
header::HeaderValue::from_static("text/plain; charset=utf-8"));
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(resp.body().binary().unwrap(), &Binary::from((&"test".to_owned())));
|
assert_eq!(resp.body().binary().unwrap(), &Binary::from(&"test".to_owned()));
|
||||||
|
|
||||||
let b = Bytes::from_static(b"test");
|
let b = Bytes::from_static(b"test");
|
||||||
let resp: HttpResponse = b.into();
|
let resp: HttpResponse = b.into();
|
||||||
|
52
src/lib.rs
52
src/lib.rs
@ -1,26 +1,31 @@
|
|||||||
//! Actix web is a small, fast, down-to-earth, open source rust web framework.
|
//! Actix web is a small, fast, pragmatic, open source rust web framework.
|
||||||
//!
|
//!
|
||||||
//! ```rust,ignore
|
//! ```rust
|
||||||
//! use actix_web::*;
|
//! use actix_web::*;
|
||||||
|
//! # use std::thread;
|
||||||
//!
|
//!
|
||||||
//! fn index(req: HttpRequest) -> String {
|
//! fn index(req: HttpRequest) -> String {
|
||||||
//! format!("Hello {}!", &req.match_info()["name"])
|
//! format!("Hello {}!", &req.match_info()["name"])
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! fn main() {
|
//! fn main() {
|
||||||
|
//! # thread::spawn(|| {
|
||||||
//! HttpServer::new(
|
//! HttpServer::new(
|
||||||
//! || Application::new()
|
//! || Application::new()
|
||||||
//! .resource("/{name}", |r| r.f(index)))
|
//! .resource("/{name}", |r| r.f(index)))
|
||||||
//! .bind("127.0.0.1:8080")?
|
//! .bind("127.0.0.1:8080").unwrap()
|
||||||
//! .start()
|
//! .run();
|
||||||
|
//! # });
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! ## Documentation
|
//! ## Documentation
|
||||||
//!
|
//!
|
||||||
//! * [User Guide](http://actix.github.io/actix-web/guide/)
|
//! * [User Guide](http://actix.github.io/actix-web/guide/)
|
||||||
|
//! * [Chat on gitter](https://gitter.im/actix/actix)
|
||||||
|
//! * [GitHub repository](https://github.com/actix/actix-web)
|
||||||
//! * Cargo package: [actix-web](https://crates.io/crates/actix-web)
|
//! * Cargo package: [actix-web](https://crates.io/crates/actix-web)
|
||||||
//! * Minimum supported Rust version: 1.20 or later
|
//! * Supported Rust version: 1.20 or later
|
||||||
//!
|
//!
|
||||||
//! ## Features
|
//! ## Features
|
||||||
//!
|
//!
|
||||||
@ -90,7 +95,6 @@ mod application;
|
|||||||
mod body;
|
mod body;
|
||||||
mod context;
|
mod context;
|
||||||
mod helpers;
|
mod helpers;
|
||||||
mod encoding;
|
|
||||||
mod httprequest;
|
mod httprequest;
|
||||||
mod httpresponse;
|
mod httpresponse;
|
||||||
mod info;
|
mod info;
|
||||||
@ -101,15 +105,6 @@ mod param;
|
|||||||
mod resource;
|
mod resource;
|
||||||
mod handler;
|
mod handler;
|
||||||
mod pipeline;
|
mod pipeline;
|
||||||
mod server;
|
|
||||||
mod worker;
|
|
||||||
mod channel;
|
|
||||||
mod wsframe;
|
|
||||||
mod wsproto;
|
|
||||||
mod h1;
|
|
||||||
mod h2;
|
|
||||||
mod h1writer;
|
|
||||||
mod h2writer;
|
|
||||||
|
|
||||||
pub mod fs;
|
pub mod fs;
|
||||||
pub mod ws;
|
pub mod ws;
|
||||||
@ -120,17 +115,18 @@ pub mod middleware;
|
|||||||
pub mod pred;
|
pub mod pred;
|
||||||
pub mod test;
|
pub mod test;
|
||||||
pub mod payload;
|
pub mod payload;
|
||||||
|
pub mod server;
|
||||||
pub use error::{Error, Result, ResponseError};
|
pub use error::{Error, Result, ResponseError};
|
||||||
pub use body::{Body, Binary};
|
pub use body::{Body, Binary};
|
||||||
pub use json::{Json};
|
pub use json::Json;
|
||||||
pub use application::Application;
|
pub use application::Application;
|
||||||
pub use httprequest::HttpRequest;
|
pub use httprequest::HttpRequest;
|
||||||
pub use httpresponse::HttpResponse;
|
pub use httpresponse::HttpResponse;
|
||||||
pub use handler::{Reply, Responder, NormalizePath, AsyncResponder};
|
pub use handler::{Reply, Responder, NormalizePath, AsyncResponder};
|
||||||
pub use route::Route;
|
pub use route::Route;
|
||||||
pub use resource::Resource;
|
pub use resource::Resource;
|
||||||
pub use server::HttpServer;
|
|
||||||
pub use context::HttpContext;
|
pub use context::HttpContext;
|
||||||
|
pub use server::HttpServer;
|
||||||
|
|
||||||
// re-exports
|
// re-exports
|
||||||
pub use http::{Method, StatusCode, Version};
|
pub use http::{Method, StatusCode, Version};
|
||||||
@ -146,12 +142,25 @@ pub use openssl::pkcs12::Pkcs12;
|
|||||||
pub mod headers {
|
pub mod headers {
|
||||||
//! Headers implementation
|
//! Headers implementation
|
||||||
|
|
||||||
pub use encoding::ContentEncoding;
|
|
||||||
pub use httpresponse::ConnectionType;
|
pub use httpresponse::ConnectionType;
|
||||||
|
|
||||||
pub use cookie::Cookie;
|
pub use cookie::{Cookie, CookieBuilder};
|
||||||
pub use cookie::CookieBuilder;
|
|
||||||
pub use http_range::HttpRange;
|
pub use http_range::HttpRange;
|
||||||
|
|
||||||
|
/// Represents supported types of content encodings
|
||||||
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||||
|
pub enum ContentEncoding {
|
||||||
|
/// Automatically select encoding based on encoding negotiation
|
||||||
|
Auto,
|
||||||
|
/// A format using the Brotli algorithm
|
||||||
|
Br,
|
||||||
|
/// A format using the zlib structure with deflate algorithm
|
||||||
|
Deflate,
|
||||||
|
/// Gzip algorithm
|
||||||
|
Gzip,
|
||||||
|
/// Indicates the identity function (i.e. no compression, nor modification)
|
||||||
|
Identity,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod dev {
|
pub mod dev {
|
||||||
@ -170,10 +179,7 @@ pub mod dev {
|
|||||||
pub use handler::Handler;
|
pub use handler::Handler;
|
||||||
pub use json::JsonBody;
|
pub use json::JsonBody;
|
||||||
pub use router::{Router, Pattern};
|
pub use router::{Router, Pattern};
|
||||||
pub use channel::{HttpChannel, HttpHandler, IntoHttpHandler};
|
|
||||||
pub use param::{FromParam, Params};
|
pub use param::{FromParam, Params};
|
||||||
pub use httprequest::{UrlEncoded, RequestBody};
|
pub use httprequest::{UrlEncoded, RequestBody};
|
||||||
pub use httpresponse::HttpResponseBuilder;
|
pub use httpresponse::HttpResponseBuilder;
|
||||||
|
|
||||||
pub use server::{ServerSettings, PauseServer, ResumeServer, StopServer};
|
|
||||||
}
|
}
|
||||||
|
853
src/middleware/cors.rs
Normal file
853
src/middleware/cors.rs
Normal file
@ -0,0 +1,853 @@
|
|||||||
|
//! Cross-origin resource sharing (CORS) for Actix applications
|
||||||
|
//!
|
||||||
|
//! CORS middleware could be used with application and with resource.
|
||||||
|
//! First you need to construct CORS middleware instance.
|
||||||
|
//!
|
||||||
|
//! To construct a cors:
|
||||||
|
//!
|
||||||
|
//! 1. Call [`Cors::build`](struct.Cors.html#method.build) to start building.
|
||||||
|
//! 2. Use any of the builder methods to set fields in the backend.
|
||||||
|
//! 3. Call [finish](struct.Cors.html#method.finish) to retrieve the constructed backend.
|
||||||
|
//!
|
||||||
|
//! Cors middleware could be used as parameter for `Application::middleware()` or
|
||||||
|
//! `Resource::middleware()` methods. But you have to use `Cors::register()` method to
|
||||||
|
//! support *preflight* OPTIONS request.
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//! # Example
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! # extern crate http;
|
||||||
|
//! # extern crate actix_web;
|
||||||
|
//! # use actix_web::*;
|
||||||
|
//! use http::header;
|
||||||
|
//! use actix_web::middleware::cors;
|
||||||
|
//!
|
||||||
|
//! fn index(mut req: HttpRequest) -> &'static str {
|
||||||
|
//! "Hello world"
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn main() {
|
||||||
|
//! let app = Application::new()
|
||||||
|
//! .resource("/index.html", |r| {
|
||||||
|
//! cors::Cors::build() // <- Construct CORS middleware
|
||||||
|
//! .allowed_origin("https://www.rust-lang.org/")
|
||||||
|
//! .allowed_methods(vec!["GET", "POST"])
|
||||||
|
//! .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT])
|
||||||
|
//! .allowed_header(header::CONTENT_TYPE)
|
||||||
|
//! .max_age(3600)
|
||||||
|
//! .finish().expect("Can not create CORS middleware")
|
||||||
|
//! .register(r); // <- Register CORS middleware
|
||||||
|
//! r.method(Method::GET).f(|_| httpcodes::HTTPOk);
|
||||||
|
//! r.method(Method::HEAD).f(|_| httpcodes::HTTPMethodNotAllowed);
|
||||||
|
//! })
|
||||||
|
//! .finish();
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//! In this example custom *CORS* middleware get registered for "/index.html" endpoint.
|
||||||
|
//!
|
||||||
|
//! Cors middleware automatically handle *OPTIONS* preflight request.
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::iter::FromIterator;
|
||||||
|
|
||||||
|
use http::{self, Method, HttpTryFrom, Uri};
|
||||||
|
use http::header::{self, HeaderName, HeaderValue};
|
||||||
|
|
||||||
|
use error::{Result, ResponseError};
|
||||||
|
use resource::Resource;
|
||||||
|
use httprequest::HttpRequest;
|
||||||
|
use httpresponse::HttpResponse;
|
||||||
|
use httpcodes::{HTTPOk, HTTPBadRequest};
|
||||||
|
use middleware::{Middleware, Response, Started};
|
||||||
|
|
||||||
|
/// A set of errors that can occur during processing CORS
|
||||||
|
#[derive(Debug, Fail)]
|
||||||
|
pub enum CorsError {
|
||||||
|
/// The HTTP request header `Origin` is required but was not provided
|
||||||
|
#[fail(display="The HTTP request header `Origin` is required but was not provided")]
|
||||||
|
MissingOrigin,
|
||||||
|
/// The HTTP request header `Origin` could not be parsed correctly.
|
||||||
|
#[fail(display="The HTTP request header `Origin` could not be parsed correctly.")]
|
||||||
|
BadOrigin,
|
||||||
|
/// The request header `Access-Control-Request-Method` is required but is missing
|
||||||
|
#[fail(display="The request header `Access-Control-Request-Method` is required but is missing")]
|
||||||
|
MissingRequestMethod,
|
||||||
|
/// The request header `Access-Control-Request-Method` has an invalid value
|
||||||
|
#[fail(display="The request header `Access-Control-Request-Method` has an invalid value")]
|
||||||
|
BadRequestMethod,
|
||||||
|
/// The request header `Access-Control-Request-Headers` has an invalid value
|
||||||
|
#[fail(display="The request header `Access-Control-Request-Headers` has an invalid value")]
|
||||||
|
BadRequestHeaders,
|
||||||
|
/// The request header `Access-Control-Request-Headers` is required but is missing.
|
||||||
|
#[fail(display="The request header `Access-Control-Request-Headers` is required but is
|
||||||
|
missing")]
|
||||||
|
MissingRequestHeaders,
|
||||||
|
/// Origin is not allowed to make this request
|
||||||
|
#[fail(display="Origin is not allowed to make this request")]
|
||||||
|
OriginNotAllowed,
|
||||||
|
/// Requested method is not allowed
|
||||||
|
#[fail(display="Requested method is not allowed")]
|
||||||
|
MethodNotAllowed,
|
||||||
|
/// One or more headers requested are not allowed
|
||||||
|
#[fail(display="One or more headers requested are not allowed")]
|
||||||
|
HeadersNotAllowed,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A set of errors that can occur during building CORS middleware
|
||||||
|
#[derive(Debug, Fail)]
|
||||||
|
pub enum CorsBuilderError {
|
||||||
|
#[fail(display="Parse error: {}", _0)]
|
||||||
|
ParseError(http::Error),
|
||||||
|
/// Credentials are allowed, but the Origin is set to "*". This is not allowed by W3C
|
||||||
|
///
|
||||||
|
/// This is a misconfiguration. Check the documentation for `Cors`.
|
||||||
|
#[fail(display="Credentials are allowed, but the Origin is set to \"*\"")]
|
||||||
|
CredentialsWithWildcardOrigin,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl ResponseError for CorsError {
|
||||||
|
|
||||||
|
fn error_response(&self) -> HttpResponse {
|
||||||
|
HTTPBadRequest.build().body(format!("{}", self)).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An enum signifying that some of type T is allowed, or `All` (everything is allowed).
|
||||||
|
///
|
||||||
|
/// `Default` is implemented for this enum and is `All`.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub enum AllOrSome<T> {
|
||||||
|
/// Everything is allowed. Usually equivalent to the "*" value.
|
||||||
|
All,
|
||||||
|
/// Only some of `T` is allowed
|
||||||
|
Some(T),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Default for AllOrSome<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
AllOrSome::All
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> AllOrSome<T> {
|
||||||
|
/// Returns whether this is an `All` variant
|
||||||
|
pub fn is_all(&self) -> bool {
|
||||||
|
match *self {
|
||||||
|
AllOrSome::All => true,
|
||||||
|
AllOrSome::Some(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether this is a `Some` variant
|
||||||
|
pub fn is_some(&self) -> bool {
|
||||||
|
!self.is_all()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns &T
|
||||||
|
pub fn as_ref(&self) -> Option<&T> {
|
||||||
|
match *self {
|
||||||
|
AllOrSome::All => None,
|
||||||
|
AllOrSome::Some(ref t) => Some(t),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Middleware` for Cross-origin resource sharing support
|
||||||
|
///
|
||||||
|
/// The Cors struct contains the settings for CORS requests to be validated and
|
||||||
|
/// for responses to be generated.
|
||||||
|
pub struct Cors {
|
||||||
|
methods: HashSet<Method>,
|
||||||
|
origins: AllOrSome<HashSet<String>>,
|
||||||
|
origins_str: Option<HeaderValue>,
|
||||||
|
headers: AllOrSome<HashSet<HeaderName>>,
|
||||||
|
expose_hdrs: Option<String>,
|
||||||
|
max_age: Option<usize>,
|
||||||
|
preflight: bool,
|
||||||
|
send_wildcard: bool,
|
||||||
|
supports_credentials: bool,
|
||||||
|
vary_header: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Cors {
|
||||||
|
fn default() -> Cors {
|
||||||
|
Cors {
|
||||||
|
origins: AllOrSome::default(),
|
||||||
|
origins_str: None,
|
||||||
|
methods: HashSet::from_iter(
|
||||||
|
vec![Method::GET, Method::HEAD,
|
||||||
|
Method::POST, Method::OPTIONS, Method::PUT,
|
||||||
|
Method::PATCH, Method::DELETE].into_iter()),
|
||||||
|
headers: AllOrSome::All,
|
||||||
|
expose_hdrs: None,
|
||||||
|
max_age: None,
|
||||||
|
preflight: true,
|
||||||
|
send_wildcard: false,
|
||||||
|
supports_credentials: false,
|
||||||
|
vary_header: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cors {
|
||||||
|
pub fn build() -> CorsBuilder {
|
||||||
|
CorsBuilder {
|
||||||
|
cors: Some(Cors {
|
||||||
|
origins: AllOrSome::All,
|
||||||
|
origins_str: None,
|
||||||
|
methods: HashSet::new(),
|
||||||
|
headers: AllOrSome::All,
|
||||||
|
expose_hdrs: None,
|
||||||
|
max_age: None,
|
||||||
|
preflight: true,
|
||||||
|
send_wildcard: false,
|
||||||
|
supports_credentials: false,
|
||||||
|
vary_header: true,
|
||||||
|
}),
|
||||||
|
methods: false,
|
||||||
|
error: None,
|
||||||
|
expose_hdrs: HashSet::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This method register cors middleware with resource and
|
||||||
|
/// adds route for *OPTIONS* preflight requests.
|
||||||
|
///
|
||||||
|
/// It is possible to register *Cors* middleware with `Resource::middleware()`
|
||||||
|
/// method, but in that case *Cors* middleware wont be able to handle *OPTIONS*
|
||||||
|
/// requests.
|
||||||
|
pub fn register<S: 'static>(self, resource: &mut Resource<S>) {
|
||||||
|
resource.method(Method::OPTIONS).h(HTTPOk);
|
||||||
|
resource.middleware(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_origin<S>(&self, req: &mut HttpRequest<S>) -> Result<(), CorsError> {
|
||||||
|
if let Some(hdr) = req.headers().get(header::ORIGIN) {
|
||||||
|
if let Ok(origin) = hdr.to_str() {
|
||||||
|
return match self.origins {
|
||||||
|
AllOrSome::All => Ok(()),
|
||||||
|
AllOrSome::Some(ref allowed_origins) => {
|
||||||
|
allowed_origins
|
||||||
|
.get(origin)
|
||||||
|
.and_then(|_| Some(()))
|
||||||
|
.ok_or_else(|| CorsError::OriginNotAllowed)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Err(CorsError::BadOrigin)
|
||||||
|
} else {
|
||||||
|
return match self.origins {
|
||||||
|
AllOrSome::All => Ok(()),
|
||||||
|
_ => Err(CorsError::MissingOrigin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_allowed_method<S>(&self, req: &mut HttpRequest<S>) -> Result<(), CorsError> {
|
||||||
|
if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_METHOD) {
|
||||||
|
if let Ok(meth) = hdr.to_str() {
|
||||||
|
if let Ok(method) = Method::try_from(meth) {
|
||||||
|
return self.methods.get(&method)
|
||||||
|
.and_then(|_| Some(()))
|
||||||
|
.ok_or_else(|| CorsError::MethodNotAllowed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(CorsError::BadRequestMethod)
|
||||||
|
} else {
|
||||||
|
Err(CorsError::MissingRequestMethod)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_allowed_headers<S>(&self, req: &mut HttpRequest<S>) -> Result<(), CorsError> {
|
||||||
|
match self.headers {
|
||||||
|
AllOrSome::All => Ok(()),
|
||||||
|
AllOrSome::Some(ref allowed_headers) => {
|
||||||
|
if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) {
|
||||||
|
if let Ok(headers) = hdr.to_str() {
|
||||||
|
let mut hdrs = HashSet::new();
|
||||||
|
for hdr in headers.split(',') {
|
||||||
|
match HeaderName::try_from(hdr.trim()) {
|
||||||
|
Ok(hdr) => hdrs.insert(hdr),
|
||||||
|
Err(_) => return Err(CorsError::BadRequestHeaders)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hdrs.is_empty() && !hdrs.is_subset(allowed_headers) {
|
||||||
|
return Err(CorsError::HeadersNotAllowed)
|
||||||
|
}
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
Err(CorsError::BadRequestHeaders)
|
||||||
|
} else {
|
||||||
|
Err(CorsError::MissingRequestHeaders)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Middleware<S> for Cors {
|
||||||
|
|
||||||
|
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
|
||||||
|
if self.preflight && Method::OPTIONS == *req.method() {
|
||||||
|
self.validate_origin(req)?;
|
||||||
|
self.validate_allowed_method(req)?;
|
||||||
|
self.validate_allowed_headers(req)?;
|
||||||
|
|
||||||
|
// allowed headers
|
||||||
|
let headers = if let Some(headers) = self.headers.as_ref() {
|
||||||
|
Some(HeaderValue::try_from(&headers.iter().fold(
|
||||||
|
String::new(), |s, v| s + "," + v.as_str()).as_str()[1..]).unwrap())
|
||||||
|
} else if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) {
|
||||||
|
Some(hdr.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Started::Response(
|
||||||
|
HTTPOk.build()
|
||||||
|
.if_some(self.max_age.as_ref(), |max_age, resp| {
|
||||||
|
let _ = resp.header(
|
||||||
|
header::ACCESS_CONTROL_MAX_AGE, format!("{}", max_age).as_str());})
|
||||||
|
.if_some(headers, |headers, resp| {
|
||||||
|
let _ = resp.header(header::ACCESS_CONTROL_ALLOW_HEADERS, headers); })
|
||||||
|
.if_true(self.origins.is_all(), |resp| {
|
||||||
|
if self.send_wildcard {
|
||||||
|
resp.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*");
|
||||||
|
} else {
|
||||||
|
let origin = req.headers().get(header::ORIGIN).unwrap();
|
||||||
|
resp.header(
|
||||||
|
header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.clone());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.if_true(self.origins.is_some(), |resp| {
|
||||||
|
resp.header(
|
||||||
|
header::ACCESS_CONTROL_ALLOW_ORIGIN,
|
||||||
|
self.origins_str.as_ref().unwrap().clone());
|
||||||
|
})
|
||||||
|
.if_true(self.supports_credentials, |resp| {
|
||||||
|
resp.header(header::ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
|
||||||
|
})
|
||||||
|
.header(
|
||||||
|
header::ACCESS_CONTROL_ALLOW_METHODS,
|
||||||
|
&self.methods.iter().fold(
|
||||||
|
String::new(), |s, v| s + "," + v.as_str()).as_str()[1..])
|
||||||
|
.finish()
|
||||||
|
.unwrap()))
|
||||||
|
} else {
|
||||||
|
self.validate_origin(req)?;
|
||||||
|
|
||||||
|
Ok(Started::Done)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn response(&self, req: &mut HttpRequest<S>, mut resp: HttpResponse) -> Result<Response> {
|
||||||
|
match self.origins {
|
||||||
|
AllOrSome::All => {
|
||||||
|
if self.send_wildcard {
|
||||||
|
resp.headers_mut().insert(
|
||||||
|
header::ACCESS_CONTROL_ALLOW_ORIGIN, HeaderValue::from_static("*"));
|
||||||
|
} else {
|
||||||
|
let origin = req.headers().get(header::ORIGIN).unwrap();
|
||||||
|
resp.headers_mut().insert(
|
||||||
|
header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AllOrSome::Some(_) => {
|
||||||
|
resp.headers_mut().insert(
|
||||||
|
header::ACCESS_CONTROL_ALLOW_ORIGIN,
|
||||||
|
self.origins_str.as_ref().unwrap().clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref expose) = self.expose_hdrs {
|
||||||
|
resp.headers_mut().insert(
|
||||||
|
header::ACCESS_CONTROL_EXPOSE_HEADERS,
|
||||||
|
HeaderValue::try_from(expose.as_str()).unwrap());
|
||||||
|
}
|
||||||
|
if self.supports_credentials {
|
||||||
|
resp.headers_mut().insert(
|
||||||
|
header::ACCESS_CONTROL_ALLOW_CREDENTIALS, HeaderValue::from_static("true"));
|
||||||
|
}
|
||||||
|
if self.vary_header {
|
||||||
|
let value = if let Some(hdr) = resp.headers_mut().get(header::VARY) {
|
||||||
|
let mut val: Vec<u8> = Vec::with_capacity(hdr.as_bytes().len() + 8);
|
||||||
|
val.extend(hdr.as_bytes());
|
||||||
|
val.extend(b", Origin");
|
||||||
|
HeaderValue::try_from(&val[..]).unwrap()
|
||||||
|
} else {
|
||||||
|
HeaderValue::from_static("Origin")
|
||||||
|
};
|
||||||
|
resp.headers_mut().insert(header::VARY, value);
|
||||||
|
}
|
||||||
|
Ok(Response::Done(resp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Structure that follows the builder pattern for building `Cors` middleware structs.
|
||||||
|
///
|
||||||
|
/// To construct a cors:
|
||||||
|
///
|
||||||
|
/// 1. Call [`Cors::build`](struct.Cors.html#method.build) to start building.
|
||||||
|
/// 2. Use any of the builder methods to set fields in the backend.
|
||||||
|
/// 3. Call [finish](struct.Cors.html#method.finish) to retrieve the constructed backend.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate http;
|
||||||
|
/// # extern crate actix_web;
|
||||||
|
/// use http::header;
|
||||||
|
/// use actix_web::middleware::cors;
|
||||||
|
///
|
||||||
|
/// # fn main() {
|
||||||
|
/// let cors = cors::Cors::build()
|
||||||
|
/// .allowed_origin("https://www.rust-lang.org/")
|
||||||
|
/// .allowed_methods(vec!["GET", "POST"])
|
||||||
|
/// .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT])
|
||||||
|
/// .allowed_header(header::CONTENT_TYPE)
|
||||||
|
/// .max_age(3600)
|
||||||
|
/// .finish().unwrap();
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub struct CorsBuilder {
|
||||||
|
cors: Option<Cors>,
|
||||||
|
methods: bool,
|
||||||
|
error: Option<http::Error>,
|
||||||
|
expose_hdrs: HashSet<HeaderName>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cors<'a>(parts: &'a mut Option<Cors>, err: &Option<http::Error>) -> Option<&'a mut Cors> {
|
||||||
|
if err.is_some() {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
parts.as_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CorsBuilder {
|
||||||
|
|
||||||
|
/// Add an origin that are allowed to make requests.
|
||||||
|
/// Will be verified against the `Origin` request header.
|
||||||
|
///
|
||||||
|
/// When `All` is set, and `send_wildcard` is set, "*" will be sent in
|
||||||
|
/// the `Access-Control-Allow-Origin` response header. Otherwise, the client's `Origin` request
|
||||||
|
/// header will be echoed back in the `Access-Control-Allow-Origin` response header.
|
||||||
|
///
|
||||||
|
/// When `Some` is set, the client's `Origin` request header will be checked in a
|
||||||
|
/// case-sensitive manner.
|
||||||
|
///
|
||||||
|
/// This is the `list of origins` in the
|
||||||
|
/// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
|
||||||
|
///
|
||||||
|
/// Defaults to `All`.
|
||||||
|
/// ```
|
||||||
|
pub fn allowed_origin(&mut self, origin: &str) -> &mut CorsBuilder {
|
||||||
|
if let Some(cors) = cors(&mut self.cors, &self.error) {
|
||||||
|
match Uri::try_from(origin) {
|
||||||
|
Ok(_) => {
|
||||||
|
if cors.origins.is_all() {
|
||||||
|
cors.origins = AllOrSome::Some(HashSet::new());
|
||||||
|
}
|
||||||
|
if let AllOrSome::Some(ref mut origins) = cors.origins {
|
||||||
|
origins.insert(origin.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.error = Some(e.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a list of methods which the allowed origins are allowed to access for
|
||||||
|
/// requests.
|
||||||
|
///
|
||||||
|
/// This is the `list of methods` in the
|
||||||
|
/// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
|
||||||
|
///
|
||||||
|
/// Defaults to `[GET, HEAD, POST, OPTIONS, PUT, PATCH, DELETE]`
|
||||||
|
pub fn allowed_methods<U, M>(&mut self, methods: U) -> &mut CorsBuilder
|
||||||
|
where U: IntoIterator<Item=M>, Method: HttpTryFrom<M>
|
||||||
|
{
|
||||||
|
self.methods = true;
|
||||||
|
if let Some(cors) = cors(&mut self.cors, &self.error) {
|
||||||
|
for m in methods {
|
||||||
|
match Method::try_from(m) {
|
||||||
|
Ok(method) => {
|
||||||
|
cors.methods.insert(method);
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
self.error = Some(e.into());
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set an allowed header
|
||||||
|
pub fn allowed_header<H>(&mut self, header: H) -> &mut CorsBuilder
|
||||||
|
where HeaderName: HttpTryFrom<H>
|
||||||
|
{
|
||||||
|
if let Some(cors) = cors(&mut self.cors, &self.error) {
|
||||||
|
match HeaderName::try_from(header) {
|
||||||
|
Ok(method) => {
|
||||||
|
if cors.headers.is_all() {
|
||||||
|
cors.headers = AllOrSome::Some(HashSet::new());
|
||||||
|
}
|
||||||
|
if let AllOrSome::Some(ref mut headers) = cors.headers {
|
||||||
|
headers.insert(method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => self.error = Some(e.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a list of header field names which can be used when
|
||||||
|
/// this resource is accessed by allowed origins.
|
||||||
|
///
|
||||||
|
/// If `All` is set, whatever is requested by the client in `Access-Control-Request-Headers`
|
||||||
|
/// will be echoed back in the `Access-Control-Allow-Headers` header.
|
||||||
|
///
|
||||||
|
/// This is the `list of headers` in the
|
||||||
|
/// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
|
||||||
|
///
|
||||||
|
/// Defaults to `All`.
|
||||||
|
pub fn allowed_headers<U, H>(&mut self, headers: U) -> &mut CorsBuilder
|
||||||
|
where U: IntoIterator<Item=H>, HeaderName: HttpTryFrom<H>
|
||||||
|
{
|
||||||
|
if let Some(cors) = cors(&mut self.cors, &self.error) {
|
||||||
|
for h in headers {
|
||||||
|
match HeaderName::try_from(h) {
|
||||||
|
Ok(method) => {
|
||||||
|
if cors.headers.is_all() {
|
||||||
|
cors.headers = AllOrSome::Some(HashSet::new());
|
||||||
|
}
|
||||||
|
if let AllOrSome::Some(ref mut headers) = cors.headers {
|
||||||
|
headers.insert(method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.error = Some(e.into());
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a list of headers which are safe to expose to the API of a CORS API specification.
|
||||||
|
/// This corresponds to the `Access-Control-Expose-Headers` response header.
|
||||||
|
///
|
||||||
|
/// This is the `list of exposed headers` in the
|
||||||
|
/// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
|
||||||
|
///
|
||||||
|
/// This defaults to an empty set.
|
||||||
|
pub fn expose_headers<U, H>(&mut self, headers: U) -> &mut CorsBuilder
|
||||||
|
where U: IntoIterator<Item=H>, HeaderName: HttpTryFrom<H>
|
||||||
|
{
|
||||||
|
for h in headers {
|
||||||
|
match HeaderName::try_from(h) {
|
||||||
|
Ok(method) => {
|
||||||
|
self.expose_hdrs.insert(method);
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
self.error = Some(e.into());
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a maximum time for which this CORS request maybe cached.
|
||||||
|
/// This value is set as the `Access-Control-Max-Age` header.
|
||||||
|
///
|
||||||
|
/// This defaults to `None` (unset).
|
||||||
|
pub fn max_age(&mut self, max_age: usize) -> &mut CorsBuilder {
|
||||||
|
if let Some(cors) = cors(&mut self.cors, &self.error) {
|
||||||
|
cors.max_age = Some(max_age)
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a wildcard origins
|
||||||
|
///
|
||||||
|
/// If send widlcard is set and the `allowed_origins` parameter is `All`, a wildcard
|
||||||
|
/// `Access-Control-Allow-Origin` response header is sent, rather than the request’s
|
||||||
|
/// `Origin` header.
|
||||||
|
///
|
||||||
|
/// This is the `supports credentials flag` in the
|
||||||
|
/// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
|
||||||
|
///
|
||||||
|
/// This **CANNOT** be used in conjunction with `allowed_origins` set to `All` and
|
||||||
|
/// `allow_credentials` set to `true`. Depending on the mode of usage, this will either result
|
||||||
|
/// in an `Error::CredentialsWithWildcardOrigin` error during actix launch or runtime.
|
||||||
|
///
|
||||||
|
/// Defaults to `false`.
|
||||||
|
pub fn send_wildcard(&mut self) -> &mut CorsBuilder {
|
||||||
|
if let Some(cors) = cors(&mut self.cors, &self.error) {
|
||||||
|
cors.send_wildcard = true
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allows users to make authenticated requests
|
||||||
|
///
|
||||||
|
/// If true, injects the `Access-Control-Allow-Credentials` header in responses.
|
||||||
|
/// This allows cookies and credentials to be submitted across domains.
|
||||||
|
///
|
||||||
|
/// This option cannot be used in conjuction with an `allowed_origin` set to `All`
|
||||||
|
/// and `send_wildcards` set to `true`.
|
||||||
|
///
|
||||||
|
/// Defaults to `false`.
|
||||||
|
pub fn supports_credentials(&mut self) -> &mut CorsBuilder {
|
||||||
|
if let Some(cors) = cors(&mut self.cors, &self.error) {
|
||||||
|
cors.supports_credentials = true
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disable `Vary` header support.
|
||||||
|
///
|
||||||
|
/// When enabled the header `Vary: Origin` will be returned as per the W3
|
||||||
|
/// implementation guidelines.
|
||||||
|
///
|
||||||
|
/// Setting this header when the `Access-Control-Allow-Origin` is
|
||||||
|
/// dynamically generated (e.g. when there is more than one allowed
|
||||||
|
/// origin, and an Origin than '*' is returned) informs CDNs and other
|
||||||
|
/// caches that the CORS headers are dynamic, and cannot be cached.
|
||||||
|
///
|
||||||
|
/// By default `vary` header support is enabled.
|
||||||
|
pub fn disable_vary_header(&mut self) -> &mut CorsBuilder {
|
||||||
|
if let Some(cors) = cors(&mut self.cors, &self.error) {
|
||||||
|
cors.vary_header = false
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disable *preflight* request support.
|
||||||
|
///
|
||||||
|
/// When enabled cors middleware automatically handles *OPTIONS* request.
|
||||||
|
/// This is useful application level middleware.
|
||||||
|
///
|
||||||
|
/// By default *preflight* support is enabled.
|
||||||
|
pub fn disable_preflight(&mut self) -> &mut CorsBuilder {
|
||||||
|
if let Some(cors) = cors(&mut self.cors, &self.error) {
|
||||||
|
cors.preflight = false
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finishes building and returns the built `Cors` instance.
|
||||||
|
pub fn finish(&mut self) -> Result<Cors, CorsBuilderError> {
|
||||||
|
if !self.methods {
|
||||||
|
self.allowed_methods(vec![Method::GET, Method::HEAD,
|
||||||
|
Method::POST, Method::OPTIONS, Method::PUT,
|
||||||
|
Method::PATCH, Method::DELETE]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(e) = self.error.take() {
|
||||||
|
return Err(CorsBuilderError::ParseError(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cors = self.cors.take().expect("cannot reuse CorsBuilder");
|
||||||
|
|
||||||
|
if cors.supports_credentials && cors.send_wildcard && cors.origins.is_all() {
|
||||||
|
return Err(CorsBuilderError::CredentialsWithWildcardOrigin)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let AllOrSome::Some(ref origins) = cors.origins {
|
||||||
|
let s = origins.iter().fold(String::new(), |s, v| s + &format!("{}", v));
|
||||||
|
cors.origins_str = Some(HeaderValue::try_from(s.as_str()).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.expose_hdrs.is_empty() {
|
||||||
|
cors.expose_hdrs = Some(
|
||||||
|
self.expose_hdrs.iter().fold(
|
||||||
|
String::new(), |s, v| s + v.as_str())[1..].to_owned());
|
||||||
|
}
|
||||||
|
Ok(cors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use test::TestRequest;
|
||||||
|
|
||||||
|
impl Started {
|
||||||
|
fn is_done(&self) -> bool {
|
||||||
|
match *self {
|
||||||
|
Started::Done => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn response(self) -> HttpResponse {
|
||||||
|
match self {
|
||||||
|
Started::Response(resp) => resp,
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Response {
|
||||||
|
fn response(self) -> HttpResponse {
|
||||||
|
match self {
|
||||||
|
Response::Done(resp) => resp,
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "CredentialsWithWildcardOrigin")]
|
||||||
|
fn cors_validates_illegal_allow_credentials() {
|
||||||
|
Cors::build()
|
||||||
|
.supports_credentials()
|
||||||
|
.send_wildcard()
|
||||||
|
.finish()
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validate_origin_allows_all_origins() {
|
||||||
|
let cors = Cors::default();
|
||||||
|
let mut req = TestRequest::with_header(
|
||||||
|
"Origin", "https://www.example.com").finish();
|
||||||
|
|
||||||
|
assert!(cors.start(&mut req).ok().unwrap().is_done())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_preflight() {
|
||||||
|
let mut cors = Cors::build()
|
||||||
|
.send_wildcard()
|
||||||
|
.max_age(3600)
|
||||||
|
.allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST])
|
||||||
|
.allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT])
|
||||||
|
.allowed_header(header::CONTENT_TYPE)
|
||||||
|
.finish().unwrap();
|
||||||
|
|
||||||
|
let mut req = TestRequest::with_header(
|
||||||
|
"Origin", "https://www.example.com")
|
||||||
|
.method(Method::OPTIONS)
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
assert!(cors.start(&mut req).is_err());
|
||||||
|
|
||||||
|
let mut req = TestRequest::with_header("Origin", "https://www.example.com")
|
||||||
|
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "put")
|
||||||
|
.method(Method::OPTIONS)
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
assert!(cors.start(&mut req).is_err());
|
||||||
|
|
||||||
|
let mut req = TestRequest::with_header("Origin", "https://www.example.com")
|
||||||
|
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST")
|
||||||
|
.header(header::ACCESS_CONTROL_REQUEST_HEADERS, "AUTHORIZATION,ACCEPT")
|
||||||
|
.method(Method::OPTIONS)
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
let resp = cors.start(&mut req).unwrap().response();
|
||||||
|
assert_eq!(
|
||||||
|
&b"*"[..],
|
||||||
|
resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).unwrap().as_bytes());
|
||||||
|
assert_eq!(
|
||||||
|
&b"3600"[..],
|
||||||
|
resp.headers().get(header::ACCESS_CONTROL_MAX_AGE).unwrap().as_bytes());
|
||||||
|
//assert_eq!(
|
||||||
|
// &b"authorization,accept,content-type"[..],
|
||||||
|
// resp.headers().get(header::ACCESS_CONTROL_ALLOW_HEADERS).unwrap().as_bytes());
|
||||||
|
//assert_eq!(
|
||||||
|
// &b"POST,GET,OPTIONS"[..],
|
||||||
|
// resp.headers().get(header::ACCESS_CONTROL_ALLOW_METHODS).unwrap().as_bytes());
|
||||||
|
|
||||||
|
cors.preflight = false;
|
||||||
|
assert!(cors.start(&mut req).unwrap().is_done());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "MissingOrigin")]
|
||||||
|
fn test_validate_missing_origin() {
|
||||||
|
let cors = Cors::build()
|
||||||
|
.allowed_origin("https://www.example.com").finish().unwrap();
|
||||||
|
|
||||||
|
let mut req = HttpRequest::default();
|
||||||
|
cors.start(&mut req).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "OriginNotAllowed")]
|
||||||
|
fn test_validate_not_allowed_origin() {
|
||||||
|
let cors = Cors::build()
|
||||||
|
.allowed_origin("https://www.example.com").finish().unwrap();
|
||||||
|
|
||||||
|
let mut req = TestRequest::with_header("Origin", "https://www.unknown.com")
|
||||||
|
.method(Method::GET)
|
||||||
|
.finish();
|
||||||
|
cors.start(&mut req).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_validate_origin() {
|
||||||
|
let cors = Cors::build()
|
||||||
|
.allowed_origin("https://www.example.com").finish().unwrap();
|
||||||
|
|
||||||
|
let mut req = TestRequest::with_header("Origin", "https://www.example.com")
|
||||||
|
.method(Method::GET)
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
assert!(cors.start(&mut req).unwrap().is_done());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_response() {
|
||||||
|
let cors = Cors::build()
|
||||||
|
.send_wildcard()
|
||||||
|
.disable_preflight()
|
||||||
|
.max_age(3600)
|
||||||
|
.allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST])
|
||||||
|
.allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT])
|
||||||
|
.allowed_header(header::CONTENT_TYPE)
|
||||||
|
.finish().unwrap();
|
||||||
|
|
||||||
|
let mut req = TestRequest::with_header(
|
||||||
|
"Origin", "https://www.example.com")
|
||||||
|
.method(Method::OPTIONS)
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
let resp: HttpResponse = HTTPOk.into();
|
||||||
|
let resp = cors.response(&mut req, resp).unwrap().response();
|
||||||
|
assert_eq!(
|
||||||
|
&b"*"[..],
|
||||||
|
resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).unwrap().as_bytes());
|
||||||
|
assert_eq!(
|
||||||
|
&b"Origin"[..],
|
||||||
|
resp.headers().get(header::VARY).unwrap().as_bytes());
|
||||||
|
|
||||||
|
let resp: HttpResponse = HTTPOk.build()
|
||||||
|
.header(header::VARY, "Accept")
|
||||||
|
.finish().unwrap();
|
||||||
|
let resp = cors.response(&mut req, resp).unwrap().response();
|
||||||
|
assert_eq!(
|
||||||
|
&b"Accept, Origin"[..],
|
||||||
|
resp.headers().get(header::VARY).unwrap().as_bytes());
|
||||||
|
|
||||||
|
let cors = Cors::build()
|
||||||
|
.disable_vary_header()
|
||||||
|
.allowed_origin("https://www.example.com")
|
||||||
|
.finish().unwrap();
|
||||||
|
let resp: HttpResponse = HTTPOk.into();
|
||||||
|
let resp = cors.response(&mut req, resp).unwrap().response();
|
||||||
|
assert_eq!(
|
||||||
|
&b"https://www.example.com"[..],
|
||||||
|
resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).unwrap().as_bytes());
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
use http::{HeaderMap, HttpTryFrom};
|
use http::{HeaderMap, HttpTryFrom};
|
||||||
use http::header::{HeaderName, HeaderValue, CONTENT_TYPE};
|
use http::header::{HeaderName, HeaderValue, CONTENT_TYPE};
|
||||||
|
|
||||||
|
use error::Result;
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
use middleware::{Response, Middleware};
|
use middleware::{Response, Middleware};
|
||||||
@ -40,7 +41,7 @@ impl DefaultHeaders {
|
|||||||
|
|
||||||
impl<S> Middleware<S> for DefaultHeaders {
|
impl<S> Middleware<S> for DefaultHeaders {
|
||||||
|
|
||||||
fn response(&self, _: &mut HttpRequest<S>, mut resp: HttpResponse) -> Response {
|
fn response(&self, _: &mut HttpRequest<S>, mut resp: HttpResponse) -> Result<Response> {
|
||||||
for (key, value) in self.headers.iter() {
|
for (key, value) in self.headers.iter() {
|
||||||
if !resp.headers().contains_key(key) {
|
if !resp.headers().contains_key(key) {
|
||||||
resp.headers_mut().insert(key, value.clone());
|
resp.headers_mut().insert(key, value.clone());
|
||||||
@ -51,7 +52,7 @@ impl<S> Middleware<S> for DefaultHeaders {
|
|||||||
resp.headers_mut().insert(
|
resp.headers_mut().insert(
|
||||||
CONTENT_TYPE, HeaderValue::from_static("application/octet-stream"));
|
CONTENT_TYPE, HeaderValue::from_static("application/octet-stream"));
|
||||||
}
|
}
|
||||||
Response::Done(resp)
|
Ok(Response::Done(resp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,14 +114,14 @@ mod tests {
|
|||||||
|
|
||||||
let resp = HttpResponse::Ok().finish().unwrap();
|
let resp = HttpResponse::Ok().finish().unwrap();
|
||||||
let resp = match mw.response(&mut req, resp) {
|
let resp = match mw.response(&mut req, resp) {
|
||||||
Response::Done(resp) => resp,
|
Ok(Response::Done(resp)) => resp,
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
};
|
};
|
||||||
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
|
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
|
||||||
|
|
||||||
let resp = HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish().unwrap();
|
let resp = HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish().unwrap();
|
||||||
let resp = match mw.response(&mut req, resp) {
|
let resp = match mw.response(&mut req, resp) {
|
||||||
Response::Done(resp) => resp,
|
Ok(Response::Done(resp)) => resp,
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
};
|
};
|
||||||
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0002");
|
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0002");
|
||||||
|
@ -7,6 +7,7 @@ use libc;
|
|||||||
use time;
|
use time;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
|
use error::Result;
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
use middleware::{Middleware, Started, Finished};
|
use middleware::{Middleware, Started, Finished};
|
||||||
@ -101,9 +102,9 @@ impl Logger {
|
|||||||
|
|
||||||
impl<S> Middleware<S> for Logger {
|
impl<S> Middleware<S> for Logger {
|
||||||
|
|
||||||
fn start(&self, req: &mut HttpRequest<S>) -> Started {
|
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
|
||||||
req.extensions().insert(StartTime(time::now()));
|
req.extensions().insert(StartTime(time::now()));
|
||||||
Started::Done
|
Ok(Started::Done)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(&self, req: &mut HttpRequest<S>, resp: &HttpResponse) -> Finished {
|
fn finish(&self, req: &mut HttpRequest<S>, resp: &HttpResponse) -> Finished {
|
||||||
@ -305,7 +306,7 @@ mod tests {
|
|||||||
.force_close().body(Body::Empty).unwrap();
|
.force_close().body(Body::Empty).unwrap();
|
||||||
|
|
||||||
match logger.start(&mut req) {
|
match logger.start(&mut req) {
|
||||||
Started::Done => (),
|
Ok(Started::Done) => (),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
};
|
};
|
||||||
match logger.finish(&mut req, &resp) {
|
match logger.finish(&mut req, &resp) {
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
//! Middlewares
|
//! Middlewares
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
|
|
||||||
use error::Error;
|
use error::{Error, Result};
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
|
|
||||||
mod logger;
|
mod logger;
|
||||||
mod session;
|
mod session;
|
||||||
mod defaultheaders;
|
mod defaultheaders;
|
||||||
|
pub mod cors;
|
||||||
pub use self::logger::Logger;
|
pub use self::logger::Logger;
|
||||||
pub use self::defaultheaders::{DefaultHeaders, DefaultHeadersBuilder};
|
pub use self::defaultheaders::{DefaultHeaders, DefaultHeadersBuilder};
|
||||||
pub use self::session::{RequestSession, Session, SessionImpl, SessionBackend, SessionStorage,
|
pub use self::session::{RequestSession, Session, SessionImpl, SessionBackend, SessionStorage,
|
||||||
@ -17,8 +18,6 @@ pub use self::session::{RequestSession, Session, SessionImpl, SessionBackend, Se
|
|||||||
pub enum Started {
|
pub enum Started {
|
||||||
/// Execution completed
|
/// Execution completed
|
||||||
Done,
|
Done,
|
||||||
/// Moddleware error
|
|
||||||
Err(Error),
|
|
||||||
/// New http response got generated. If middleware generates response
|
/// New http response got generated. If middleware generates response
|
||||||
/// handler execution halts.
|
/// handler execution halts.
|
||||||
Response(HttpResponse),
|
Response(HttpResponse),
|
||||||
@ -28,8 +27,6 @@ pub enum Started {
|
|||||||
|
|
||||||
/// Middleware execution result
|
/// Middleware execution result
|
||||||
pub enum Response {
|
pub enum Response {
|
||||||
/// Moddleware error
|
|
||||||
Err(Error),
|
|
||||||
/// New http response got generated
|
/// New http response got generated
|
||||||
Done(HttpResponse),
|
Done(HttpResponse),
|
||||||
/// Result is a future that resolves to a new http response
|
/// Result is a future that resolves to a new http response
|
||||||
@ -46,18 +43,18 @@ pub enum Finished {
|
|||||||
|
|
||||||
/// Middleware definition
|
/// Middleware definition
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
pub trait Middleware<S> {
|
pub trait Middleware<S>: 'static {
|
||||||
|
|
||||||
/// Method is called when request is ready. It may return
|
/// Method is called when request is ready. It may return
|
||||||
/// future, which should resolve before next middleware get called.
|
/// future, which should resolve before next middleware get called.
|
||||||
fn start(&self, req: &mut HttpRequest<S>) -> Started {
|
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
|
||||||
Started::Done
|
Ok(Started::Done)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Method is called when handler returns response,
|
/// Method is called when handler returns response,
|
||||||
/// but before sending http message to peer.
|
/// but before sending http message to peer.
|
||||||
fn response(&self, req: &mut HttpRequest<S>, resp: HttpResponse) -> Response {
|
fn response(&self, req: &mut HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
|
||||||
Response::Done(resp)
|
Ok(Response::Done(resp))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Method is called after body stream get sent to peer.
|
/// Method is called after body stream get sent to peer.
|
||||||
|
@ -141,7 +141,7 @@ impl<S, T: SessionBackend<S>> SessionStorage<T, S> {
|
|||||||
|
|
||||||
impl<S: 'static, T: SessionBackend<S>> Middleware<S> for SessionStorage<T, S> {
|
impl<S: 'static, T: SessionBackend<S>> Middleware<S> for SessionStorage<T, S> {
|
||||||
|
|
||||||
fn start(&self, req: &mut HttpRequest<S>) -> Started {
|
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
|
||||||
let mut req = req.clone();
|
let mut req = req.clone();
|
||||||
|
|
||||||
let fut = self.0.from_request(&mut req)
|
let fut = self.0.from_request(&mut req)
|
||||||
@ -154,14 +154,14 @@ impl<S: 'static, T: SessionBackend<S>> Middleware<S> for SessionStorage<T, S> {
|
|||||||
Err(err) => FutErr(err)
|
Err(err) => FutErr(err)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Started::Future(Box::new(fut))
|
Ok(Started::Future(Box::new(fut)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn response(&self, req: &mut HttpRequest<S>, resp: HttpResponse) -> Response {
|
fn response(&self, req: &mut HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
|
||||||
if let Some(s_box) = req.extensions().remove::<Arc<SessionImplBox>>() {
|
if let Some(s_box) = req.extensions().remove::<Arc<SessionImplBox>>() {
|
||||||
s_box.0.write(resp)
|
s_box.0.write(resp)
|
||||||
} else {
|
} else {
|
||||||
Response::Done(resp)
|
Ok(Response::Done(resp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -179,7 +179,7 @@ pub trait SessionImpl: 'static {
|
|||||||
fn clear(&mut self);
|
fn clear(&mut self);
|
||||||
|
|
||||||
/// Write session to storage backend.
|
/// Write session to storage backend.
|
||||||
fn write(&self, resp: HttpResponse) -> Response;
|
fn write(&self, resp: HttpResponse) -> Result<Response>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Session's storage backend trait definition.
|
/// Session's storage backend trait definition.
|
||||||
@ -205,8 +205,8 @@ impl SessionImpl for DummySessionImpl {
|
|||||||
fn set(&mut self, key: &str, value: String) {}
|
fn set(&mut self, key: &str, value: String) {}
|
||||||
fn remove(&mut self, key: &str) {}
|
fn remove(&mut self, key: &str) {}
|
||||||
fn clear(&mut self) {}
|
fn clear(&mut self) {}
|
||||||
fn write(&self, resp: HttpResponse) -> Response {
|
fn write(&self, resp: HttpResponse) -> Result<Response> {
|
||||||
Response::Done(resp)
|
Ok(Response::Done(resp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,7 +217,7 @@ pub struct CookieSession {
|
|||||||
inner: Rc<CookieSessionInner>,
|
inner: Rc<CookieSessionInner>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Errors that can occure during handling cookie session
|
/// Errors that can occur during handling cookie session
|
||||||
#[derive(Fail, Debug)]
|
#[derive(Fail, Debug)]
|
||||||
pub enum CookieSessionError {
|
pub enum CookieSessionError {
|
||||||
/// Size of the serialized session is greater than 4000 bytes.
|
/// Size of the serialized session is greater than 4000 bytes.
|
||||||
@ -255,11 +255,11 @@ impl SessionImpl for CookieSession {
|
|||||||
self.state.clear()
|
self.state.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&self, mut resp: HttpResponse) -> Response {
|
fn write(&self, mut resp: HttpResponse) -> Result<Response> {
|
||||||
if self.changed {
|
if self.changed {
|
||||||
let _ = self.inner.set_cookie(&mut resp, &self.state);
|
let _ = self.inner.set_cookie(&mut resp, &self.state);
|
||||||
}
|
}
|
||||||
Response::Done(resp)
|
Ok(Response::Done(resp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -395,7 +395,6 @@ impl<S> SessionBackend<S> for CookieSessionBackend {
|
|||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate actix_web;
|
/// # extern crate actix_web;
|
||||||
///
|
|
||||||
/// use actix_web::middleware::CookieSessionBackend;
|
/// use actix_web::middleware::CookieSessionBackend;
|
||||||
///
|
///
|
||||||
/// # fn main() {
|
/// # fn main() {
|
||||||
|
@ -6,7 +6,7 @@ use std::slice::Iter;
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use error::{ResponseError, UriSegmentError, ErrorBadRequest};
|
use error::{ResponseError, UriSegmentError, InternalError, ErrorBadRequest};
|
||||||
|
|
||||||
|
|
||||||
/// A trait to abstract the idea of creating a new instance of a type from a path parameter.
|
/// A trait to abstract the idea of creating a new instance of a type from a path parameter.
|
||||||
@ -77,7 +77,7 @@ impl<'a> Params<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return iterator to items in paramter container
|
/// Return iterator to items in parameter container
|
||||||
pub fn iter(&self) -> Iter<(Cow<'a, str>, Cow<'a, str>)> {
|
pub fn iter(&self) -> Iter<(Cow<'a, str>, Cow<'a, str>)> {
|
||||||
self.0.iter()
|
self.0.iter()
|
||||||
}
|
}
|
||||||
@ -141,7 +141,7 @@ impl FromParam for PathBuf {
|
|||||||
macro_rules! FROM_STR {
|
macro_rules! FROM_STR {
|
||||||
($type:ty) => {
|
($type:ty) => {
|
||||||
impl FromParam for $type {
|
impl FromParam for $type {
|
||||||
type Err = ErrorBadRequest<<$type as FromStr>::Err>;
|
type Err = InternalError<<$type as FromStr>::Err>;
|
||||||
|
|
||||||
fn from_param(val: &str) -> Result<Self, Self::Err> {
|
fn from_param(val: &str) -> Result<Self, Self::Err> {
|
||||||
<$type as FromStr>::from_str(val).map_err(ErrorBadRequest)
|
<$type as FromStr>::from_str(val).map_err(ErrorBadRequest)
|
||||||
|
@ -70,61 +70,73 @@ impl Payload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Indicates EOF of payload
|
/// Indicates EOF of payload
|
||||||
|
#[inline]
|
||||||
pub fn eof(&self) -> bool {
|
pub fn eof(&self) -> bool {
|
||||||
self.inner.borrow().eof()
|
self.inner.borrow().eof()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Length of the data in this payload
|
/// Length of the data in this payload
|
||||||
|
#[inline]
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.inner.borrow().len()
|
self.inner.borrow().len()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Is payload empty
|
/// Is payload empty
|
||||||
|
#[inline]
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.inner.borrow().len() == 0
|
self.inner.borrow().len() == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get first available chunk of data.
|
/// Get first available chunk of data.
|
||||||
|
#[inline]
|
||||||
pub fn readany(&self) -> ReadAny {
|
pub fn readany(&self) -> ReadAny {
|
||||||
ReadAny(Rc::clone(&self.inner))
|
ReadAny(Rc::clone(&self.inner))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get exact number of bytes
|
/// Get exact number of bytes
|
||||||
|
#[inline]
|
||||||
pub fn readexactly(&self, size: usize) -> ReadExactly {
|
pub fn readexactly(&self, size: usize) -> ReadExactly {
|
||||||
ReadExactly(Rc::clone(&self.inner), size)
|
ReadExactly(Rc::clone(&self.inner), size)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read until `\n`
|
/// Read until `\n`
|
||||||
|
#[inline]
|
||||||
pub fn readline(&self) -> ReadLine {
|
pub fn readline(&self) -> ReadLine {
|
||||||
ReadLine(Rc::clone(&self.inner))
|
ReadLine(Rc::clone(&self.inner))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read until match line
|
/// Read until match line
|
||||||
|
#[inline]
|
||||||
pub fn readuntil(&self, line: &[u8]) -> ReadUntil {
|
pub fn readuntil(&self, line: &[u8]) -> ReadUntil {
|
||||||
ReadUntil(Rc::clone(&self.inner), line.to_vec())
|
ReadUntil(Rc::clone(&self.inner), line.to_vec())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
|
#[inline]
|
||||||
pub fn readall(&self) -> Option<Bytes> {
|
pub fn readall(&self) -> Option<Bytes> {
|
||||||
self.inner.borrow_mut().readall()
|
self.inner.borrow_mut().readall()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Put unused data back to payload
|
/// Put unused data back to payload
|
||||||
|
#[inline]
|
||||||
pub fn unread_data(&mut self, data: Bytes) {
|
pub fn unread_data(&mut self, data: Bytes) {
|
||||||
self.inner.borrow_mut().unread_data(data);
|
self.inner.borrow_mut().unread_data(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get size of payload buffer
|
/// Get size of payload buffer
|
||||||
|
#[inline]
|
||||||
pub fn buffer_size(&self) -> usize {
|
pub fn buffer_size(&self) -> usize {
|
||||||
self.inner.borrow().buffer_size()
|
self.inner.borrow().buffer_size()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set size of payload buffer
|
/// Set size of payload buffer
|
||||||
|
#[inline]
|
||||||
pub fn set_buffer_size(&self, size: usize) {
|
pub fn set_buffer_size(&self, size: usize) {
|
||||||
self.inner.borrow_mut().set_buffer_size(size)
|
self.inner.borrow_mut().set_buffer_size(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert payload into compatible `HttpResponse` body stream
|
/// Convert payload into compatible `HttpResponse` body stream
|
||||||
|
#[inline]
|
||||||
pub fn stream(self) -> BodyStream {
|
pub fn stream(self) -> BodyStream {
|
||||||
Box::new(self.map(|i| i.0).map_err(|e| e.into()))
|
Box::new(self.map(|i| i.0).map_err(|e| e.into()))
|
||||||
}
|
}
|
||||||
@ -134,6 +146,7 @@ impl Stream for Payload {
|
|||||||
type Item = PayloadItem;
|
type Item = PayloadItem;
|
||||||
type Error = PayloadError;
|
type Error = PayloadError;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn poll(&mut self) -> Poll<Option<PayloadItem>, PayloadError> {
|
fn poll(&mut self) -> Poll<Option<PayloadItem>, PayloadError> {
|
||||||
self.inner.borrow_mut().readany()
|
self.inner.borrow_mut().readany()
|
||||||
}
|
}
|
||||||
@ -407,7 +420,7 @@ impl Inner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn readall(&mut self) -> Option<Bytes> {
|
pub fn readall(&mut self) -> Option<Bytes> {
|
||||||
let len = self.items.iter().fold(0, |cur, item| cur + item.len());
|
let len = self.items.iter().map(|b| b.len()).sum();
|
||||||
if len > 0 {
|
if len > 0 {
|
||||||
let mut buf = BytesMut::with_capacity(len);
|
let mut buf = BytesMut::with_capacity(len);
|
||||||
for item in &self.items {
|
for item in &self.items {
|
||||||
|
385
src/pipeline.rs
385
src/pipeline.rs
@ -3,19 +3,19 @@ use std::rc::Rc;
|
|||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use log::Level::Debug;
|
||||||
use futures::{Async, Poll, Future, Stream};
|
use futures::{Async, Poll, Future, Stream};
|
||||||
use futures::unsync::oneshot;
|
use futures::unsync::oneshot;
|
||||||
|
|
||||||
use channel::HttpHandlerTask;
|
|
||||||
use body::{Body, BodyStream};
|
use body::{Body, BodyStream};
|
||||||
use context::{Frame, ActorHttpContext};
|
use context::{Frame, ActorHttpContext};
|
||||||
use error::Error;
|
use error::Error;
|
||||||
use handler::{Reply, ReplyItem};
|
use handler::{Reply, ReplyItem};
|
||||||
use h1writer::{Writer, WriterState};
|
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
use middleware::{Middleware, Finished, Started, Response};
|
use middleware::{Middleware, Finished, Started, Response};
|
||||||
use application::Inner;
|
use application::Inner;
|
||||||
|
use server::{Writer, WriterState, HttpHandlerTask};
|
||||||
|
|
||||||
pub(crate) trait PipelineHandler<S> {
|
pub(crate) trait PipelineHandler<S> {
|
||||||
fn handle(&mut self, req: HttpRequest<S>) -> Reply;
|
fn handle(&mut self, req: HttpRequest<S>) -> Reply;
|
||||||
@ -34,9 +34,30 @@ enum PipelineState<S, H> {
|
|||||||
Completed(Completed<S, H>),
|
Completed(Completed<S, H>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<S: 'static, H: PipelineHandler<S>> PipelineState<S, H> {
|
||||||
|
|
||||||
|
fn is_response(&self) -> bool {
|
||||||
|
match *self {
|
||||||
|
PipelineState::Response(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll(&mut self, info: &mut PipelineInfo<S>) -> Option<PipelineState<S, H>> {
|
||||||
|
match *self {
|
||||||
|
PipelineState::Starting(ref mut state) => state.poll(info),
|
||||||
|
PipelineState::Handler(ref mut state) => state.poll(info),
|
||||||
|
PipelineState::RunMiddlewares(ref mut state) => state.poll(info),
|
||||||
|
PipelineState::Finishing(ref mut state) => state.poll(info),
|
||||||
|
PipelineState::Completed(ref mut state) => state.poll(info),
|
||||||
|
PipelineState::Response(_) | PipelineState::None | PipelineState::Error => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct PipelineInfo<S> {
|
struct PipelineInfo<S> {
|
||||||
req: HttpRequest<S>,
|
req: HttpRequest<S>,
|
||||||
count: usize,
|
count: u16,
|
||||||
mws: Rc<Vec<Box<Middleware<S>>>>,
|
mws: Rc<Vec<Box<Middleware<S>>>>,
|
||||||
context: Option<Box<ActorHttpContext>>,
|
context: Option<Box<ActorHttpContext>>,
|
||||||
error: Option<Error>,
|
error: Option<Error>,
|
||||||
@ -74,7 +95,7 @@ impl<S> PipelineInfo<S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, H: PipelineHandler<S>> Pipeline<S, H> {
|
impl<S: 'static, H: PipelineHandler<S>> Pipeline<S, H> {
|
||||||
|
|
||||||
pub fn new(req: HttpRequest<S>,
|
pub fn new(req: HttpRequest<S>,
|
||||||
mws: Rc<Vec<Box<Middleware<S>>>>,
|
mws: Rc<Vec<Box<Middleware<S>>>>,
|
||||||
@ -101,65 +122,32 @@ impl Pipeline<(), Inner<()>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, H> Pipeline<S, H> {
|
impl<S: 'static, H> Pipeline<S, H> {
|
||||||
|
|
||||||
fn is_done(&self) -> bool {
|
fn is_done(&self) -> bool {
|
||||||
match self.1 {
|
match self.1 {
|
||||||
PipelineState::None | PipelineState::Error
|
PipelineState::None | PipelineState::Error
|
||||||
| PipelineState::Starting(_) | PipelineState::Handler(_)
|
| PipelineState::Starting(_) | PipelineState::Handler(_)
|
||||||
| PipelineState::RunMiddlewares(_) | PipelineState::Response(_) => true,
|
| PipelineState::RunMiddlewares(_) | PipelineState::Response(_) => true,
|
||||||
PipelineState::Finishing(_) => self.0.context.is_none(),
|
PipelineState::Finishing(_) | PipelineState::Completed(_) => false,
|
||||||
PipelineState::Completed(_) => false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, H: PipelineHandler<S>> HttpHandlerTask for Pipeline<S, H> {
|
impl<S: 'static, H: PipelineHandler<S>> HttpHandlerTask for Pipeline<S, H> {
|
||||||
|
|
||||||
fn disconnected(&mut self) {
|
fn disconnected(&mut self) {
|
||||||
self.0.disconnected = Some(true);
|
self.0.disconnected = Some(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error> {
|
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error> {
|
||||||
|
let info: &mut PipelineInfo<_> = unsafe{ mem::transmute(&mut self.0) };
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
if self.1.is_response() {
|
||||||
let state = mem::replace(&mut self.1, PipelineState::None);
|
let state = mem::replace(&mut self.1, PipelineState::None);
|
||||||
match state {
|
if let PipelineState::Response(st) = state {
|
||||||
PipelineState::None =>
|
match st.poll_io(io, info) {
|
||||||
return Ok(Async::Ready(true)),
|
|
||||||
PipelineState::Error =>
|
|
||||||
return Err(io::Error::new(io::ErrorKind::Other, "Internal error").into()),
|
|
||||||
PipelineState::Starting(st) => {
|
|
||||||
match st.poll(&mut self.0) {
|
|
||||||
Ok(state) =>
|
|
||||||
self.1 = state,
|
|
||||||
Err(state) => {
|
|
||||||
self.1 = state;
|
|
||||||
return Ok(Async::NotReady)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PipelineState::Handler(st) => {
|
|
||||||
match st.poll(&mut self.0) {
|
|
||||||
Ok(state) =>
|
|
||||||
self.1 = state,
|
|
||||||
Err(state) => {
|
|
||||||
self.1 = state;
|
|
||||||
return Ok(Async::NotReady)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PipelineState::RunMiddlewares(st) => {
|
|
||||||
match st.poll(&mut self.0) {
|
|
||||||
Ok(state) =>
|
|
||||||
self.1 = state,
|
|
||||||
Err(state) => {
|
|
||||||
self.1 = state;
|
|
||||||
return Ok(Async::NotReady)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PipelineState::Response(st) => {
|
|
||||||
match st.poll_io(io, &mut self.0) {
|
|
||||||
Ok(state) => {
|
Ok(state) => {
|
||||||
self.1 = state;
|
self.1 = state;
|
||||||
if let Some(error) = self.0.error.take() {
|
if let Some(error) = self.0.error.take() {
|
||||||
@ -170,100 +158,42 @@ impl<S, H: PipelineHandler<S>> HttpHandlerTask for Pipeline<S, H> {
|
|||||||
}
|
}
|
||||||
Err(state) => {
|
Err(state) => {
|
||||||
self.1 = state;
|
self.1 = state;
|
||||||
return Ok(Async::NotReady)
|
return Ok(Async::NotReady);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PipelineState::Finishing(st) => {
|
|
||||||
match st.poll(&mut self.0) {
|
|
||||||
Ok(state) =>
|
|
||||||
self.1 = state,
|
|
||||||
Err(state) => {
|
|
||||||
self.1 = state;
|
|
||||||
return Ok(Async::NotReady)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PipelineState::Completed(st) => {
|
|
||||||
match st.poll(&mut self.0) {
|
|
||||||
Ok(state) => {
|
|
||||||
self.1 = state;
|
|
||||||
return Ok(Async::Ready(true));
|
|
||||||
}
|
|
||||||
Err(state) => {
|
|
||||||
self.1 = state;
|
|
||||||
return Ok(Async::NotReady)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
match self.1 {
|
||||||
|
PipelineState::None =>
|
||||||
|
return Ok(Async::Ready(true)),
|
||||||
|
PipelineState::Error =>
|
||||||
|
return Err(io::Error::new(io::ErrorKind::Other, "Internal error").into()),
|
||||||
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match self.1.poll(info) {
|
||||||
|
Some(state) => self.1 = state,
|
||||||
|
None => return Ok(Async::NotReady),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll(&mut self) -> Poll<(), Error> {
|
fn poll(&mut self) -> Poll<(), Error> {
|
||||||
|
let info: &mut PipelineInfo<_> = unsafe{ mem::transmute(&mut self.0) };
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let state = mem::replace(&mut self.1, PipelineState::None);
|
match self.1 {
|
||||||
match state {
|
|
||||||
PipelineState::None | PipelineState::Error => {
|
PipelineState::None | PipelineState::Error => {
|
||||||
return Ok(Async::Ready(()))
|
return Ok(Async::Ready(()))
|
||||||
}
|
}
|
||||||
PipelineState::Starting(st) => {
|
_ => (),
|
||||||
match st.poll(&mut self.0) {
|
}
|
||||||
Ok(state) =>
|
|
||||||
self.1 = state,
|
if let Some(state) = self.1.poll(info) {
|
||||||
Err(state) => {
|
|
||||||
self.1 = state;
|
|
||||||
return Ok(Async::NotReady)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PipelineState::Handler(st) => {
|
|
||||||
match st.poll(&mut self.0) {
|
|
||||||
Ok(state) =>
|
|
||||||
self.1 = state,
|
|
||||||
Err(state) => {
|
|
||||||
self.1 = state;
|
|
||||||
return Ok(Async::NotReady)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PipelineState::RunMiddlewares(st) => {
|
|
||||||
match st.poll(&mut self.0) {
|
|
||||||
Ok(state) =>
|
|
||||||
self.1 = state,
|
|
||||||
Err(state) => {
|
|
||||||
self.1 = state;
|
|
||||||
return Ok(Async::NotReady)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PipelineState::Response(_) => {
|
|
||||||
self.1 = state;
|
self.1 = state;
|
||||||
|
} else {
|
||||||
return Ok(Async::NotReady);
|
return Ok(Async::NotReady);
|
||||||
}
|
}
|
||||||
PipelineState::Finishing(st) => {
|
|
||||||
match st.poll(&mut self.0) {
|
|
||||||
Ok(state) =>
|
|
||||||
self.1 = state,
|
|
||||||
Err(state) => {
|
|
||||||
self.1 = state;
|
|
||||||
return Ok(Async::NotReady)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PipelineState::Completed(st) => {
|
|
||||||
match st.poll(&mut self.0) {
|
|
||||||
Ok(state) => {
|
|
||||||
self.1 = state;
|
|
||||||
return Ok(Async::Ready(()));
|
|
||||||
}
|
|
||||||
Err(state) => {
|
|
||||||
self.1 = state;
|
|
||||||
return Ok(Async::NotReady)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -277,24 +207,23 @@ struct StartMiddlewares<S, H> {
|
|||||||
_s: PhantomData<S>,
|
_s: PhantomData<S>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, H: PipelineHandler<S>> StartMiddlewares<S, H> {
|
impl<S: 'static, H: PipelineHandler<S>> StartMiddlewares<S, H> {
|
||||||
|
|
||||||
fn init(info: &mut PipelineInfo<S>, handler: Rc<RefCell<H>>) -> PipelineState<S, H>
|
fn init(info: &mut PipelineInfo<S>, handler: Rc<RefCell<H>>) -> PipelineState<S, H> {
|
||||||
{
|
|
||||||
// execute middlewares, we need this stage because middlewares could be non-async
|
// execute middlewares, we need this stage because middlewares could be non-async
|
||||||
// and we can move to next state immidietly
|
// and we can move to next state immediately
|
||||||
let len = info.mws.len();
|
let len = info.mws.len() as u16;
|
||||||
loop {
|
loop {
|
||||||
if info.count == len {
|
if info.count == len {
|
||||||
let reply = handler.borrow_mut().handle(info.req.clone());
|
let reply = handler.borrow_mut().handle(info.req.clone());
|
||||||
return WaitingResponse::init(info, reply)
|
return WaitingResponse::init(info, reply)
|
||||||
} else {
|
} else {
|
||||||
match info.mws[info.count].start(&mut info.req) {
|
match info.mws[info.count as usize].start(&mut info.req) {
|
||||||
Started::Done =>
|
Ok(Started::Done) =>
|
||||||
info.count += 1,
|
info.count += 1,
|
||||||
Started::Response(resp) =>
|
Ok(Started::Response(resp)) =>
|
||||||
return RunMiddlewares::init(info, resp),
|
return RunMiddlewares::init(info, resp),
|
||||||
Started::Future(mut fut) =>
|
Ok(Started::Future(mut fut)) =>
|
||||||
match fut.poll() {
|
match fut.poll() {
|
||||||
Ok(Async::NotReady) =>
|
Ok(Async::NotReady) =>
|
||||||
return PipelineState::Starting(StartMiddlewares {
|
return PipelineState::Starting(StartMiddlewares {
|
||||||
@ -310,48 +239,46 @@ impl<S, H: PipelineHandler<S>> StartMiddlewares<S, H> {
|
|||||||
Err(err) =>
|
Err(err) =>
|
||||||
return ProcessResponse::init(err.into()),
|
return ProcessResponse::init(err.into()),
|
||||||
},
|
},
|
||||||
Started::Err(err) =>
|
Err(err) =>
|
||||||
return ProcessResponse::init(err.into()),
|
return ProcessResponse::init(err.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll(mut self, info: &mut PipelineInfo<S>) -> Result<PipelineState<S, H>, PipelineState<S, H>>
|
fn poll(&mut self, info: &mut PipelineInfo<S>) -> Option<PipelineState<S, H>> {
|
||||||
{
|
let len = info.mws.len() as u16;
|
||||||
let len = info.mws.len();
|
|
||||||
'outer: loop {
|
'outer: loop {
|
||||||
match self.fut.as_mut().unwrap().poll() {
|
match self.fut.as_mut().unwrap().poll() {
|
||||||
Ok(Async::NotReady) =>
|
Ok(Async::NotReady) => return None,
|
||||||
return Err(PipelineState::Starting(self)),
|
|
||||||
Ok(Async::Ready(resp)) => {
|
Ok(Async::Ready(resp)) => {
|
||||||
info.count += 1;
|
info.count += 1;
|
||||||
if let Some(resp) = resp {
|
if let Some(resp) = resp {
|
||||||
return Ok(RunMiddlewares::init(info, resp));
|
return Some(RunMiddlewares::init(info, resp));
|
||||||
}
|
}
|
||||||
if info.count == len {
|
if info.count == len {
|
||||||
let reply = (*self.hnd.borrow_mut()).handle(info.req.clone());
|
let reply = (*self.hnd.borrow_mut()).handle(info.req.clone());
|
||||||
return Ok(WaitingResponse::init(info, reply));
|
return Some(WaitingResponse::init(info, reply));
|
||||||
} else {
|
} else {
|
||||||
loop {
|
loop {
|
||||||
match info.mws[info.count].start(info.req_mut()) {
|
match info.mws[info.count as usize].start(info.req_mut()) {
|
||||||
Started::Done =>
|
Ok(Started::Done) =>
|
||||||
info.count += 1,
|
info.count += 1,
|
||||||
Started::Response(resp) => {
|
Ok(Started::Response(resp)) => {
|
||||||
return Ok(RunMiddlewares::init(info, resp));
|
return Some(RunMiddlewares::init(info, resp));
|
||||||
},
|
},
|
||||||
Started::Future(fut) => {
|
Ok(Started::Future(fut)) => {
|
||||||
self.fut = Some(fut);
|
self.fut = Some(fut);
|
||||||
continue 'outer
|
continue 'outer
|
||||||
},
|
},
|
||||||
Started::Err(err) =>
|
Err(err) =>
|
||||||
return Ok(ProcessResponse::init(err.into()))
|
return Some(ProcessResponse::init(err.into()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) =>
|
Err(err) =>
|
||||||
return Ok(ProcessResponse::init(err.into()))
|
return Some(ProcessResponse::init(err.into()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -364,11 +291,10 @@ struct WaitingResponse<S, H> {
|
|||||||
_h: PhantomData<H>,
|
_h: PhantomData<H>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, H> WaitingResponse<S, H> {
|
impl<S: 'static, H> WaitingResponse<S, H> {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn init(info: &mut PipelineInfo<S>, reply: Reply) -> PipelineState<S, H>
|
fn init(info: &mut PipelineInfo<S>, reply: Reply) -> PipelineState<S, H> {
|
||||||
{
|
|
||||||
match reply.into() {
|
match reply.into() {
|
||||||
ReplyItem::Message(resp) =>
|
ReplyItem::Message(resp) =>
|
||||||
RunMiddlewares::init(info, resp),
|
RunMiddlewares::init(info, resp),
|
||||||
@ -378,15 +304,13 @@ impl<S, H> WaitingResponse<S, H> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll(mut self, info: &mut PipelineInfo<S>) -> Result<PipelineState<S, H>, PipelineState<S, H>>
|
fn poll(&mut self, info: &mut PipelineInfo<S>) -> Option<PipelineState<S, H>> {
|
||||||
{
|
|
||||||
match self.fut.poll() {
|
match self.fut.poll() {
|
||||||
Ok(Async::NotReady) =>
|
Ok(Async::NotReady) => None,
|
||||||
Err(PipelineState::Handler(self)),
|
|
||||||
Ok(Async::Ready(response)) =>
|
Ok(Async::Ready(response)) =>
|
||||||
Ok(RunMiddlewares::init(info, response)),
|
Some(RunMiddlewares::init(info, response)),
|
||||||
Err(err) =>
|
Err(err) =>
|
||||||
Ok(ProcessResponse::init(err.into())),
|
Some(ProcessResponse::init(err.into())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -399,10 +323,9 @@ struct RunMiddlewares<S, H> {
|
|||||||
_h: PhantomData<H>,
|
_h: PhantomData<H>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, H> RunMiddlewares<S, H> {
|
impl<S: 'static, H> RunMiddlewares<S, H> {
|
||||||
|
|
||||||
fn init(info: &mut PipelineInfo<S>, mut resp: HttpResponse) -> PipelineState<S, H>
|
fn init(info: &mut PipelineInfo<S>, mut resp: HttpResponse) -> PipelineState<S, H> {
|
||||||
{
|
|
||||||
if info.count == 0 {
|
if info.count == 0 {
|
||||||
return ProcessResponse::init(resp);
|
return ProcessResponse::init(resp);
|
||||||
}
|
}
|
||||||
@ -411,11 +334,11 @@ impl<S, H> RunMiddlewares<S, H> {
|
|||||||
|
|
||||||
loop {
|
loop {
|
||||||
resp = match info.mws[curr].response(info.req_mut(), resp) {
|
resp = match info.mws[curr].response(info.req_mut(), resp) {
|
||||||
Response::Err(err) => {
|
Err(err) => {
|
||||||
info.count = curr + 1;
|
info.count = (curr + 1) as u16;
|
||||||
return ProcessResponse::init(err.into())
|
return ProcessResponse::init(err.into())
|
||||||
}
|
}
|
||||||
Response::Done(r) => {
|
Ok(Response::Done(r)) => {
|
||||||
curr += 1;
|
curr += 1;
|
||||||
if curr == len {
|
if curr == len {
|
||||||
return ProcessResponse::init(r)
|
return ProcessResponse::init(r)
|
||||||
@ -423,7 +346,7 @@ impl<S, H> RunMiddlewares<S, H> {
|
|||||||
r
|
r
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Response::Future(fut) => {
|
Ok(Response::Future(fut)) => {
|
||||||
return PipelineState::RunMiddlewares(
|
return PipelineState::RunMiddlewares(
|
||||||
RunMiddlewares { curr: curr, fut: Some(fut),
|
RunMiddlewares { curr: curr, fut: Some(fut),
|
||||||
_s: PhantomData, _h: PhantomData })
|
_s: PhantomData, _h: PhantomData })
|
||||||
@ -432,36 +355,35 @@ impl<S, H> RunMiddlewares<S, H> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll(mut self, info: &mut PipelineInfo<S>) -> Result<PipelineState<S,H>, PipelineState<S, H>>
|
fn poll(&mut self, info: &mut PipelineInfo<S>) -> Option<PipelineState<S, H>> {
|
||||||
{
|
|
||||||
let len = info.mws.len();
|
let len = info.mws.len();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// poll latest fut
|
// poll latest fut
|
||||||
let mut resp = match self.fut.as_mut().unwrap().poll() {
|
let mut resp = match self.fut.as_mut().unwrap().poll() {
|
||||||
Ok(Async::NotReady) => {
|
Ok(Async::NotReady) => {
|
||||||
return Err(PipelineState::RunMiddlewares(self))
|
return None
|
||||||
}
|
}
|
||||||
Ok(Async::Ready(resp)) => {
|
Ok(Async::Ready(resp)) => {
|
||||||
self.curr += 1;
|
self.curr += 1;
|
||||||
resp
|
resp
|
||||||
}
|
}
|
||||||
Err(err) =>
|
Err(err) =>
|
||||||
return Ok(ProcessResponse::init(err.into())),
|
return Some(ProcessResponse::init(err.into())),
|
||||||
};
|
};
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if self.curr == len {
|
if self.curr == len {
|
||||||
return Ok(ProcessResponse::init(resp));
|
return Some(ProcessResponse::init(resp));
|
||||||
} else {
|
} else {
|
||||||
match info.mws[self.curr].response(info.req_mut(), resp) {
|
match info.mws[self.curr].response(info.req_mut(), resp) {
|
||||||
Response::Err(err) =>
|
Err(err) =>
|
||||||
return Ok(ProcessResponse::init(err.into())),
|
return Some(ProcessResponse::init(err.into())),
|
||||||
Response::Done(r) => {
|
Ok(Response::Done(r)) => {
|
||||||
self.curr += 1;
|
self.curr += 1;
|
||||||
resp = r
|
resp = r
|
||||||
},
|
},
|
||||||
Response::Future(fut) => {
|
Ok(Response::Future(fut)) => {
|
||||||
self.fut = Some(fut);
|
self.fut = Some(fut);
|
||||||
break
|
break
|
||||||
},
|
},
|
||||||
@ -510,17 +432,15 @@ enum IOState {
|
|||||||
Done,
|
Done,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, H> ProcessResponse<S, H> {
|
impl<S: 'static, H> ProcessResponse<S, H> {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn init(resp: HttpResponse) -> PipelineState<S, H>
|
fn init(resp: HttpResponse) -> PipelineState<S, H> {
|
||||||
{
|
|
||||||
PipelineState::Response(
|
PipelineState::Response(
|
||||||
ProcessResponse{ resp: resp,
|
ProcessResponse{ resp: resp,
|
||||||
iostate: IOState::Response,
|
iostate: IOState::Response,
|
||||||
running: RunningState::Running,
|
running: RunningState::Running,
|
||||||
drain: None,
|
drain: None, _s: PhantomData, _h: PhantomData})
|
||||||
_s: PhantomData, _h: PhantomData})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll_io(mut self, io: &mut Writer, info: &mut PipelineInfo<S>)
|
fn poll_io(mut self, io: &mut Writer, info: &mut PipelineInfo<S>)
|
||||||
@ -528,7 +448,7 @@ impl<S, H> ProcessResponse<S, H> {
|
|||||||
{
|
{
|
||||||
if self.drain.is_none() && self.running != RunningState::Paused {
|
if self.drain.is_none() && self.running != RunningState::Paused {
|
||||||
// if task is paused, write buffer is probably full
|
// if task is paused, write buffer is probably full
|
||||||
loop {
|
'outter: loop {
|
||||||
let result = match mem::replace(&mut self.iostate, IOState::Done) {
|
let result = match mem::replace(&mut self.iostate, IOState::Done) {
|
||||||
IOState::Response => {
|
IOState::Response => {
|
||||||
let result = match io.start(info.req_mut().get_inner(), &mut self.resp) {
|
let result = match io.start(info.req_mut().get_inner(), &mut self.resp) {
|
||||||
@ -539,6 +459,13 @@ impl<S, H> ProcessResponse<S, H> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if let Some(err) = self.resp.error() {
|
||||||
|
warn!("Error occured during request handling: {}", err);
|
||||||
|
if log_enabled!(Debug) {
|
||||||
|
debug!("{:?}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match self.resp.replace_body(Body::Empty) {
|
match self.resp.replace_body(Body::Empty) {
|
||||||
Body::Streaming(stream) =>
|
Body::Streaming(stream) =>
|
||||||
self.iostate = IOState::Payload(stream),
|
self.iostate = IOState::Payload(stream),
|
||||||
@ -550,19 +477,6 @@ impl<S, H> ProcessResponse<S, H> {
|
|||||||
result
|
result
|
||||||
},
|
},
|
||||||
IOState::Payload(mut body) => {
|
IOState::Payload(mut body) => {
|
||||||
// always poll context
|
|
||||||
if self.running == RunningState::Running {
|
|
||||||
match info.poll_context() {
|
|
||||||
Ok(Async::NotReady) => (),
|
|
||||||
Ok(Async::Ready(_)) =>
|
|
||||||
self.running = RunningState::Done,
|
|
||||||
Err(err) => {
|
|
||||||
info.error = Some(err);
|
|
||||||
return Ok(FinishingMiddlewares::init(info, self.resp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match body.poll() {
|
match body.poll() {
|
||||||
Ok(Async::Ready(None)) => {
|
Ok(Async::Ready(None)) => {
|
||||||
self.iostate = IOState::Done;
|
self.iostate = IOState::Done;
|
||||||
@ -574,7 +488,7 @@ impl<S, H> ProcessResponse<S, H> {
|
|||||||
},
|
},
|
||||||
Ok(Async::Ready(Some(chunk))) => {
|
Ok(Async::Ready(Some(chunk))) => {
|
||||||
self.iostate = IOState::Payload(body);
|
self.iostate = IOState::Payload(body);
|
||||||
match io.write(chunk.as_ref()) {
|
match io.write(chunk.into()) {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
info.error = Some(err.into());
|
info.error = Some(err.into());
|
||||||
return Ok(FinishingMiddlewares::init(info, self.resp))
|
return Ok(FinishingMiddlewares::init(info, self.resp))
|
||||||
@ -597,9 +511,15 @@ impl<S, H> ProcessResponse<S, H> {
|
|||||||
ctx.disconnected();
|
ctx.disconnected();
|
||||||
}
|
}
|
||||||
match ctx.poll() {
|
match ctx.poll() {
|
||||||
Ok(Async::Ready(Some(frame))) => {
|
Ok(Async::Ready(Some(vec))) => {
|
||||||
|
if vec.is_empty() {
|
||||||
|
self.iostate = IOState::Actor(ctx);
|
||||||
|
break
|
||||||
|
}
|
||||||
|
let mut res = None;
|
||||||
|
for frame in vec {
|
||||||
match frame {
|
match frame {
|
||||||
Frame::Payload(None) => {
|
Frame::Chunk(None) => {
|
||||||
info.context = Some(ctx);
|
info.context = Some(ctx);
|
||||||
self.iostate = IOState::Done;
|
self.iostate = IOState::Done;
|
||||||
if let Err(err) = io.write_eof() {
|
if let Err(err) = io.write_eof() {
|
||||||
@ -607,25 +527,28 @@ impl<S, H> ProcessResponse<S, H> {
|
|||||||
return Ok(
|
return Ok(
|
||||||
FinishingMiddlewares::init(info, self.resp))
|
FinishingMiddlewares::init(info, self.resp))
|
||||||
}
|
}
|
||||||
break
|
break 'outter
|
||||||
},
|
},
|
||||||
Frame::Payload(Some(chunk)) => {
|
Frame::Chunk(Some(chunk)) => {
|
||||||
self.iostate = IOState::Actor(ctx);
|
match io.write(chunk) {
|
||||||
match io.write(chunk.as_ref()) {
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
info.error = Some(err.into());
|
info.error = Some(err.into());
|
||||||
return Ok(FinishingMiddlewares::init(
|
return Ok(
|
||||||
info, self.resp))
|
FinishingMiddlewares::init(info, self.resp))
|
||||||
},
|
},
|
||||||
Ok(result) => result
|
Ok(result) => res = Some(result),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Frame::Drain(fut) => {
|
Frame::Drain(fut) =>
|
||||||
self.drain = Some(fut);
|
self.drain = Some(fut),
|
||||||
|
}
|
||||||
|
}
|
||||||
self.iostate = IOState::Actor(ctx);
|
self.iostate = IOState::Actor(ctx);
|
||||||
break
|
if self.drain.is_some() {
|
||||||
}
|
self.running.resume();
|
||||||
|
break 'outter
|
||||||
}
|
}
|
||||||
|
res.unwrap()
|
||||||
},
|
},
|
||||||
Ok(Async::Ready(None)) => {
|
Ok(Async::Ready(None)) => {
|
||||||
self.iostate = IOState::Done;
|
self.iostate = IOState::Done;
|
||||||
@ -669,10 +592,8 @@ impl<S, H> ProcessResponse<S, H> {
|
|||||||
// restart io processing
|
// restart io processing
|
||||||
return self.poll_io(io, info);
|
return self.poll_io(io, info);
|
||||||
},
|
},
|
||||||
Ok(Async::NotReady) =>
|
Ok(Async::NotReady) => return Err(PipelineState::Response(self)),
|
||||||
return Err(PipelineState::Response(self)),
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
debug!("Error sending data: {}", err);
|
|
||||||
info.error = Some(err.into());
|
info.error = Some(err.into());
|
||||||
return Ok(FinishingMiddlewares::init(info, self.resp))
|
return Ok(FinishingMiddlewares::init(info, self.resp))
|
||||||
}
|
}
|
||||||
@ -685,7 +606,6 @@ impl<S, H> ProcessResponse<S, H> {
|
|||||||
match io.write_eof() {
|
match io.write_eof() {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
debug!("Error sending data: {}", err);
|
|
||||||
info.error = Some(err.into());
|
info.error = Some(err.into());
|
||||||
return Ok(FinishingMiddlewares::init(info, self.resp))
|
return Ok(FinishingMiddlewares::init(info, self.resp))
|
||||||
}
|
}
|
||||||
@ -693,7 +613,7 @@ impl<S, H> ProcessResponse<S, H> {
|
|||||||
self.resp.set_response_size(io.written());
|
self.resp.set_response_size(io.written());
|
||||||
Ok(FinishingMiddlewares::init(info, self.resp))
|
Ok(FinishingMiddlewares::init(info, self.resp))
|
||||||
}
|
}
|
||||||
_ => Err(PipelineState::Response(self))
|
_ => Err(PipelineState::Response(self)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -706,21 +626,23 @@ struct FinishingMiddlewares<S, H> {
|
|||||||
_h: PhantomData<H>,
|
_h: PhantomData<H>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, H> FinishingMiddlewares<S, H> {
|
impl<S: 'static, H> FinishingMiddlewares<S, H> {
|
||||||
|
|
||||||
fn init(info: &mut PipelineInfo<S>, resp: HttpResponse) -> PipelineState<S, H> {
|
fn init(info: &mut PipelineInfo<S>, resp: HttpResponse) -> PipelineState<S, H> {
|
||||||
if info.count == 0 {
|
if info.count == 0 {
|
||||||
Completed::init(info)
|
Completed::init(info)
|
||||||
} else {
|
} else {
|
||||||
match (FinishingMiddlewares{resp: resp, fut: None,
|
let mut state = FinishingMiddlewares{resp: resp, fut: None,
|
||||||
_s: PhantomData, _h: PhantomData}).poll(info) {
|
_s: PhantomData, _h: PhantomData};
|
||||||
Ok(st) | Err(st) => st,
|
if let Some(st) = state.poll(info) {
|
||||||
|
st
|
||||||
|
} else {
|
||||||
|
PipelineState::Finishing(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll(mut self, info: &mut PipelineInfo<S>) -> Result<PipelineState<S, H>, PipelineState<S, H>>
|
fn poll(&mut self, info: &mut PipelineInfo<S>) -> Option<PipelineState<S, H>> {
|
||||||
{
|
|
||||||
loop {
|
loop {
|
||||||
// poll latest fut
|
// poll latest fut
|
||||||
let not_ready = if let Some(ref mut fut) = self.fut {
|
let not_ready = if let Some(ref mut fut) = self.fut {
|
||||||
@ -740,15 +662,15 @@ impl<S, H> FinishingMiddlewares<S, H> {
|
|||||||
false
|
false
|
||||||
};
|
};
|
||||||
if not_ready {
|
if not_ready {
|
||||||
return Ok(PipelineState::Finishing(self))
|
return None;
|
||||||
}
|
}
|
||||||
self.fut = None;
|
self.fut = None;
|
||||||
info.count -= 1;
|
info.count -= 1;
|
||||||
|
|
||||||
match info.mws[info.count].finish(info.req_mut(), &self.resp) {
|
match info.mws[info.count as usize].finish(info.req_mut(), &self.resp) {
|
||||||
Finished::Done => {
|
Finished::Done => {
|
||||||
if info.count == 0 {
|
if info.count == 0 {
|
||||||
return Ok(Completed::init(info))
|
return Some(Completed::init(info))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Finished::Future(fut) => {
|
Finished::Future(fut) => {
|
||||||
@ -759,12 +681,17 @@ impl<S, H> FinishingMiddlewares<S, H> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
struct Completed<S, H>(PhantomData<S>, PhantomData<H>);
|
struct Completed<S, H>(PhantomData<S>, PhantomData<H>);
|
||||||
|
|
||||||
impl<S, H> Completed<S, H> {
|
impl<S, H> Completed<S, H> {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn init(info: &mut PipelineInfo<S>) -> PipelineState<S, H> {
|
fn init(info: &mut PipelineInfo<S>) -> PipelineState<S, H> {
|
||||||
|
if let Some(ref err) = info.error {
|
||||||
|
error!("Error occured during request handling: {}", err);
|
||||||
|
}
|
||||||
|
|
||||||
if info.context.is_none() {
|
if info.context.is_none() {
|
||||||
PipelineState::None
|
PipelineState::None
|
||||||
} else {
|
} else {
|
||||||
@ -773,13 +700,11 @@ impl<S, H> Completed<S, H> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn poll(self, info: &mut PipelineInfo<S>) -> Result<PipelineState<S, H>, PipelineState<S, H>> {
|
fn poll(&mut self, info: &mut PipelineInfo<S>) -> Option<PipelineState<S, H>> {
|
||||||
match info.poll_context() {
|
match info.poll_context() {
|
||||||
Ok(Async::NotReady) =>
|
Ok(Async::NotReady) => None,
|
||||||
Ok(PipelineState::Completed(Completed(PhantomData, PhantomData))),
|
Ok(Async::Ready(())) => Some(PipelineState::None),
|
||||||
Ok(Async::Ready(())) =>
|
Err(_) => Some(PipelineState::Error),
|
||||||
Ok(PipelineState::None),
|
|
||||||
Err(_) => Ok(PipelineState::Error),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -819,17 +744,17 @@ mod tests {
|
|||||||
info.context = Some(Box::new(ctx));
|
info.context = Some(Box::new(ctx));
|
||||||
let mut state = Completed::<(), Inner<()>>::init(&mut info).completed().unwrap();
|
let mut state = Completed::<(), Inner<()>>::init(&mut info).completed().unwrap();
|
||||||
|
|
||||||
let st = state.poll(&mut info).ok().unwrap();
|
assert!(state.poll(&mut info).is_none());
|
||||||
let pp = Pipeline(info, st);
|
let pp = Pipeline(info, PipelineState::Completed(state));
|
||||||
assert!(!pp.is_done());
|
assert!(!pp.is_done());
|
||||||
|
|
||||||
let Pipeline(mut info, st) = pp;
|
let Pipeline(mut info, st) = pp;
|
||||||
state = st.completed().unwrap();
|
let mut st = st.completed().unwrap();
|
||||||
drop(addr);
|
drop(addr);
|
||||||
|
|
||||||
state.poll(&mut info).ok().unwrap().is_none().unwrap();
|
assert!(st.poll(&mut info).unwrap().is_none().unwrap());
|
||||||
|
|
||||||
result(Ok::<_, ()>(()))
|
result(Ok::<_, ()>(()))
|
||||||
})).unwrap()
|
})).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use std::rc::Rc;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use http::{Method, StatusCode};
|
use http::{Method, StatusCode};
|
||||||
@ -6,6 +7,7 @@ use pred;
|
|||||||
use body::Body;
|
use body::Body;
|
||||||
use route::Route;
|
use route::Route;
|
||||||
use handler::{Reply, Handler, Responder};
|
use handler::{Reply, Handler, Responder};
|
||||||
|
use middleware::Middleware;
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
|
|
||||||
@ -17,7 +19,7 @@ use httpresponse::HttpResponse;
|
|||||||
/// Route uses builder-like pattern for configuration.
|
/// Route uses builder-like pattern for configuration.
|
||||||
/// During request handling, resource object iterate through all routes
|
/// During request handling, resource object iterate through all routes
|
||||||
/// and check all predicates for specific route, if request matches all predicates route
|
/// and check all predicates for specific route, if request matches all predicates route
|
||||||
/// route considired matched and route handler get called.
|
/// route considered matched and route handler get called.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate actix_web;
|
/// # extern crate actix_web;
|
||||||
@ -33,6 +35,7 @@ pub struct Resource<S=()> {
|
|||||||
name: String,
|
name: String,
|
||||||
state: PhantomData<S>,
|
state: PhantomData<S>,
|
||||||
routes: Vec<Route<S>>,
|
routes: Vec<Route<S>>,
|
||||||
|
middlewares: Rc<Vec<Box<Middleware<S>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> Default for Resource<S> {
|
impl<S> Default for Resource<S> {
|
||||||
@ -40,7 +43,8 @@ impl<S> Default for Resource<S> {
|
|||||||
Resource {
|
Resource {
|
||||||
name: String::new(),
|
name: String::new(),
|
||||||
state: PhantomData,
|
state: PhantomData,
|
||||||
routes: Vec::new() }
|
routes: Vec::new(),
|
||||||
|
middlewares: Rc::new(Vec::new()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,7 +54,8 @@ impl<S> Resource<S> {
|
|||||||
Resource {
|
Resource {
|
||||||
name: String::new(),
|
name: String::new(),
|
||||||
state: PhantomData,
|
state: PhantomData,
|
||||||
routes: Vec::new() }
|
routes: Vec::new(),
|
||||||
|
middlewares: Rc::new(Vec::new()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set resource name
|
/// Set resource name
|
||||||
@ -126,12 +131,25 @@ impl<S: 'static> Resource<S> {
|
|||||||
self.routes.last_mut().unwrap().f(handler)
|
self.routes.last_mut().unwrap().f(handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn handle(&mut self, mut req: HttpRequest<S>, default: Option<&mut Resource<S>>)
|
/// Register a middleware
|
||||||
-> Reply
|
///
|
||||||
|
/// This is similar to `Application's` middlewares, but
|
||||||
|
/// middlewares get invoked on resource level.
|
||||||
|
pub fn middleware<M: Middleware<S>>(&mut self, mw: M) {
|
||||||
|
Rc::get_mut(&mut self.middlewares).unwrap().push(Box::new(mw));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn handle(&mut self,
|
||||||
|
mut req: HttpRequest<S>,
|
||||||
|
default: Option<&mut Resource<S>>) -> Reply
|
||||||
{
|
{
|
||||||
for route in &mut self.routes {
|
for route in &mut self.routes {
|
||||||
if route.check(&mut req) {
|
if route.check(&mut req) {
|
||||||
return route.handle(req)
|
return if self.middlewares.is_empty() {
|
||||||
|
route.handle(req)
|
||||||
|
} else {
|
||||||
|
route.compose(req, Rc::clone(&self.middlewares))
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(resource) = default {
|
if let Some(resource) = default {
|
||||||
|
338
src/route.rs
338
src/route.rs
@ -1,10 +1,16 @@
|
|||||||
use futures::Future;
|
use std::mem;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use futures::{Async, Future, Poll};
|
||||||
|
|
||||||
use error::Error;
|
use error::Error;
|
||||||
use pred::Predicate;
|
use pred::Predicate;
|
||||||
use handler::{Reply, Handler, Responder, RouteHandler, AsyncHandler, WrapHandler};
|
use handler::{Reply, ReplyItem, Handler,
|
||||||
|
Responder, RouteHandler, AsyncHandler, WrapHandler};
|
||||||
|
use middleware::{Middleware, Response as MiddlewareResponse, Started as MiddlewareStarted};
|
||||||
use httpcodes::HTTPNotFound;
|
use httpcodes::HTTPNotFound;
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
|
use httpresponse::HttpResponse;
|
||||||
|
|
||||||
/// Resource route definition
|
/// Resource route definition
|
||||||
///
|
///
|
||||||
@ -12,7 +18,7 @@ use httprequest::HttpRequest;
|
|||||||
/// 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<S> {
|
pub struct Route<S> {
|
||||||
preds: Vec<Box<Predicate<S>>>,
|
preds: Vec<Box<Predicate<S>>>,
|
||||||
handler: Box<RouteHandler<S>>,
|
handler: InnerHandler<S>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: 'static> Default for Route<S> {
|
impl<S: 'static> Default for Route<S> {
|
||||||
@ -20,13 +26,14 @@ impl<S: 'static> Default for Route<S> {
|
|||||||
fn default() -> Route<S> {
|
fn default() -> Route<S> {
|
||||||
Route {
|
Route {
|
||||||
preds: Vec::new(),
|
preds: Vec::new(),
|
||||||
handler: Box::new(WrapHandler::new(|_| HTTPNotFound)),
|
handler: InnerHandler::new(|_| HTTPNotFound),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: 'static> Route<S> {
|
impl<S: 'static> Route<S> {
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub(crate) fn check(&self, req: &mut HttpRequest<S>) -> bool {
|
pub(crate) fn check(&self, req: &mut HttpRequest<S>) -> bool {
|
||||||
for pred in &self.preds {
|
for pred in &self.preds {
|
||||||
if !pred.check(req) {
|
if !pred.check(req) {
|
||||||
@ -36,10 +43,18 @@ impl<S: 'static> Route<S> {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub(crate) fn handle(&mut self, req: HttpRequest<S>) -> Reply {
|
pub(crate) fn handle(&mut self, req: HttpRequest<S>) -> Reply {
|
||||||
self.handler.handle(req)
|
self.handler.handle(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn compose(&mut self,
|
||||||
|
req: HttpRequest<S>,
|
||||||
|
mws: Rc<Vec<Box<Middleware<S>>>>) -> Reply {
|
||||||
|
Reply::async(Compose::new(req, mws, self.handler.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
/// Add match predicate to route.
|
/// Add match predicate to route.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
@ -65,7 +80,7 @@ impl<S: 'static> Route<S> {
|
|||||||
/// Set handler object. Usually call to this method is last call
|
/// Set handler object. Usually call to this method is last call
|
||||||
/// during route configuration, because it does not return reference to self.
|
/// during route configuration, because it does not return reference to self.
|
||||||
pub fn h<H: Handler<S>>(&mut self, handler: H) {
|
pub fn h<H: Handler<S>>(&mut self, handler: H) {
|
||||||
self.handler = Box::new(WrapHandler::new(handler));
|
self.handler = InnerHandler::new(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set handler function. Usually call to this method is last call
|
/// Set handler function. Usually call to this method is last call
|
||||||
@ -74,7 +89,7 @@ impl<S: 'static> Route<S> {
|
|||||||
where F: Fn(HttpRequest<S>) -> R + 'static,
|
where F: Fn(HttpRequest<S>) -> R + 'static,
|
||||||
R: Responder + 'static,
|
R: Responder + 'static,
|
||||||
{
|
{
|
||||||
self.handler = Box::new(WrapHandler::new(handler));
|
self.handler = InnerHandler::new(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set async handler function.
|
/// Set async handler function.
|
||||||
@ -84,6 +99,315 @@ impl<S: 'static> Route<S> {
|
|||||||
R: Responder + 'static,
|
R: Responder + 'static,
|
||||||
E: Into<Error> + 'static
|
E: Into<Error> + 'static
|
||||||
{
|
{
|
||||||
self.handler = Box::new(AsyncHandler::new(handler));
|
self.handler = InnerHandler::async(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `RouteHandler` wrapper. This struct is required because it needs to be shared
|
||||||
|
/// for resource level middlewares.
|
||||||
|
struct InnerHandler<S>(Rc<Box<RouteHandler<S>>>);
|
||||||
|
|
||||||
|
impl<S: 'static> InnerHandler<S> {
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn new<H: Handler<S>>(h: H) -> Self {
|
||||||
|
InnerHandler(Rc::new(Box::new(WrapHandler::new(h))))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn async<H, R, F, E>(h: H) -> Self
|
||||||
|
where H: Fn(HttpRequest<S>) -> F + 'static,
|
||||||
|
F: Future<Item=R, Error=E> + 'static,
|
||||||
|
R: Responder + 'static,
|
||||||
|
E: Into<Error> + 'static
|
||||||
|
{
|
||||||
|
InnerHandler(Rc::new(Box::new(AsyncHandler::new(h))))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn handle(&self, req: HttpRequest<S>) -> Reply {
|
||||||
|
// reason: handler is unique per thread,
|
||||||
|
// handler get called from async code, and handler doesnt have side effects
|
||||||
|
#[allow(mutable_transmutes)]
|
||||||
|
#[cfg_attr(feature = "cargo-clippy", allow(borrowed_box))]
|
||||||
|
let h: &mut Box<RouteHandler<S>> = unsafe { mem::transmute(self.0.as_ref()) };
|
||||||
|
h.handle(req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Clone for InnerHandler<S> {
|
||||||
|
#[inline]
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
InnerHandler(Rc::clone(&self.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Compose resource level middlewares with route handler.
|
||||||
|
struct Compose<S: 'static> {
|
||||||
|
info: ComposeInfo<S>,
|
||||||
|
state: ComposeState<S>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ComposeInfo<S: 'static> {
|
||||||
|
count: usize,
|
||||||
|
req: HttpRequest<S>,
|
||||||
|
mws: Rc<Vec<Box<Middleware<S>>>>,
|
||||||
|
handler: InnerHandler<S>,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ComposeState<S: 'static> {
|
||||||
|
Starting(StartMiddlewares<S>),
|
||||||
|
Handler(WaitingResponse<S>),
|
||||||
|
RunMiddlewares(RunMiddlewares<S>),
|
||||||
|
Response(Response<S>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: 'static> ComposeState<S> {
|
||||||
|
fn poll(&mut self, info: &mut ComposeInfo<S>) -> Option<ComposeState<S>> {
|
||||||
|
match *self {
|
||||||
|
ComposeState::Starting(ref mut state) => state.poll(info),
|
||||||
|
ComposeState::Handler(ref mut state) => state.poll(info),
|
||||||
|
ComposeState::RunMiddlewares(ref mut state) => state.poll(info),
|
||||||
|
ComposeState::Response(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: 'static> Compose<S> {
|
||||||
|
fn new(req: HttpRequest<S>,
|
||||||
|
mws: Rc<Vec<Box<Middleware<S>>>>,
|
||||||
|
handler: InnerHandler<S>) -> Self
|
||||||
|
{
|
||||||
|
let mut info = ComposeInfo {
|
||||||
|
count: 0,
|
||||||
|
req: req,
|
||||||
|
mws: mws,
|
||||||
|
handler: handler };
|
||||||
|
let state = StartMiddlewares::init(&mut info);
|
||||||
|
|
||||||
|
Compose {state: state, info: info}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Future for Compose<S> {
|
||||||
|
type Item = HttpResponse;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||||
|
loop {
|
||||||
|
if let ComposeState::Response(ref mut resp) = self.state {
|
||||||
|
let resp = resp.resp.take().unwrap();
|
||||||
|
return Ok(Async::Ready(resp))
|
||||||
|
}
|
||||||
|
if let Some(state) = self.state.poll(&mut self.info) {
|
||||||
|
self.state = state;
|
||||||
|
} else {
|
||||||
|
return Ok(Async::NotReady)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Middlewares start executor
|
||||||
|
struct StartMiddlewares<S> {
|
||||||
|
fut: Option<Fut>,
|
||||||
|
_s: PhantomData<S>,
|
||||||
|
}
|
||||||
|
|
||||||
|
type Fut = Box<Future<Item=Option<HttpResponse>, Error=Error>>;
|
||||||
|
|
||||||
|
impl<S: 'static> StartMiddlewares<S> {
|
||||||
|
|
||||||
|
fn init(info: &mut ComposeInfo<S>) -> ComposeState<S> {
|
||||||
|
let len = info.mws.len();
|
||||||
|
loop {
|
||||||
|
if info.count == len {
|
||||||
|
let reply = info.handler.handle(info.req.clone());
|
||||||
|
return WaitingResponse::init(info, reply)
|
||||||
|
} else {
|
||||||
|
match info.mws[info.count].start(&mut info.req) {
|
||||||
|
Ok(MiddlewareStarted::Done) =>
|
||||||
|
info.count += 1,
|
||||||
|
Ok(MiddlewareStarted::Response(resp)) =>
|
||||||
|
return RunMiddlewares::init(info, resp),
|
||||||
|
Ok(MiddlewareStarted::Future(mut fut)) =>
|
||||||
|
match fut.poll() {
|
||||||
|
Ok(Async::NotReady) =>
|
||||||
|
return ComposeState::Starting(StartMiddlewares {
|
||||||
|
fut: Some(fut),
|
||||||
|
_s: PhantomData}),
|
||||||
|
Ok(Async::Ready(resp)) => {
|
||||||
|
if let Some(resp) = resp {
|
||||||
|
return RunMiddlewares::init(info, resp);
|
||||||
|
}
|
||||||
|
info.count += 1;
|
||||||
|
}
|
||||||
|
Err(err) =>
|
||||||
|
return Response::init(err.into()),
|
||||||
|
},
|
||||||
|
Err(err) =>
|
||||||
|
return Response::init(err.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll(&mut self, info: &mut ComposeInfo<S>) -> Option<ComposeState<S>>
|
||||||
|
{
|
||||||
|
let len = info.mws.len();
|
||||||
|
'outer: loop {
|
||||||
|
match self.fut.as_mut().unwrap().poll() {
|
||||||
|
Ok(Async::NotReady) =>
|
||||||
|
return None,
|
||||||
|
Ok(Async::Ready(resp)) => {
|
||||||
|
info.count += 1;
|
||||||
|
if let Some(resp) = resp {
|
||||||
|
return Some(RunMiddlewares::init(info, resp));
|
||||||
|
}
|
||||||
|
if info.count == len {
|
||||||
|
let reply = info.handler.handle(info.req.clone());
|
||||||
|
return Some(WaitingResponse::init(info, reply));
|
||||||
|
} else {
|
||||||
|
loop {
|
||||||
|
match info.mws[info.count].start(&mut info.req) {
|
||||||
|
Ok(MiddlewareStarted::Done) =>
|
||||||
|
info.count += 1,
|
||||||
|
Ok(MiddlewareStarted::Response(resp)) => {
|
||||||
|
return Some(RunMiddlewares::init(info, resp));
|
||||||
|
},
|
||||||
|
Ok(MiddlewareStarted::Future(fut)) => {
|
||||||
|
self.fut = Some(fut);
|
||||||
|
continue 'outer
|
||||||
|
},
|
||||||
|
Err(err) =>
|
||||||
|
return Some(Response::init(err.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) =>
|
||||||
|
return Some(Response::init(err.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// waiting for response
|
||||||
|
struct WaitingResponse<S> {
|
||||||
|
fut: Box<Future<Item=HttpResponse, Error=Error>>,
|
||||||
|
_s: PhantomData<S>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: 'static> WaitingResponse<S> {
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn init(info: &mut ComposeInfo<S>, reply: Reply) -> ComposeState<S> {
|
||||||
|
match reply.into() {
|
||||||
|
ReplyItem::Message(resp) =>
|
||||||
|
RunMiddlewares::init(info, resp),
|
||||||
|
ReplyItem::Future(fut) =>
|
||||||
|
ComposeState::Handler(
|
||||||
|
WaitingResponse { fut: fut, _s: PhantomData }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll(&mut self, info: &mut ComposeInfo<S>) -> Option<ComposeState<S>> {
|
||||||
|
match self.fut.poll() {
|
||||||
|
Ok(Async::NotReady) => None,
|
||||||
|
Ok(Async::Ready(response)) =>
|
||||||
|
Some(RunMiddlewares::init(info, response)),
|
||||||
|
Err(err) =>
|
||||||
|
Some(Response::init(err.into())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Middlewares response executor
|
||||||
|
struct RunMiddlewares<S> {
|
||||||
|
curr: usize,
|
||||||
|
fut: Option<Box<Future<Item=HttpResponse, Error=Error>>>,
|
||||||
|
_s: PhantomData<S>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: 'static> RunMiddlewares<S> {
|
||||||
|
|
||||||
|
fn init(info: &mut ComposeInfo<S>, mut resp: HttpResponse) -> ComposeState<S> {
|
||||||
|
let mut curr = 0;
|
||||||
|
let len = info.mws.len();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
resp = match info.mws[curr].response(&mut info.req, resp) {
|
||||||
|
Err(err) => {
|
||||||
|
info.count = curr + 1;
|
||||||
|
return Response::init(err.into())
|
||||||
|
},
|
||||||
|
Ok(MiddlewareResponse::Done(r)) => {
|
||||||
|
curr += 1;
|
||||||
|
if curr == len {
|
||||||
|
return Response::init(r)
|
||||||
|
} else {
|
||||||
|
r
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Ok(MiddlewareResponse::Future(fut)) => {
|
||||||
|
return ComposeState::RunMiddlewares(
|
||||||
|
RunMiddlewares { curr: curr, fut: Some(fut), _s: PhantomData })
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll(&mut self, info: &mut ComposeInfo<S>) -> Option<ComposeState<S>>
|
||||||
|
{
|
||||||
|
let len = info.mws.len();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// poll latest fut
|
||||||
|
let mut resp = match self.fut.as_mut().unwrap().poll() {
|
||||||
|
Ok(Async::NotReady) => {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
Ok(Async::Ready(resp)) => {
|
||||||
|
self.curr += 1;
|
||||||
|
resp
|
||||||
|
}
|
||||||
|
Err(err) =>
|
||||||
|
return Some(Response::init(err.into())),
|
||||||
|
};
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if self.curr == len {
|
||||||
|
return Some(Response::init(resp));
|
||||||
|
} else {
|
||||||
|
match info.mws[self.curr].response(&mut info.req, resp) {
|
||||||
|
Err(err) =>
|
||||||
|
return Some(Response::init(err.into())),
|
||||||
|
Ok(MiddlewareResponse::Done(r)) => {
|
||||||
|
self.curr += 1;
|
||||||
|
resp = r
|
||||||
|
},
|
||||||
|
Ok(MiddlewareResponse::Future(fut)) => {
|
||||||
|
self.fut = Some(fut);
|
||||||
|
break
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Response<S> {
|
||||||
|
resp: Option<HttpResponse>,
|
||||||
|
_s: PhantomData<S>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: 'static> Response<S> {
|
||||||
|
|
||||||
|
fn init(resp: HttpResponse) -> ComposeState<S> {
|
||||||
|
ComposeState::Response(
|
||||||
|
Response{resp: Some(resp), _s: PhantomData})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,83 +2,58 @@ use std::{ptr, mem, time, io};
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::net::{SocketAddr, Shutdown};
|
use std::net::{SocketAddr, Shutdown};
|
||||||
|
|
||||||
use bytes::{Bytes, Buf, BufMut};
|
use bytes::{Bytes, BytesMut, Buf, BufMut};
|
||||||
use futures::{Future, Poll, Async};
|
use futures::{Future, Poll, Async};
|
||||||
use tokio_io::{AsyncRead, AsyncWrite};
|
use tokio_io::{AsyncRead, AsyncWrite};
|
||||||
use tokio_core::net::TcpStream;
|
|
||||||
|
|
||||||
use {h1, h2};
|
use super::{h1, h2, utils, HttpHandler, IoStream};
|
||||||
use error::Error;
|
use super::settings::WorkerSettings;
|
||||||
use h1writer::Writer;
|
|
||||||
use httprequest::HttpRequest;
|
|
||||||
use server::ServerSettings;
|
|
||||||
use worker::WorkerSettings;
|
|
||||||
|
|
||||||
/// Low level http request handler
|
const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0";
|
||||||
#[allow(unused_variables)]
|
|
||||||
pub trait HttpHandler: 'static {
|
|
||||||
|
|
||||||
/// Handle request
|
|
||||||
fn handle(&mut self, req: HttpRequest) -> Result<Box<HttpHandlerTask>, HttpRequest>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait HttpHandlerTask {
|
enum HttpProtocol<T: IoStream, H: 'static> {
|
||||||
|
|
||||||
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error>;
|
|
||||||
|
|
||||||
fn poll(&mut self) -> Poll<(), Error>;
|
|
||||||
|
|
||||||
fn disconnected(&mut self);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Conversion helper trait
|
|
||||||
pub trait IntoHttpHandler {
|
|
||||||
/// The associated type which is result of conversion.
|
|
||||||
type Handler: HttpHandler;
|
|
||||||
|
|
||||||
/// Convert into `HttpHandler` object.
|
|
||||||
fn into_handler(self, settings: ServerSettings) -> Self::Handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: HttpHandler> IntoHttpHandler for T {
|
|
||||||
type Handler = T;
|
|
||||||
|
|
||||||
fn into_handler(self, _: ServerSettings) -> Self::Handler {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum HttpProtocol<T: IoStream, H: 'static>
|
|
||||||
{
|
|
||||||
H1(h1::Http1<T, H>),
|
H1(h1::Http1<T, H>),
|
||||||
H2(h2::Http2<T, H>),
|
H2(h2::Http2<T, H>),
|
||||||
|
Unknown(Rc<WorkerSettings<H>>, Option<SocketAddr>, T, BytesMut),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: IoStream, H: 'static> HttpProtocol<T, H> {
|
||||||
|
fn is_unknown(&self) -> bool {
|
||||||
|
match *self {
|
||||||
|
HttpProtocol::Unknown(_, _, _, _) => true,
|
||||||
|
_ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ProtocolKind {
|
||||||
|
Http1,
|
||||||
|
Http2,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub struct HttpChannel<T, H>
|
pub struct HttpChannel<T, H> where T: IoStream, H: HttpHandler + 'static {
|
||||||
where T: IoStream, H: HttpHandler + 'static
|
|
||||||
{
|
|
||||||
proto: Option<HttpProtocol<T, H>>,
|
proto: Option<HttpProtocol<T, H>>,
|
||||||
node: Option<Node<HttpChannel<T, H>>>,
|
node: Option<Node<HttpChannel<T, H>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, H> HttpChannel<T, H>
|
impl<T, H> HttpChannel<T, H> where T: IoStream, H: HttpHandler + 'static
|
||||||
where T: IoStream, H: HttpHandler + 'static
|
|
||||||
{
|
{
|
||||||
pub(crate) fn new(h: Rc<WorkerSettings<H>>,
|
pub(crate) fn new(settings: Rc<WorkerSettings<H>>,
|
||||||
io: T, peer: Option<SocketAddr>, http2: bool) -> HttpChannel<T, H>
|
io: T, peer: Option<SocketAddr>, http2: bool) -> HttpChannel<T, H>
|
||||||
{
|
{
|
||||||
h.add_channel();
|
settings.add_channel();
|
||||||
if http2 {
|
if http2 {
|
||||||
HttpChannel {
|
HttpChannel {
|
||||||
node: None,
|
node: None,
|
||||||
proto: Some(HttpProtocol::H2(
|
proto: Some(HttpProtocol::H2(
|
||||||
h2::Http2::new(h, io, peer, Bytes::new()))) }
|
h2::Http2::new(settings, io, peer, Bytes::new()))) }
|
||||||
} else {
|
} else {
|
||||||
HttpChannel {
|
HttpChannel {
|
||||||
node: None,
|
node: None,
|
||||||
proto: Some(HttpProtocol::H1(
|
proto: Some(HttpProtocol::Unknown(
|
||||||
h1::Http1::new(h, io, peer))) }
|
settings, peer, io, BytesMut::with_capacity(4096))) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,82 +67,90 @@ impl<T, H> HttpChannel<T, H>
|
|||||||
Some(HttpProtocol::H2(ref mut h2)) => {
|
Some(HttpProtocol::H2(ref mut h2)) => {
|
||||||
h2.shutdown()
|
h2.shutdown()
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*impl<T, H> Drop for HttpChannel<T, H>
|
impl<T, H> Future for HttpChannel<T, H> where T: IoStream, H: HttpHandler + 'static
|
||||||
where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static
|
|
||||||
{
|
|
||||||
fn drop(&mut self) {
|
|
||||||
println!("Drop http channel");
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
impl<T, H> Future for HttpChannel<T, H>
|
|
||||||
where T: IoStream, H: HttpHandler + 'static
|
|
||||||
{
|
{
|
||||||
type Item = ();
|
type Item = ();
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||||
if self.node.is_none() {
|
if !self.proto.as_ref().map(|p| p.is_unknown()).unwrap_or(false) && self.node.is_none() {
|
||||||
self.node = Some(Node::new(self));
|
self.node = Some(Node::new(self));
|
||||||
match self.proto {
|
match self.proto {
|
||||||
Some(HttpProtocol::H1(ref mut h1)) => {
|
Some(HttpProtocol::H1(ref mut h1)) =>
|
||||||
h1.settings().head().insert(self.node.as_ref().unwrap());
|
h1.settings().head().insert(self.node.as_ref().unwrap()),
|
||||||
}
|
Some(HttpProtocol::H2(ref mut h2)) =>
|
||||||
Some(HttpProtocol::H2(ref mut h2)) => {
|
h2.settings().head().insert(self.node.as_ref().unwrap()),
|
||||||
h2.settings().head().insert(self.node.as_ref().unwrap());
|
_ => (),
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.proto {
|
let kind = match self.proto {
|
||||||
Some(HttpProtocol::H1(ref mut h1)) => {
|
Some(HttpProtocol::H1(ref mut h1)) => {
|
||||||
match h1.poll() {
|
let result = h1.poll();
|
||||||
Ok(Async::Ready(h1::Http1Result::Done)) => {
|
match result {
|
||||||
|
Ok(Async::Ready(())) | Err(_) => {
|
||||||
h1.settings().remove_channel();
|
h1.settings().remove_channel();
|
||||||
self.node.as_ref().unwrap().remove();
|
self.node.as_ref().unwrap().remove();
|
||||||
return Ok(Async::Ready(()))
|
},
|
||||||
}
|
_ => (),
|
||||||
Ok(Async::Ready(h1::Http1Result::Switch)) => (),
|
|
||||||
Ok(Async::NotReady) =>
|
|
||||||
return Ok(Async::NotReady),
|
|
||||||
Err(_) => {
|
|
||||||
h1.settings().remove_channel();
|
|
||||||
self.node.as_ref().unwrap().remove();
|
|
||||||
return Err(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return result
|
||||||
|
},
|
||||||
Some(HttpProtocol::H2(ref mut h2)) => {
|
Some(HttpProtocol::H2(ref mut h2)) => {
|
||||||
let result = h2.poll();
|
let result = h2.poll();
|
||||||
match result {
|
match result {
|
||||||
Ok(Async::Ready(())) | Err(_) => {
|
Ok(Async::Ready(())) | Err(_) => {
|
||||||
h2.settings().remove_channel();
|
h2.settings().remove_channel();
|
||||||
self.node.as_ref().unwrap().remove();
|
self.node.as_ref().unwrap().remove();
|
||||||
}
|
},
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
},
|
||||||
None => unreachable!(),
|
Some(HttpProtocol::Unknown(ref mut settings, _, ref mut io, ref mut buf)) => {
|
||||||
|
match utils::read_from_io(io, buf) {
|
||||||
|
Ok(Async::Ready(0)) | Err(_) => {
|
||||||
|
debug!("Ignored premature client disconnection");
|
||||||
|
settings.remove_channel();
|
||||||
|
return Err(())
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
// upgrade to h2
|
if buf.len() >= 14 {
|
||||||
let proto = self.proto.take().unwrap();
|
if buf[..14] == HTTP2_PREFACE[..] {
|
||||||
match proto {
|
ProtocolKind::Http2
|
||||||
HttpProtocol::H1(h1) => {
|
} else {
|
||||||
let (h, io, addr, buf) = h1.into_inner();
|
ProtocolKind::Http1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Ok(Async::NotReady);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// upgrade to specific http protocol
|
||||||
|
if let Some(HttpProtocol::Unknown(settings, addr, io, buf)) = self.proto.take() {
|
||||||
|
match kind {
|
||||||
|
ProtocolKind::Http1 => {
|
||||||
self.proto = Some(
|
self.proto = Some(
|
||||||
HttpProtocol::H2(h2::Http2::new(h, io, addr, buf)));
|
HttpProtocol::H1(h1::Http1::new(settings, io, addr, buf)));
|
||||||
self.poll()
|
return self.poll()
|
||||||
|
},
|
||||||
|
ProtocolKind::Http2 => {
|
||||||
|
self.proto = Some(
|
||||||
|
HttpProtocol::H2(h2::Http2::new(settings, io, addr, buf.freeze())));
|
||||||
|
return self.poll()
|
||||||
|
},
|
||||||
}
|
}
|
||||||
_ => unreachable!()
|
|
||||||
}
|
}
|
||||||
|
unreachable!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,77 +230,40 @@ impl Node<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Low-level io stream operations
|
|
||||||
pub trait IoStream: AsyncRead + AsyncWrite + 'static {
|
|
||||||
fn shutdown(&mut self, how: Shutdown) -> io::Result<()>;
|
|
||||||
|
|
||||||
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()>;
|
|
||||||
|
|
||||||
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IoStream for TcpStream {
|
|
||||||
#[inline]
|
|
||||||
fn shutdown(&mut self, how: Shutdown) -> io::Result<()> {
|
|
||||||
TcpStream::shutdown(self, how)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
|
|
||||||
TcpStream::set_nodelay(self, nodelay)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
|
|
||||||
TcpStream::set_linger(self, dur)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Wrapper for `AsyncRead + AsyncWrite` types
|
/// Wrapper for `AsyncRead + AsyncWrite` types
|
||||||
pub(crate) struct WrapperStream<T> where T: AsyncRead + AsyncWrite + 'static {
|
pub(crate) struct WrapperStream<T> where T: AsyncRead + AsyncWrite + 'static {
|
||||||
io: T,
|
io: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> WrapperStream<T> where T: AsyncRead + AsyncWrite + 'static
|
impl<T> WrapperStream<T> where T: AsyncRead + AsyncWrite + 'static {
|
||||||
{
|
|
||||||
pub fn new(io: T) -> Self {
|
pub fn new(io: T) -> Self {
|
||||||
WrapperStream{io: io}
|
WrapperStream{io: io}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> IoStream for WrapperStream<T>
|
impl<T> IoStream for WrapperStream<T> where T: AsyncRead + AsyncWrite + 'static {
|
||||||
where T: AsyncRead + AsyncWrite + 'static
|
|
||||||
{
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn shutdown(&mut self, _: Shutdown) -> io::Result<()> {
|
fn shutdown(&mut self, _: Shutdown) -> io::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn set_nodelay(&mut self, _: bool) -> io::Result<()> {
|
fn set_nodelay(&mut self, _: bool) -> io::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn set_linger(&mut self, _: Option<time::Duration>) -> io::Result<()> {
|
fn set_linger(&mut self, _: Option<time::Duration>) -> io::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> io::Read for WrapperStream<T>
|
impl<T> io::Read for WrapperStream<T> where T: AsyncRead + AsyncWrite + 'static {
|
||||||
where T: AsyncRead + AsyncWrite + 'static
|
|
||||||
{
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
self.io.read(buf)
|
self.io.read(buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> io::Write for WrapperStream<T>
|
impl<T> io::Write for WrapperStream<T> where T: AsyncRead + AsyncWrite + 'static {
|
||||||
where T: AsyncRead + AsyncWrite + 'static
|
|
||||||
{
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
self.io.write(buf)
|
self.io.write(buf)
|
||||||
@ -328,66 +274,20 @@ impl<T> io::Write for WrapperStream<T>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> AsyncRead for WrapperStream<T>
|
impl<T> AsyncRead for WrapperStream<T> where T: AsyncRead + AsyncWrite + 'static {
|
||||||
where T: AsyncRead + AsyncWrite + 'static
|
#[inline]
|
||||||
{
|
|
||||||
fn read_buf<B: BufMut>(&mut self, buf: &mut B) -> Poll<usize, io::Error> {
|
fn read_buf<B: BufMut>(&mut self, buf: &mut B) -> Poll<usize, io::Error> {
|
||||||
self.io.read_buf(buf)
|
self.io.read_buf(buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> AsyncWrite for WrapperStream<T>
|
impl<T> AsyncWrite for WrapperStream<T> where T: AsyncRead + AsyncWrite + 'static {
|
||||||
where T: AsyncRead + AsyncWrite + 'static
|
#[inline]
|
||||||
{
|
|
||||||
fn shutdown(&mut self) -> Poll<(), io::Error> {
|
fn shutdown(&mut self) -> Poll<(), io::Error> {
|
||||||
self.io.shutdown()
|
self.io.shutdown()
|
||||||
}
|
}
|
||||||
|
#[inline]
|
||||||
fn write_buf<B: Buf>(&mut self, buf: &mut B) -> Poll<usize, io::Error> {
|
fn write_buf<B: Buf>(&mut self, buf: &mut B) -> Poll<usize, io::Error> {
|
||||||
self.io.write_buf(buf)
|
self.io.write_buf(buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(feature="alpn")]
|
|
||||||
use tokio_openssl::SslStream;
|
|
||||||
|
|
||||||
#[cfg(feature="alpn")]
|
|
||||||
impl IoStream for SslStream<TcpStream> {
|
|
||||||
#[inline]
|
|
||||||
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
|
|
||||||
let _ = self.get_mut().shutdown();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
|
|
||||||
self.get_mut().get_mut().set_nodelay(nodelay)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
|
|
||||||
self.get_mut().get_mut().set_linger(dur)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature="tls")]
|
|
||||||
use tokio_tls::TlsStream;
|
|
||||||
|
|
||||||
#[cfg(feature="tls")]
|
|
||||||
impl IoStream for TlsStream<TcpStream> {
|
|
||||||
#[inline]
|
|
||||||
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
|
|
||||||
let _ = self.get_mut().shutdown();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
|
|
||||||
self.get_mut().get_mut().set_nodelay(nodelay)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
|
|
||||||
self.get_mut().get_mut().set_linger(dur)
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,40 +3,29 @@ use std::io::{Read, Write};
|
|||||||
use std::fmt::Write as FmtWrite;
|
use std::fmt::Write as FmtWrite;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use http::Version;
|
use http::{Version, Method, HttpTryFrom};
|
||||||
use http::header::{HeaderMap, HeaderValue,
|
use http::header::{HeaderMap, HeaderValue,
|
||||||
ACCEPT_ENCODING, CONNECTION,
|
ACCEPT_ENCODING, CONNECTION,
|
||||||
CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING};
|
CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING};
|
||||||
use flate2::Compression;
|
use flate2::Compression;
|
||||||
use flate2::read::{GzDecoder};
|
use flate2::read::GzDecoder;
|
||||||
use flate2::write::{GzEncoder, DeflateDecoder, DeflateEncoder};
|
use flate2::write::{GzEncoder, DeflateDecoder, DeflateEncoder};
|
||||||
use brotli2::write::{BrotliDecoder, BrotliEncoder};
|
use brotli2::write::{BrotliDecoder, BrotliEncoder};
|
||||||
use bytes::{Bytes, BytesMut, BufMut, Writer};
|
use bytes::{Bytes, BytesMut, BufMut, Writer};
|
||||||
|
|
||||||
|
use headers::ContentEncoding;
|
||||||
use body::{Body, Binary};
|
use body::{Body, Binary};
|
||||||
use error::PayloadError;
|
use error::PayloadError;
|
||||||
use helpers::SharedBytes;
|
|
||||||
use httprequest::HttpMessage;
|
use httprequest::HttpMessage;
|
||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
use payload::{PayloadSender, PayloadWriter};
|
use payload::{PayloadSender, PayloadWriter};
|
||||||
|
|
||||||
/// Represents supported types of content encodings
|
use super::shared::SharedBytes;
|
||||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
|
||||||
pub enum ContentEncoding {
|
|
||||||
/// Automatically select encoding based on encoding negotiation
|
|
||||||
Auto,
|
|
||||||
/// A format using the Brotli algorithm
|
|
||||||
Br,
|
|
||||||
/// A format using the zlib structure with deflate algorithm
|
|
||||||
Deflate,
|
|
||||||
/// Gzip algorithm
|
|
||||||
Gzip,
|
|
||||||
/// Indicates the identity function (i.e. no compression, nor modification)
|
|
||||||
Identity,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ContentEncoding {
|
impl ContentEncoding {
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn is_compression(&self) -> bool {
|
fn is_compression(&self) -> bool {
|
||||||
match *self {
|
match *self {
|
||||||
ContentEncoding::Identity | ContentEncoding::Auto => false,
|
ContentEncoding::Identity | ContentEncoding::Auto => false,
|
||||||
@ -52,7 +41,7 @@ impl ContentEncoding {
|
|||||||
ContentEncoding::Identity | ContentEncoding::Auto => "identity",
|
ContentEncoding::Identity | ContentEncoding::Auto => "identity",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// default quality
|
/// default quality value
|
||||||
fn quality(&self) -> f64 {
|
fn quality(&self) -> f64 {
|
||||||
match *self {
|
match *self {
|
||||||
ContentEncoding::Br => 1.1,
|
ContentEncoding::Br => 1.1,
|
||||||
@ -63,6 +52,7 @@ impl ContentEncoding {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: remove memory allocation
|
||||||
impl<'a> From<&'a str> for ContentEncoding {
|
impl<'a> From<&'a str> for ContentEncoding {
|
||||||
fn from(s: &'a str) -> ContentEncoding {
|
fn from(s: &'a str) -> ContentEncoding {
|
||||||
match s.trim().to_lowercase().as_ref() {
|
match s.trim().to_lowercase().as_ref() {
|
||||||
@ -135,7 +125,7 @@ impl PayloadWriter for PayloadType {
|
|||||||
|
|
||||||
enum Decoder {
|
enum Decoder {
|
||||||
Deflate(Box<DeflateDecoder<Writer<BytesMut>>>),
|
Deflate(Box<DeflateDecoder<Writer<BytesMut>>>),
|
||||||
Gzip(Box<Option<GzDecoder<Wrapper>>>),
|
Gzip(Option<Box<GzDecoder<Wrapper>>>),
|
||||||
Br(Box<BrotliDecoder<Writer<BytesMut>>>),
|
Br(Box<BrotliDecoder<Writer<BytesMut>>>),
|
||||||
Identity,
|
Identity,
|
||||||
}
|
}
|
||||||
@ -143,7 +133,8 @@ enum Decoder {
|
|||||||
// should go after write::GzDecoder get implemented
|
// should go after write::GzDecoder get implemented
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Wrapper {
|
struct Wrapper {
|
||||||
buf: BytesMut
|
buf: BytesMut,
|
||||||
|
eof: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl io::Read for Wrapper {
|
impl io::Read for Wrapper {
|
||||||
@ -151,15 +142,33 @@ impl io::Read for Wrapper {
|
|||||||
let len = cmp::min(buf.len(), self.buf.len());
|
let len = cmp::min(buf.len(), self.buf.len());
|
||||||
buf[..len].copy_from_slice(&self.buf[..len]);
|
buf[..len].copy_from_slice(&self.buf[..len]);
|
||||||
self.buf.split_to(len);
|
self.buf.split_to(len);
|
||||||
|
if len == 0 {
|
||||||
|
if self.eof {
|
||||||
|
Ok(0)
|
||||||
|
} else {
|
||||||
|
Err(io::Error::new(io::ErrorKind::WouldBlock, ""))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
Ok(len)
|
Ok(len)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl io::Write for Wrapper {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
|
self.buf.extend_from_slice(buf);
|
||||||
|
Ok(buf.len())
|
||||||
|
}
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Payload wrapper with content decompression support
|
/// Payload wrapper with content decompression support
|
||||||
pub(crate) struct EncodedPayload {
|
pub(crate) struct EncodedPayload {
|
||||||
inner: PayloadSender,
|
inner: PayloadSender,
|
||||||
decoder: Decoder,
|
decoder: Decoder,
|
||||||
dst: Writer<BytesMut>,
|
dst: BytesMut,
|
||||||
error: bool,
|
error: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,15 +179,10 @@ impl EncodedPayload {
|
|||||||
Box::new(BrotliDecoder::new(BytesMut::with_capacity(8192).writer()))),
|
Box::new(BrotliDecoder::new(BytesMut::with_capacity(8192).writer()))),
|
||||||
ContentEncoding::Deflate => Decoder::Deflate(
|
ContentEncoding::Deflate => Decoder::Deflate(
|
||||||
Box::new(DeflateDecoder::new(BytesMut::with_capacity(8192).writer()))),
|
Box::new(DeflateDecoder::new(BytesMut::with_capacity(8192).writer()))),
|
||||||
ContentEncoding::Gzip => Decoder::Gzip(Box::new(None)),
|
ContentEncoding::Gzip => Decoder::Gzip(None),
|
||||||
_ => Decoder::Identity,
|
_ => Decoder::Identity,
|
||||||
};
|
};
|
||||||
EncodedPayload {
|
EncodedPayload{ inner: inner, decoder: dec, error: false, dst: BytesMut::new() }
|
||||||
inner: inner,
|
|
||||||
decoder: dec,
|
|
||||||
error: false,
|
|
||||||
dst: BytesMut::new().writer(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,30 +211,29 @@ impl PayloadWriter for EncodedPayload {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
Decoder::Gzip(ref mut decoder) => {
|
Decoder::Gzip(ref mut decoder) => {
|
||||||
if decoder.is_none() {
|
if let Some(ref mut decoder) = *decoder {
|
||||||
self.inner.feed_eof();
|
decoder.as_mut().get_mut().eof = true;
|
||||||
return
|
|
||||||
}
|
|
||||||
loop {
|
|
||||||
let len = self.dst.get_ref().len();
|
|
||||||
let len_buf = decoder.as_mut().as_mut().unwrap().get_mut().buf.len();
|
|
||||||
|
|
||||||
if len < len_buf * 2 {
|
loop {
|
||||||
self.dst.get_mut().reserve(len_buf * 2 - len);
|
self.dst.reserve(8192);
|
||||||
unsafe{self.dst.get_mut().set_len(len_buf * 2)};
|
match decoder.read(unsafe{self.dst.bytes_mut()}) {
|
||||||
}
|
|
||||||
match decoder.as_mut().as_mut().unwrap().read(&mut self.dst.get_mut()) {
|
|
||||||
Ok(n) => {
|
Ok(n) => {
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
self.inner.feed_eof();
|
self.inner.feed_eof();
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
self.inner.feed_data(self.dst.get_mut().split_to(n).freeze());
|
unsafe{self.dst.set_len(n)};
|
||||||
|
self.inner.feed_data(self.dst.split_to(n).freeze());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => break Some(err)
|
Err(err) => {
|
||||||
|
break Some(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Decoder::Deflate(ref mut decoder) => {
|
Decoder::Deflate(ref mut decoder) => {
|
||||||
match decoder.try_finish() {
|
match decoder.try_finish() {
|
||||||
@ -278,33 +281,30 @@ impl PayloadWriter for EncodedPayload {
|
|||||||
|
|
||||||
Decoder::Gzip(ref mut decoder) => {
|
Decoder::Gzip(ref mut decoder) => {
|
||||||
if decoder.is_none() {
|
if decoder.is_none() {
|
||||||
let mut buf = BytesMut::new();
|
*decoder = Some(
|
||||||
buf.extend_from_slice(&data);
|
Box::new(GzDecoder::new(
|
||||||
*(decoder.as_mut()) = Some(GzDecoder::new(Wrapper{buf: buf}).unwrap());
|
Wrapper{buf: BytesMut::from(data), eof: false})));
|
||||||
} else {
|
} else {
|
||||||
decoder.as_mut().as_mut().unwrap().get_mut().buf.extend_from_slice(&data);
|
let _ = decoder.as_mut().unwrap().write(&data);
|
||||||
}
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let len_buf = decoder.as_mut().as_mut().unwrap().get_mut().buf.len();
|
self.dst.reserve(8192);
|
||||||
if len_buf == 0 {
|
match decoder.as_mut().as_mut().unwrap().read(unsafe{self.dst.bytes_mut()}) {
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let len = self.dst.get_ref().len();
|
|
||||||
if len < len_buf * 2 {
|
|
||||||
self.dst.get_mut().reserve(len_buf * 2 - len);
|
|
||||||
unsafe{self.dst.get_mut().set_len(len_buf * 2)};
|
|
||||||
}
|
|
||||||
match decoder.as_mut().as_mut().unwrap().read(&mut self.dst.get_mut()) {
|
|
||||||
Ok(n) => {
|
Ok(n) => {
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
self.inner.feed_data(self.dst.get_mut().split_to(n).freeze());
|
unsafe{self.dst.set_len(n)};
|
||||||
|
self.inner.feed_data(self.dst.split_to(n).freeze());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => break
|
Err(e) => {
|
||||||
|
if e.kind() == io::ErrorKind::WouldBlock {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -343,20 +343,20 @@ impl PayloadEncoder {
|
|||||||
PayloadEncoder(ContentEncoder::Identity(TransferEncoding::eof(bytes)))
|
PayloadEncoder(ContentEncoder::Identity(TransferEncoding::eof(bytes)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(buf: SharedBytes, req: &HttpMessage, resp: &mut HttpResponse)
|
pub fn new(buf: SharedBytes, req: &HttpMessage, resp: &mut HttpResponse) -> PayloadEncoder {
|
||||||
-> PayloadEncoder
|
|
||||||
{
|
|
||||||
let version = resp.version().unwrap_or_else(|| req.version);
|
let version = resp.version().unwrap_or_else(|| req.version);
|
||||||
let mut body = resp.replace_body(Body::Empty);
|
let mut body = resp.replace_body(Body::Empty);
|
||||||
|
let response_encoding = resp.content_encoding();
|
||||||
let has_body = match body {
|
let has_body = match body {
|
||||||
Body::Empty => false,
|
Body::Empty => false,
|
||||||
Body::Binary(ref bin) => bin.len() >= 1024,
|
Body::Binary(ref bin) =>
|
||||||
|
!(response_encoding == ContentEncoding::Auto && bin.len() < 96),
|
||||||
_ => true,
|
_ => true,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Enable content encoding only if response does not contain Content-Encoding header
|
// Enable content encoding only if response does not contain Content-Encoding header
|
||||||
let mut encoding = if has_body && !resp.headers().contains_key(CONTENT_ENCODING) {
|
let mut encoding = if has_body {
|
||||||
let encoding = match *resp.content_encoding() {
|
let encoding = match response_encoding {
|
||||||
ContentEncoding::Auto => {
|
ContentEncoding::Auto => {
|
||||||
// negotiate content-encoding
|
// negotiate content-encoding
|
||||||
if let Some(val) = req.headers.get(ACCEPT_ENCODING) {
|
if let Some(val) = req.headers.get(ACCEPT_ENCODING) {
|
||||||
@ -380,40 +380,42 @@ impl PayloadEncoder {
|
|||||||
ContentEncoding::Identity
|
ContentEncoding::Identity
|
||||||
};
|
};
|
||||||
|
|
||||||
// in general case it is very expensive to get compressed payload length,
|
let mut transfer = match body {
|
||||||
// just switch to chunked encoding
|
|
||||||
let compression = encoding != ContentEncoding::Identity;
|
|
||||||
|
|
||||||
let transfer = match body {
|
|
||||||
Body::Empty => {
|
Body::Empty => {
|
||||||
if resp.chunked() {
|
if req.method != Method::HEAD {
|
||||||
error!("Chunked transfer is enabled but body is set to Empty");
|
|
||||||
}
|
|
||||||
resp.headers_mut().remove(CONTENT_LENGTH);
|
resp.headers_mut().remove(CONTENT_LENGTH);
|
||||||
TransferEncoding::eof(buf)
|
}
|
||||||
|
TransferEncoding::length(0, buf)
|
||||||
},
|
},
|
||||||
Body::Binary(ref mut bytes) => {
|
Body::Binary(ref mut bytes) => {
|
||||||
if compression {
|
if encoding.is_compression() {
|
||||||
let buf = SharedBytes::default();
|
let tmp = SharedBytes::default();
|
||||||
let transfer = TransferEncoding::eof(buf.clone());
|
let transfer = TransferEncoding::eof(tmp.clone());
|
||||||
let mut enc = match encoding {
|
let mut enc = match encoding {
|
||||||
ContentEncoding::Deflate => ContentEncoder::Deflate(
|
ContentEncoding::Deflate => ContentEncoder::Deflate(
|
||||||
DeflateEncoder::new(transfer, Compression::Default)),
|
DeflateEncoder::new(transfer, Compression::default())),
|
||||||
ContentEncoding::Gzip => ContentEncoder::Gzip(
|
ContentEncoding::Gzip => ContentEncoder::Gzip(
|
||||||
GzEncoder::new(transfer, Compression::Default)),
|
GzEncoder::new(transfer, Compression::default())),
|
||||||
ContentEncoding::Br => ContentEncoder::Br(
|
ContentEncoding::Br => ContentEncoder::Br(
|
||||||
BrotliEncoder::new(transfer, 5)),
|
BrotliEncoder::new(transfer, 5)),
|
||||||
ContentEncoding::Identity => ContentEncoder::Identity(transfer),
|
ContentEncoding::Identity => ContentEncoder::Identity(transfer),
|
||||||
ContentEncoding::Auto => unreachable!()
|
ContentEncoding::Auto => unreachable!()
|
||||||
};
|
};
|
||||||
// TODO return error!
|
// TODO return error!
|
||||||
let _ = enc.write(bytes.as_ref());
|
let _ = enc.write(bytes.clone());
|
||||||
let _ = enc.write_eof();
|
let _ = enc.write_eof();
|
||||||
|
|
||||||
*bytes = Binary::from(buf.get_mut().take());
|
*bytes = Binary::from(tmp.take());
|
||||||
encoding = ContentEncoding::Identity;
|
encoding = ContentEncoding::Identity;
|
||||||
}
|
}
|
||||||
|
if req.method == Method::HEAD {
|
||||||
|
let mut b = BytesMut::new();
|
||||||
|
let _ = write!(b, "{}", bytes.len());
|
||||||
|
resp.headers_mut().insert(
|
||||||
|
CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap());
|
||||||
|
} else {
|
||||||
resp.headers_mut().remove(CONTENT_LENGTH);
|
resp.headers_mut().remove(CONTENT_LENGTH);
|
||||||
|
}
|
||||||
TransferEncoding::eof(buf)
|
TransferEncoding::eof(buf)
|
||||||
}
|
}
|
||||||
Body::Streaming(_) | Body::Actor(_) => {
|
Body::Streaming(_) | Body::Actor(_) => {
|
||||||
@ -429,11 +431,38 @@ impl PayloadEncoder {
|
|||||||
resp.headers_mut().remove(CONTENT_ENCODING);
|
resp.headers_mut().remove(CONTENT_ENCODING);
|
||||||
}
|
}
|
||||||
TransferEncoding::eof(buf)
|
TransferEncoding::eof(buf)
|
||||||
} else if resp.chunked() {
|
} else {
|
||||||
resp.headers_mut().remove(CONTENT_LENGTH);
|
PayloadEncoder::streaming_encoding(buf, version, resp)
|
||||||
if version != Version::HTTP_11 {
|
|
||||||
error!("Chunked transfer encoding is forbidden for {:?}", version);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//
|
||||||
|
if req.method == Method::HEAD {
|
||||||
|
transfer.kind = TransferEncodingKind::Length(0);
|
||||||
|
} else {
|
||||||
|
resp.replace_body(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
PayloadEncoder(
|
||||||
|
match encoding {
|
||||||
|
ContentEncoding::Deflate => ContentEncoder::Deflate(
|
||||||
|
DeflateEncoder::new(transfer, Compression::default())),
|
||||||
|
ContentEncoding::Gzip => ContentEncoder::Gzip(
|
||||||
|
GzEncoder::new(transfer, Compression::default())),
|
||||||
|
ContentEncoding::Br => ContentEncoder::Br(
|
||||||
|
BrotliEncoder::new(transfer, 5)),
|
||||||
|
ContentEncoding::Identity => ContentEncoder::Identity(transfer),
|
||||||
|
ContentEncoding::Auto => unreachable!()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn streaming_encoding(buf: SharedBytes, version: Version,
|
||||||
|
resp: &mut HttpResponse) -> TransferEncoding {
|
||||||
|
match resp.chunked() {
|
||||||
|
Some(true) => {
|
||||||
|
// Enable transfer encoding
|
||||||
|
resp.headers_mut().remove(CONTENT_LENGTH);
|
||||||
if version == Version::HTTP_2 {
|
if version == Version::HTTP_2 {
|
||||||
resp.headers_mut().remove(TRANSFER_ENCODING);
|
resp.headers_mut().remove(TRANSFER_ENCODING);
|
||||||
TransferEncoding::eof(buf)
|
TransferEncoding::eof(buf)
|
||||||
@ -442,53 +471,56 @@ impl PayloadEncoder {
|
|||||||
TRANSFER_ENCODING, HeaderValue::from_static("chunked"));
|
TRANSFER_ENCODING, HeaderValue::from_static("chunked"));
|
||||||
TransferEncoding::chunked(buf)
|
TransferEncoding::chunked(buf)
|
||||||
}
|
}
|
||||||
} else if let Some(len) = resp.headers().get(CONTENT_LENGTH) {
|
},
|
||||||
|
Some(false) =>
|
||||||
|
TransferEncoding::eof(buf),
|
||||||
|
None => {
|
||||||
|
// if Content-Length is specified, then use it as length hint
|
||||||
|
let (len, chunked) =
|
||||||
|
if let Some(len) = resp.headers().get(CONTENT_LENGTH) {
|
||||||
// Content-Length
|
// Content-Length
|
||||||
if let Ok(s) = len.to_str() {
|
if let Ok(s) = len.to_str() {
|
||||||
if let Ok(len) = s.parse::<u64>() {
|
if let Ok(len) = s.parse::<u64>() {
|
||||||
|
(Some(len), false)
|
||||||
|
} else {
|
||||||
|
error!("illegal Content-Length: {:?}", len);
|
||||||
|
(None, false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error!("illegal Content-Length: {:?}", len);
|
||||||
|
(None, false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(None, true)
|
||||||
|
};
|
||||||
|
|
||||||
|
if !chunked {
|
||||||
|
if let Some(len) = len {
|
||||||
TransferEncoding::length(len, buf)
|
TransferEncoding::length(len, buf)
|
||||||
} else {
|
|
||||||
debug!("illegal Content-Length: {:?}", len);
|
|
||||||
TransferEncoding::eof(buf)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
TransferEncoding::eof(buf)
|
TransferEncoding::eof(buf)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Enable transfer encoding
|
||||||
|
match version {
|
||||||
|
Version::HTTP_11 => {
|
||||||
|
resp.headers_mut().insert(
|
||||||
|
TRANSFER_ENCODING, HeaderValue::from_static("chunked"));
|
||||||
|
TransferEncoding::chunked(buf)
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
resp.headers_mut().remove(TRANSFER_ENCODING);
|
||||||
TransferEncoding::eof(buf)
|
TransferEncoding::eof(buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
resp.replace_body(body);
|
|
||||||
|
|
||||||
PayloadEncoder(
|
|
||||||
match encoding {
|
|
||||||
ContentEncoding::Deflate => ContentEncoder::Deflate(
|
|
||||||
DeflateEncoder::new(transfer, Compression::Default)),
|
|
||||||
ContentEncoding::Gzip => ContentEncoder::Gzip(
|
|
||||||
GzEncoder::new(transfer, Compression::Default)),
|
|
||||||
ContentEncoding::Br => ContentEncoder::Br(
|
|
||||||
BrotliEncoder::new(transfer, 5)),
|
|
||||||
ContentEncoding::Identity => ContentEncoder::Identity(transfer),
|
|
||||||
ContentEncoding::Auto =>
|
|
||||||
unreachable!()
|
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PayloadEncoder {
|
impl PayloadEncoder {
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn len(&self) -> usize {
|
|
||||||
self.0.get_ref().len()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn get_mut(&mut self) -> &mut BytesMut {
|
|
||||||
self.0.get_mut()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_eof(&self) -> bool {
|
pub fn is_eof(&self) -> bool {
|
||||||
self.0.is_eof()
|
self.0.is_eof()
|
||||||
@ -496,7 +528,7 @@ impl PayloadEncoder {
|
|||||||
|
|
||||||
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
|
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn write(&mut self, payload: &[u8]) -> Result<(), io::Error> {
|
pub fn write(&mut self, payload: Binary) -> Result<(), io::Error> {
|
||||||
self.0.write(payload)
|
self.0.write(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -519,42 +551,10 @@ impl ContentEncoder {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_eof(&self) -> bool {
|
pub fn is_eof(&self) -> bool {
|
||||||
match *self {
|
match *self {
|
||||||
ContentEncoder::Br(ref encoder) =>
|
ContentEncoder::Br(ref encoder) => encoder.get_ref().is_eof(),
|
||||||
encoder.get_ref().is_eof(),
|
ContentEncoder::Deflate(ref encoder) => encoder.get_ref().is_eof(),
|
||||||
ContentEncoder::Deflate(ref encoder) =>
|
ContentEncoder::Gzip(ref encoder) => encoder.get_ref().is_eof(),
|
||||||
encoder.get_ref().is_eof(),
|
ContentEncoder::Identity(ref encoder) => encoder.is_eof(),
|
||||||
ContentEncoder::Gzip(ref encoder) =>
|
|
||||||
encoder.get_ref().is_eof(),
|
|
||||||
ContentEncoder::Identity(ref encoder) =>
|
|
||||||
encoder.is_eof(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn get_ref(&self) -> &BytesMut {
|
|
||||||
match *self {
|
|
||||||
ContentEncoder::Br(ref encoder) =>
|
|
||||||
encoder.get_ref().buffer.get_ref(),
|
|
||||||
ContentEncoder::Deflate(ref encoder) =>
|
|
||||||
encoder.get_ref().buffer.get_ref(),
|
|
||||||
ContentEncoder::Gzip(ref encoder) =>
|
|
||||||
encoder.get_ref().buffer.get_ref(),
|
|
||||||
ContentEncoder::Identity(ref encoder) =>
|
|
||||||
encoder.buffer.get_ref(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn get_mut(&mut self) -> &mut BytesMut {
|
|
||||||
match *self {
|
|
||||||
ContentEncoder::Br(ref mut encoder) =>
|
|
||||||
encoder.get_mut().buffer.get_mut(),
|
|
||||||
ContentEncoder::Deflate(ref mut encoder) =>
|
|
||||||
encoder.get_mut().buffer.get_mut(),
|
|
||||||
ContentEncoder::Gzip(ref mut encoder) =>
|
|
||||||
encoder.get_mut().buffer.get_mut(),
|
|
||||||
ContentEncoder::Identity(ref mut encoder) =>
|
|
||||||
encoder.buffer.get_mut(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -605,10 +605,10 @@ impl ContentEncoder {
|
|||||||
|
|
||||||
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
|
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn write(&mut self, data: &[u8]) -> Result<(), io::Error> {
|
pub fn write(&mut self, data: Binary) -> Result<(), io::Error> {
|
||||||
match *self {
|
match *self {
|
||||||
ContentEncoder::Br(ref mut encoder) => {
|
ContentEncoder::Br(ref mut encoder) => {
|
||||||
match encoder.write(data) {
|
match encoder.write(data.as_ref()) {
|
||||||
Ok(_) =>
|
Ok(_) =>
|
||||||
encoder.flush(),
|
encoder.flush(),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@ -618,7 +618,7 @@ impl ContentEncoder {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
ContentEncoder::Gzip(ref mut encoder) => {
|
ContentEncoder::Gzip(ref mut encoder) => {
|
||||||
match encoder.write(data) {
|
match encoder.write(data.as_ref()) {
|
||||||
Ok(_) =>
|
Ok(_) =>
|
||||||
encoder.flush(),
|
encoder.flush(),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@ -628,7 +628,7 @@ impl ContentEncoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ContentEncoder::Deflate(ref mut encoder) => {
|
ContentEncoder::Deflate(ref mut encoder) => {
|
||||||
match encoder.write(data) {
|
match encoder.write(data.as_ref()) {
|
||||||
Ok(_) =>
|
Ok(_) =>
|
||||||
encoder.flush(),
|
encoder.flush(),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@ -662,7 +662,7 @@ enum TransferEncodingKind {
|
|||||||
Length(u64),
|
Length(u64),
|
||||||
/// An Encoder for when Content-Length is not known.
|
/// An Encoder for when Content-Length is not known.
|
||||||
///
|
///
|
||||||
/// Appliction decides when to stop writing.
|
/// Application decides when to stop writing.
|
||||||
Eof,
|
Eof,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -696,20 +696,19 @@ impl TransferEncoding {
|
|||||||
pub fn is_eof(&self) -> bool {
|
pub fn is_eof(&self) -> bool {
|
||||||
match self.kind {
|
match self.kind {
|
||||||
TransferEncodingKind::Eof => true,
|
TransferEncodingKind::Eof => true,
|
||||||
TransferEncodingKind::Chunked(ref eof) =>
|
TransferEncodingKind::Chunked(ref eof) => *eof,
|
||||||
*eof,
|
TransferEncodingKind::Length(ref remaining) => *remaining == 0,
|
||||||
TransferEncodingKind::Length(ref remaining) =>
|
|
||||||
*remaining == 0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encode message. Return `EOF` state of encoder
|
/// Encode message. Return `EOF` state of encoder
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn encode(&mut self, msg: &[u8]) -> io::Result<bool> {
|
pub fn encode(&mut self, mut msg: Binary) -> io::Result<bool> {
|
||||||
match self.kind {
|
match self.kind {
|
||||||
TransferEncodingKind::Eof => {
|
TransferEncodingKind::Eof => {
|
||||||
self.buffer.get_mut().extend_from_slice(msg);
|
let eof = msg.is_empty();
|
||||||
Ok(msg.is_empty())
|
self.buffer.extend(msg);
|
||||||
|
Ok(eof)
|
||||||
},
|
},
|
||||||
TransferEncodingKind::Chunked(ref mut eof) => {
|
TransferEncodingKind::Chunked(ref mut eof) => {
|
||||||
if *eof {
|
if *eof {
|
||||||
@ -718,24 +717,31 @@ impl TransferEncoding {
|
|||||||
|
|
||||||
if msg.is_empty() {
|
if msg.is_empty() {
|
||||||
*eof = true;
|
*eof = true;
|
||||||
self.buffer.get_mut().extend_from_slice(b"0\r\n\r\n");
|
self.buffer.extend_from_slice(b"0\r\n\r\n");
|
||||||
} else {
|
} else {
|
||||||
write!(self.buffer.get_mut(), "{:X}\r\n", msg.len())
|
let mut buf = BytesMut::new();
|
||||||
|
write!(&mut buf, "{:X}\r\n", msg.len())
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||||
self.buffer.get_mut().extend_from_slice(msg);
|
self.buffer.reserve(buf.len() + msg.len() + 2);
|
||||||
self.buffer.get_mut().extend_from_slice(b"\r\n");
|
self.buffer.extend(buf.into());
|
||||||
|
self.buffer.extend(msg);
|
||||||
|
self.buffer.extend_from_slice(b"\r\n");
|
||||||
}
|
}
|
||||||
Ok(*eof)
|
Ok(*eof)
|
||||||
},
|
},
|
||||||
TransferEncodingKind::Length(ref mut remaining) => {
|
TransferEncodingKind::Length(ref mut remaining) => {
|
||||||
|
if *remaining > 0 {
|
||||||
if msg.is_empty() {
|
if msg.is_empty() {
|
||||||
return Ok(*remaining == 0)
|
return Ok(*remaining == 0)
|
||||||
}
|
}
|
||||||
let max = cmp::min(*remaining, msg.len() as u64);
|
let len = cmp::min(*remaining, msg.len() as u64);
|
||||||
self.buffer.get_mut().extend_from_slice(msg[..max as usize].as_ref());
|
self.buffer.extend(msg.take().split_to(len as usize).into());
|
||||||
|
|
||||||
*remaining -= max as u64;
|
*remaining -= len as u64;
|
||||||
Ok(*remaining == 0)
|
Ok(*remaining == 0)
|
||||||
|
} else {
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -748,7 +754,7 @@ impl TransferEncoding {
|
|||||||
TransferEncodingKind::Chunked(ref mut eof) => {
|
TransferEncodingKind::Chunked(ref mut eof) => {
|
||||||
if !*eof {
|
if !*eof {
|
||||||
*eof = true;
|
*eof = true;
|
||||||
self.buffer.get_mut().extend_from_slice(b"0\r\n\r\n");
|
self.buffer.extend_from_slice(b"0\r\n\r\n");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -759,7 +765,7 @@ impl io::Write for TransferEncoding {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
self.encode(buf)?;
|
self.encode(Binary::from_slice(buf))?;
|
||||||
Ok(buf.len())
|
Ok(buf.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -845,8 +851,8 @@ mod tests {
|
|||||||
fn test_chunked_te() {
|
fn test_chunked_te() {
|
||||||
let bytes = SharedBytes::default();
|
let bytes = SharedBytes::default();
|
||||||
let mut enc = TransferEncoding::chunked(bytes.clone());
|
let mut enc = TransferEncoding::chunked(bytes.clone());
|
||||||
assert!(!enc.encode(b"test").ok().unwrap());
|
assert!(!enc.encode(Binary::from(b"test".as_ref())).ok().unwrap());
|
||||||
assert!(enc.encode(b"").ok().unwrap());
|
assert!(enc.encode(Binary::from(b"".as_ref())).ok().unwrap());
|
||||||
assert_eq!(bytes.get_mut().take().freeze(),
|
assert_eq!(bytes.get_mut().take().freeze(),
|
||||||
Bytes::from_static(b"4\r\ntest\r\n0\r\n\r\n"));
|
Bytes::from_static(b"4\r\ntest\r\n0\r\n\r\n"));
|
||||||
}
|
}
|
@ -8,32 +8,30 @@ use actix::Arbiter;
|
|||||||
use httparse;
|
use httparse;
|
||||||
use http::{Uri, Method, Version, HttpTryFrom, HeaderMap};
|
use http::{Uri, Method, Version, HttpTryFrom, HeaderMap};
|
||||||
use http::header::{self, HeaderName, HeaderValue};
|
use http::header::{self, HeaderName, HeaderValue};
|
||||||
use bytes::{Bytes, BytesMut, BufMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use futures::{Future, Poll, Async};
|
use futures::{Future, Poll, Async};
|
||||||
use tokio_core::reactor::Timeout;
|
use tokio_core::reactor::Timeout;
|
||||||
|
|
||||||
use pipeline::Pipeline;
|
use pipeline::Pipeline;
|
||||||
use encoding::PayloadType;
|
|
||||||
use channel::{HttpHandler, HttpHandlerTask, IoStream};
|
|
||||||
use h1writer::{Writer, H1Writer};
|
|
||||||
use worker::WorkerSettings;
|
|
||||||
use httpcodes::HTTPNotFound;
|
use httpcodes::HTTPNotFound;
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
use error::{ParseError, PayloadError, ResponseError};
|
use error::{ParseError, PayloadError, ResponseError};
|
||||||
use payload::{Payload, PayloadWriter, DEFAULT_BUFFER_SIZE};
|
use payload::{Payload, PayloadWriter, DEFAULT_BUFFER_SIZE};
|
||||||
|
|
||||||
const LW_BUFFER_SIZE: usize = 4096;
|
use super::{utils, Writer};
|
||||||
const HW_BUFFER_SIZE: usize = 16_384;
|
use super::h1writer::H1Writer;
|
||||||
|
use super::encoding::PayloadType;
|
||||||
|
use super::settings::WorkerSettings;
|
||||||
|
use super::{HttpHandler, HttpHandlerTask, IoStream};
|
||||||
|
|
||||||
const MAX_BUFFER_SIZE: usize = 131_072;
|
const MAX_BUFFER_SIZE: usize = 131_072;
|
||||||
const MAX_HEADERS: usize = 100;
|
const MAX_HEADERS: usize = 96;
|
||||||
const MAX_PIPELINED_MESSAGES: usize = 16;
|
const MAX_PIPELINED_MESSAGES: usize = 16;
|
||||||
const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0";
|
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
struct Flags: u8 {
|
struct Flags: u8 {
|
||||||
const ERROR = 0b0000_0010;
|
const ERROR = 0b0000_0010;
|
||||||
const KEEPALIVE = 0b0000_0100;
|
const KEEPALIVE = 0b0000_0100;
|
||||||
const H2 = 0b0000_1000;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,17 +43,6 @@ bitflags! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) enum Http1Result {
|
|
||||||
Done,
|
|
||||||
Switch,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum Item {
|
|
||||||
Http1(HttpRequest),
|
|
||||||
Http2,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct Http1<T: IoStream, H: 'static> {
|
pub(crate) struct Http1<T: IoStream, H: 'static> {
|
||||||
flags: Flags,
|
flags: Flags,
|
||||||
settings: Rc<WorkerSettings<H>>,
|
settings: Rc<WorkerSettings<H>>,
|
||||||
@ -75,14 +62,16 @@ struct Entry {
|
|||||||
impl<T, H> Http1<T, H>
|
impl<T, H> Http1<T, H>
|
||||||
where T: IoStream, H: HttpHandler + 'static
|
where T: IoStream, H: HttpHandler + 'static
|
||||||
{
|
{
|
||||||
pub fn new(h: Rc<WorkerSettings<H>>, stream: T, addr: Option<SocketAddr>) -> Self {
|
pub fn new(h: Rc<WorkerSettings<H>>, stream: T, addr: Option<SocketAddr>, buf: BytesMut)
|
||||||
|
-> Self
|
||||||
|
{
|
||||||
let bytes = h.get_shared_bytes();
|
let bytes = h.get_shared_bytes();
|
||||||
Http1{ flags: Flags::KEEPALIVE,
|
Http1{ flags: Flags::KEEPALIVE,
|
||||||
settings: h,
|
settings: h,
|
||||||
addr: addr,
|
addr: addr,
|
||||||
stream: H1Writer::new(stream, bytes),
|
stream: H1Writer::new(stream, bytes),
|
||||||
reader: Reader::new(),
|
reader: Reader::new(),
|
||||||
read_buf: BytesMut::new(),
|
read_buf: buf,
|
||||||
tasks: VecDeque::new(),
|
tasks: VecDeque::new(),
|
||||||
keepalive_timer: None }
|
keepalive_timer: None }
|
||||||
}
|
}
|
||||||
@ -91,10 +80,6 @@ impl<T, H> Http1<T, H>
|
|||||||
self.settings.as_ref()
|
self.settings.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_inner(self) -> (Rc<WorkerSettings<H>>, T, Option<SocketAddr>, Bytes) {
|
|
||||||
(self.settings, self.stream.into_inner(), self.addr, self.read_buf.freeze())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn io(&mut self) -> &mut T {
|
pub(crate) fn io(&mut self) -> &mut T {
|
||||||
self.stream.get_mut()
|
self.stream.get_mut()
|
||||||
}
|
}
|
||||||
@ -111,15 +96,15 @@ impl<T, H> Http1<T, H>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: refacrtor
|
// TODO: refactor
|
||||||
#[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))]
|
#[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))]
|
||||||
pub fn poll(&mut self) -> Poll<Http1Result, ()> {
|
pub fn poll(&mut self) -> Poll<(), ()> {
|
||||||
// keep-alive timer
|
// keep-alive timer
|
||||||
if self.keepalive_timer.is_some() {
|
if let Some(ref mut timer) = self.keepalive_timer {
|
||||||
match self.keepalive_timer.as_mut().unwrap().poll() {
|
match timer.poll() {
|
||||||
Ok(Async::Ready(_)) => {
|
Ok(Async::Ready(_)) => {
|
||||||
trace!("Keep-alive timeout, close connection");
|
trace!("Keep-alive timeout, close connection");
|
||||||
return Ok(Async::Ready(Http1Result::Done))
|
return Ok(Async::Ready(()))
|
||||||
}
|
}
|
||||||
Ok(Async::NotReady) => (),
|
Ok(Async::NotReady) => (),
|
||||||
Err(_) => unreachable!(),
|
Err(_) => unreachable!(),
|
||||||
@ -148,7 +133,7 @@ impl<T, H> Http1<T, H>
|
|||||||
Ok(Async::Ready(ready)) => {
|
Ok(Async::Ready(ready)) => {
|
||||||
not_ready = false;
|
not_ready = false;
|
||||||
|
|
||||||
// overide keep-alive state
|
// override keep-alive state
|
||||||
if self.stream.keepalive() {
|
if self.stream.keepalive() {
|
||||||
self.flags.insert(Flags::KEEPALIVE);
|
self.flags.insert(Flags::KEEPALIVE);
|
||||||
} else {
|
} else {
|
||||||
@ -161,10 +146,8 @@ impl<T, H> Http1<T, H>
|
|||||||
item.flags.insert(EntryFlags::FINISHED);
|
item.flags.insert(EntryFlags::FINISHED);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Ok(Async::NotReady) => {
|
|
||||||
// no more IO for this iteration
|
// no more IO for this iteration
|
||||||
io = true;
|
Ok(Async::NotReady) => io = true,
|
||||||
},
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
// it is not possible to recover from error
|
// it is not possible to recover from error
|
||||||
// during pipe handling, so just drop connection
|
// during pipe handling, so just drop connection
|
||||||
@ -207,27 +190,18 @@ impl<T, H> Http1<T, H>
|
|||||||
|
|
||||||
// no keep-alive
|
// no keep-alive
|
||||||
if !self.flags.contains(Flags::KEEPALIVE) && self.tasks.is_empty() {
|
if !self.flags.contains(Flags::KEEPALIVE) && self.tasks.is_empty() {
|
||||||
let h2 = self.flags.contains(Flags::H2);
|
|
||||||
|
|
||||||
// check stream state
|
// check stream state
|
||||||
if !self.poll_completed(!h2)? {
|
if !self.poll_completed(true)? {
|
||||||
return Ok(Async::NotReady)
|
return Ok(Async::NotReady)
|
||||||
}
|
}
|
||||||
|
return Ok(Async::Ready(()))
|
||||||
if h2 {
|
|
||||||
return Ok(Async::Ready(Http1Result::Switch))
|
|
||||||
} else {
|
|
||||||
return Ok(Async::Ready(Http1Result::Done))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// read incoming data
|
// read incoming data
|
||||||
while !self.flags.contains(Flags::ERROR) && !self.flags.contains(Flags::H2) &&
|
while !self.flags.contains(Flags::ERROR) && self.tasks.len() < MAX_PIPELINED_MESSAGES {
|
||||||
self.tasks.len() < MAX_PIPELINED_MESSAGES
|
|
||||||
{
|
|
||||||
match self.reader.parse(self.stream.get_mut(),
|
match self.reader.parse(self.stream.get_mut(),
|
||||||
&mut self.read_buf, &self.settings) {
|
&mut self.read_buf, &self.settings) {
|
||||||
Ok(Async::Ready(Item::Http1(mut req))) => {
|
Ok(Async::Ready(mut req)) => {
|
||||||
not_ready = false;
|
not_ready = false;
|
||||||
|
|
||||||
// set remote addr
|
// set remote addr
|
||||||
@ -251,10 +225,42 @@ impl<T, H> Http1<T, H>
|
|||||||
self.tasks.push_back(
|
self.tasks.push_back(
|
||||||
Entry {pipe: pipe.unwrap_or_else(|| Pipeline::error(HTTPNotFound)),
|
Entry {pipe: pipe.unwrap_or_else(|| Pipeline::error(HTTPNotFound)),
|
||||||
flags: EntryFlags::empty()});
|
flags: EntryFlags::empty()});
|
||||||
|
},
|
||||||
|
Ok(Async::NotReady) => {
|
||||||
|
// start keep-alive timer, this also is slow request timeout
|
||||||
|
if self.tasks.is_empty() {
|
||||||
|
if self.settings.keep_alive_enabled() {
|
||||||
|
let keep_alive = self.settings.keep_alive();
|
||||||
|
if keep_alive > 0 && self.flags.contains(Flags::KEEPALIVE) {
|
||||||
|
if self.keepalive_timer.is_none() {
|
||||||
|
trace!("Start keep-alive timer");
|
||||||
|
let mut to = Timeout::new(
|
||||||
|
Duration::new(keep_alive, 0),
|
||||||
|
Arbiter::handle()).unwrap();
|
||||||
|
// register timeout
|
||||||
|
let _ = to.poll();
|
||||||
|
self.keepalive_timer = Some(to);
|
||||||
}
|
}
|
||||||
Ok(Async::Ready(Item::Http2)) => {
|
} else {
|
||||||
self.flags.insert(Flags::H2);
|
// check stream state
|
||||||
|
if !self.poll_completed(true)? {
|
||||||
|
return Ok(Async::NotReady)
|
||||||
}
|
}
|
||||||
|
// keep-alive disable, drop connection
|
||||||
|
return Ok(Async::Ready(()))
|
||||||
|
}
|
||||||
|
} else if !self.poll_completed(false)? ||
|
||||||
|
self.flags.contains(Flags::KEEPALIVE)
|
||||||
|
{
|
||||||
|
// check stream state or
|
||||||
|
// if keep-alive unset, rely on operating system
|
||||||
|
return Ok(Async::NotReady)
|
||||||
|
} else {
|
||||||
|
return Ok(Async::Ready(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
},
|
||||||
Err(ReaderError::Disconnect) => {
|
Err(ReaderError::Disconnect) => {
|
||||||
not_ready = false;
|
not_ready = false;
|
||||||
self.flags.insert(Flags::ERROR);
|
self.flags.insert(Flags::ERROR);
|
||||||
@ -285,58 +291,18 @@ impl<T, H> Http1<T, H>
|
|||||||
flags: EntryFlags::empty()});
|
flags: EntryFlags::empty()});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
Ok(Async::NotReady) => {
|
|
||||||
// start keep-alive timer, this also is slow request timeout
|
|
||||||
if self.tasks.is_empty() {
|
|
||||||
if self.settings.keep_alive_enabled() {
|
|
||||||
let keep_alive = self.settings.keep_alive();
|
|
||||||
if keep_alive > 0 && self.flags.contains(Flags::KEEPALIVE) {
|
|
||||||
if self.keepalive_timer.is_none() {
|
|
||||||
trace!("Start keep-alive timer");
|
|
||||||
let mut to = Timeout::new(
|
|
||||||
Duration::new(keep_alive, 0),
|
|
||||||
Arbiter::handle()).unwrap();
|
|
||||||
// register timeout
|
|
||||||
let _ = to.poll();
|
|
||||||
self.keepalive_timer = Some(to);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// check stream state
|
|
||||||
if !self.poll_completed(true)? {
|
|
||||||
return Ok(Async::NotReady)
|
|
||||||
}
|
|
||||||
// keep-alive disable, drop connection
|
|
||||||
return Ok(Async::Ready(Http1Result::Done))
|
|
||||||
}
|
|
||||||
} else if !self.poll_completed(false)? ||
|
|
||||||
self.flags.contains(Flags::KEEPALIVE)
|
|
||||||
{
|
|
||||||
// check stream state or
|
|
||||||
// if keep-alive unset, rely on operating system
|
|
||||||
return Ok(Async::NotReady)
|
|
||||||
} else {
|
|
||||||
return Ok(Async::Ready(Http1Result::Done))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for parse error
|
// check for parse error
|
||||||
if self.tasks.is_empty() {
|
if self.tasks.is_empty() {
|
||||||
let h2 = self.flags.contains(Flags::H2);
|
|
||||||
|
|
||||||
// check stream state
|
// check stream state
|
||||||
if !self.poll_completed(!h2)? {
|
if !self.poll_completed(true)? {
|
||||||
return Ok(Async::NotReady)
|
return Ok(Async::NotReady)
|
||||||
}
|
}
|
||||||
if h2 {
|
|
||||||
return Ok(Async::Ready(Http1Result::Switch))
|
|
||||||
}
|
|
||||||
if self.flags.contains(Flags::ERROR) || self.keepalive_timer.is_none() {
|
if self.flags.contains(Flags::ERROR) || self.keepalive_timer.is_none() {
|
||||||
return Ok(Async::Ready(Http1Result::Done))
|
return Ok(Async::Ready(()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -349,7 +315,6 @@ impl<T, H> Http1<T, H>
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct Reader {
|
struct Reader {
|
||||||
h1: bool,
|
|
||||||
payload: Option<PayloadInfo>,
|
payload: Option<PayloadInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -371,22 +336,14 @@ enum ReaderError {
|
|||||||
Error(ParseError),
|
Error(ParseError),
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Message {
|
|
||||||
Http1(HttpRequest, Option<PayloadInfo>),
|
|
||||||
Http2,
|
|
||||||
NotReady,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Reader {
|
impl Reader {
|
||||||
pub fn new() -> Reader {
|
pub fn new() -> Reader {
|
||||||
Reader {
|
Reader {
|
||||||
h1: false,
|
|
||||||
payload: None,
|
payload: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode(&mut self, buf: &mut BytesMut) -> std::result::Result<Decoding, ReaderError>
|
fn decode(&mut self, buf: &mut BytesMut) -> std::result::Result<Decoding, ReaderError> {
|
||||||
{
|
|
||||||
if let Some(ref mut payload) = self.payload {
|
if let Some(ref mut payload) = self.payload {
|
||||||
if payload.tx.capacity() > DEFAULT_BUFFER_SIZE {
|
if payload.tx.capacity() > DEFAULT_BUFFER_SIZE {
|
||||||
return Ok(Decoding::Paused)
|
return Ok(Decoding::Paused)
|
||||||
@ -414,12 +371,12 @@ impl Reader {
|
|||||||
|
|
||||||
pub fn parse<T, H>(&mut self, io: &mut T,
|
pub fn parse<T, H>(&mut self, io: &mut T,
|
||||||
buf: &mut BytesMut,
|
buf: &mut BytesMut,
|
||||||
settings: &WorkerSettings<H>) -> Poll<Item, ReaderError>
|
settings: &WorkerSettings<H>) -> Poll<HttpRequest, ReaderError>
|
||||||
where T: IoStream
|
where T: IoStream
|
||||||
{
|
{
|
||||||
// read payload
|
// read payload
|
||||||
if self.payload.is_some() {
|
if self.payload.is_some() {
|
||||||
match self.read_from_io(io, buf) {
|
match utils::read_from_io(io, buf) {
|
||||||
Ok(Async::Ready(0)) => {
|
Ok(Async::Ready(0)) => {
|
||||||
if let Some(ref mut payload) = self.payload {
|
if let Some(ref mut payload) = self.payload {
|
||||||
payload.tx.set_error(PayloadError::Incomplete);
|
payload.tx.set_error(PayloadError::Incomplete);
|
||||||
@ -444,7 +401,7 @@ impl Reader {
|
|||||||
|
|
||||||
// if buf is empty parse_message will always return NotReady, let's avoid that
|
// if buf is empty parse_message will always return NotReady, let's avoid that
|
||||||
let read = if buf.is_empty() {
|
let read = if buf.is_empty() {
|
||||||
match self.read_from_io(io, buf) {
|
match utils::read_from_io(io, buf) {
|
||||||
Ok(Async::Ready(0)) => {
|
Ok(Async::Ready(0)) => {
|
||||||
// debug!("Ignored premature client disconnection");
|
// debug!("Ignored premature client disconnection");
|
||||||
return Err(ReaderError::Disconnect);
|
return Err(ReaderError::Disconnect);
|
||||||
@ -462,7 +419,7 @@ impl Reader {
|
|||||||
|
|
||||||
loop {
|
loop {
|
||||||
match Reader::parse_message(buf, settings).map_err(ReaderError::Error)? {
|
match Reader::parse_message(buf, settings).map_err(ReaderError::Error)? {
|
||||||
Message::Http1(msg, decoder) => {
|
Async::Ready((msg, decoder)) => {
|
||||||
// process payload
|
// process payload
|
||||||
if let Some(payload) = decoder {
|
if let Some(payload) = decoder {
|
||||||
self.payload = Some(payload);
|
self.payload = Some(payload);
|
||||||
@ -471,22 +428,15 @@ impl Reader {
|
|||||||
Decoding::Ready => self.payload = None,
|
Decoding::Ready => self.payload = None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.h1 = true;
|
return Ok(Async::Ready(msg));
|
||||||
return Ok(Async::Ready(Item::Http1(msg)));
|
|
||||||
},
|
},
|
||||||
Message::Http2 => {
|
Async::NotReady => {
|
||||||
if self.h1 {
|
|
||||||
return Err(ReaderError::Error(ParseError::Version))
|
|
||||||
}
|
|
||||||
return Ok(Async::Ready(Item::Http2));
|
|
||||||
},
|
|
||||||
Message::NotReady => {
|
|
||||||
if buf.capacity() >= MAX_BUFFER_SIZE {
|
if buf.capacity() >= MAX_BUFFER_SIZE {
|
||||||
error!("MAX_BUFFER_SIZE unprocessed data reached, closing");
|
error!("MAX_BUFFER_SIZE unprocessed data reached, closing");
|
||||||
return Err(ReaderError::Error(ParseError::TooLarge));
|
return Err(ReaderError::Error(ParseError::TooLarge));
|
||||||
}
|
}
|
||||||
if read {
|
if read {
|
||||||
match self.read_from_io(io, buf) {
|
match utils::read_from_io(io, buf) {
|
||||||
Ok(Async::Ready(0)) => {
|
Ok(Async::Ready(0)) => {
|
||||||
debug!("Ignored premature client disconnection");
|
debug!("Ignored premature client disconnection");
|
||||||
return Err(ReaderError::Disconnect);
|
return Err(ReaderError::Disconnect);
|
||||||
@ -505,39 +455,8 @@ impl Reader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_from_io<T: IoStream>(&mut self, io: &mut T, buf: &mut BytesMut)
|
|
||||||
-> Poll<usize, io::Error>
|
|
||||||
{
|
|
||||||
unsafe {
|
|
||||||
if buf.remaining_mut() < LW_BUFFER_SIZE {
|
|
||||||
buf.reserve(HW_BUFFER_SIZE);
|
|
||||||
}
|
|
||||||
match io.read(buf.bytes_mut()) {
|
|
||||||
Ok(n) => {
|
|
||||||
buf.advance_mut(n);
|
|
||||||
Ok(Async::Ready(n))
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
if e.kind() == io::ErrorKind::WouldBlock {
|
|
||||||
Ok(Async::NotReady)
|
|
||||||
} else {
|
|
||||||
Err(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_message<H>(buf: &mut BytesMut, settings: &WorkerSettings<H>)
|
fn parse_message<H>(buf: &mut BytesMut, settings: &WorkerSettings<H>)
|
||||||
-> Result<Message, ParseError>
|
-> Poll<(HttpRequest, Option<PayloadInfo>), ParseError> {
|
||||||
{
|
|
||||||
if buf.is_empty() {
|
|
||||||
return Ok(Message::NotReady);
|
|
||||||
}
|
|
||||||
if buf.len() >= 14 && buf[..14] == HTTP2_PREFACE[..] {
|
|
||||||
return Ok(Message::Http2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse http message
|
// Parse http message
|
||||||
let msg = {
|
let msg = {
|
||||||
let bytes_ptr = buf.as_ref().as_ptr() as usize;
|
let bytes_ptr = buf.as_ref().as_ptr() as usize;
|
||||||
@ -563,7 +482,7 @@ impl Reader {
|
|||||||
};
|
};
|
||||||
(len, method, path, version, req.headers.len())
|
(len, method, path, version, req.headers.len())
|
||||||
}
|
}
|
||||||
httparse::Status::Partial => return Ok(Message::NotReady),
|
httparse::Status::Partial => return Ok(Async::NotReady),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -623,9 +542,9 @@ impl Reader {
|
|||||||
decoder: decoder,
|
decoder: decoder,
|
||||||
};
|
};
|
||||||
msg.get_mut().payload = Some(payload);
|
msg.get_mut().payload = Some(payload);
|
||||||
Ok(Message::Http1(HttpRequest::from_message(msg), Some(info)))
|
Ok(Async::Ready((HttpRequest::from_message(msg), Some(info))))
|
||||||
} else {
|
} else {
|
||||||
Ok(Message::Http1(HttpRequest::from_message(msg), None))
|
Ok(Async::Ready((HttpRequest::from_message(msg), None)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -901,8 +820,8 @@ mod tests {
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use application::HttpApplication;
|
use application::HttpApplication;
|
||||||
use worker::WorkerSettings;
|
use server::settings::WorkerSettings;
|
||||||
use channel::IoStream;
|
use server::IoStream;
|
||||||
|
|
||||||
struct Buffer {
|
struct Buffer {
|
||||||
buf: Bytes,
|
buf: Bytes,
|
||||||
@ -975,7 +894,7 @@ mod tests {
|
|||||||
($e:expr) => ({
|
($e:expr) => ({
|
||||||
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), None);
|
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), None);
|
||||||
match Reader::new().parse($e, &mut BytesMut::new(), &settings) {
|
match Reader::new().parse($e, &mut BytesMut::new(), &settings) {
|
||||||
Ok(Async::Ready(Item::Http1(req))) => req,
|
Ok(Async::Ready(req)) => req,
|
||||||
Ok(_) => panic!("Eof during parsing http request"),
|
Ok(_) => panic!("Eof during parsing http request"),
|
||||||
Err(err) => panic!("Error during parsing http request: {:?}", err),
|
Err(err) => panic!("Error during parsing http request: {:?}", err),
|
||||||
}
|
}
|
||||||
@ -985,7 +904,7 @@ mod tests {
|
|||||||
macro_rules! reader_parse_ready {
|
macro_rules! reader_parse_ready {
|
||||||
($e:expr) => (
|
($e:expr) => (
|
||||||
match $e {
|
match $e {
|
||||||
Ok(Async::Ready(Item::Http1(req))) => req,
|
Ok(Async::Ready(req)) => req,
|
||||||
Ok(_) => panic!("Eof during parsing http request"),
|
Ok(_) => panic!("Eof during parsing http request"),
|
||||||
Err(err) => panic!("Error during parsing http request: {:?}", err),
|
Err(err) => panic!("Error during parsing http request: {:?}", err),
|
||||||
}
|
}
|
||||||
@ -1017,7 +936,7 @@ mod tests {
|
|||||||
|
|
||||||
let mut reader = Reader::new();
|
let mut reader = Reader::new();
|
||||||
match reader.parse(&mut buf, &mut readbuf, &settings) {
|
match reader.parse(&mut buf, &mut readbuf, &settings) {
|
||||||
Ok(Async::Ready(Item::Http1(req))) => {
|
Ok(Async::Ready(req)) => {
|
||||||
assert_eq!(req.version(), Version::HTTP_11);
|
assert_eq!(req.version(), Version::HTTP_11);
|
||||||
assert_eq!(*req.method(), Method::GET);
|
assert_eq!(*req.method(), Method::GET);
|
||||||
assert_eq!(req.path(), "/test");
|
assert_eq!(req.path(), "/test");
|
||||||
@ -1040,7 +959,7 @@ mod tests {
|
|||||||
|
|
||||||
buf.feed_data(".1\r\n\r\n");
|
buf.feed_data(".1\r\n\r\n");
|
||||||
match reader.parse(&mut buf, &mut readbuf, &settings) {
|
match reader.parse(&mut buf, &mut readbuf, &settings) {
|
||||||
Ok(Async::Ready(Item::Http1(req))) => {
|
Ok(Async::Ready(req)) => {
|
||||||
assert_eq!(req.version(), Version::HTTP_11);
|
assert_eq!(req.version(), Version::HTTP_11);
|
||||||
assert_eq!(*req.method(), Method::PUT);
|
assert_eq!(*req.method(), Method::PUT);
|
||||||
assert_eq!(req.path(), "/test");
|
assert_eq!(req.path(), "/test");
|
||||||
@ -1057,7 +976,7 @@ mod tests {
|
|||||||
|
|
||||||
let mut reader = Reader::new();
|
let mut reader = Reader::new();
|
||||||
match reader.parse(&mut buf, &mut readbuf, &settings) {
|
match reader.parse(&mut buf, &mut readbuf, &settings) {
|
||||||
Ok(Async::Ready(Item::Http1(req))) => {
|
Ok(Async::Ready(req)) => {
|
||||||
assert_eq!(req.version(), Version::HTTP_10);
|
assert_eq!(req.version(), Version::HTTP_10);
|
||||||
assert_eq!(*req.method(), Method::POST);
|
assert_eq!(*req.method(), Method::POST);
|
||||||
assert_eq!(req.path(), "/test2");
|
assert_eq!(req.path(), "/test2");
|
||||||
@ -1074,7 +993,7 @@ mod tests {
|
|||||||
|
|
||||||
let mut reader = Reader::new();
|
let mut reader = Reader::new();
|
||||||
match reader.parse(&mut buf, &mut readbuf, &settings) {
|
match reader.parse(&mut buf, &mut readbuf, &settings) {
|
||||||
Ok(Async::Ready(Item::Http1(mut req))) => {
|
Ok(Async::Ready(mut req)) => {
|
||||||
assert_eq!(req.version(), Version::HTTP_11);
|
assert_eq!(req.version(), Version::HTTP_11);
|
||||||
assert_eq!(*req.method(), Method::GET);
|
assert_eq!(*req.method(), Method::GET);
|
||||||
assert_eq!(req.path(), "/test");
|
assert_eq!(req.path(), "/test");
|
||||||
@ -1093,7 +1012,7 @@ mod tests {
|
|||||||
|
|
||||||
let mut reader = Reader::new();
|
let mut reader = Reader::new();
|
||||||
match reader.parse(&mut buf, &mut readbuf, &settings) {
|
match reader.parse(&mut buf, &mut readbuf, &settings) {
|
||||||
Ok(Async::Ready(Item::Http1(mut req))) => {
|
Ok(Async::Ready(mut req)) => {
|
||||||
assert_eq!(req.version(), Version::HTTP_11);
|
assert_eq!(req.version(), Version::HTTP_11);
|
||||||
assert_eq!(*req.method(), Method::GET);
|
assert_eq!(*req.method(), Method::GET);
|
||||||
assert_eq!(req.path(), "/test");
|
assert_eq!(req.path(), "/test");
|
||||||
@ -1114,7 +1033,7 @@ mod tests {
|
|||||||
|
|
||||||
buf.feed_data("\r\n");
|
buf.feed_data("\r\n");
|
||||||
match reader.parse(&mut buf, &mut readbuf, &settings) {
|
match reader.parse(&mut buf, &mut readbuf, &settings) {
|
||||||
Ok(Async::Ready(Item::Http1(req))) => {
|
Ok(Async::Ready(req)) => {
|
||||||
assert_eq!(req.version(), Version::HTTP_11);
|
assert_eq!(req.version(), Version::HTTP_11);
|
||||||
assert_eq!(*req.method(), Method::GET);
|
assert_eq!(*req.method(), Method::GET);
|
||||||
assert_eq!(req.path(), "/test");
|
assert_eq!(req.path(), "/test");
|
||||||
@ -1140,7 +1059,7 @@ mod tests {
|
|||||||
|
|
||||||
buf.feed_data("t: value\r\n\r\n");
|
buf.feed_data("t: value\r\n\r\n");
|
||||||
match reader.parse(&mut buf, &mut readbuf, &settings) {
|
match reader.parse(&mut buf, &mut readbuf, &settings) {
|
||||||
Ok(Async::Ready(Item::Http1(req))) => {
|
Ok(Async::Ready(req)) => {
|
||||||
assert_eq!(req.version(), Version::HTTP_11);
|
assert_eq!(req.version(), Version::HTTP_11);
|
||||||
assert_eq!(*req.method(), Method::GET);
|
assert_eq!(*req.method(), Method::GET);
|
||||||
assert_eq!(req.path(), "/test");
|
assert_eq!(req.path(), "/test");
|
||||||
@ -1161,7 +1080,7 @@ mod tests {
|
|||||||
|
|
||||||
let mut reader = Reader::new();
|
let mut reader = Reader::new();
|
||||||
match reader.parse(&mut buf, &mut readbuf, &settings) {
|
match reader.parse(&mut buf, &mut readbuf, &settings) {
|
||||||
Ok(Async::Ready(Item::Http1(req))) => {
|
Ok(Async::Ready(req)) => {
|
||||||
let val: Vec<_> = req.headers().get_all("Set-Cookie")
|
let val: Vec<_> = req.headers().get_all("Set-Cookie")
|
||||||
.iter().map(|v| v.to_str().unwrap().to_owned()).collect();
|
.iter().map(|v| v.to_str().unwrap().to_owned()).collect();
|
||||||
assert_eq!(val[0], "c1=cookie1");
|
assert_eq!(val[0], "c1=cookie1");
|
||||||
@ -1283,6 +1202,7 @@ mod tests {
|
|||||||
panic!("Error");
|
panic!("Error");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// type in chunked
|
||||||
let mut buf = Buffer::new(
|
let mut buf = Buffer::new(
|
||||||
"GET /test HTTP/1.1\r\n\
|
"GET /test HTTP/1.1\r\n\
|
||||||
transfer-encoding: chnked\r\n\r\n");
|
transfer-encoding: chnked\r\n\r\n");
|
||||||
@ -1510,17 +1430,4 @@ mod tests {
|
|||||||
Err(err) => panic!("{:?}", err),
|
Err(err) => panic!("{:?}", err),
|
||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_http2_prefix() {
|
|
||||||
let mut buf = Buffer::new("PRI * HTTP/2.0\r\n\r\n");
|
|
||||||
let mut readbuf = BytesMut::new();
|
|
||||||
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), None);
|
|
||||||
|
|
||||||
let mut reader = Reader::new();
|
|
||||||
match reader.parse(&mut buf, &mut readbuf, &settings) {
|
|
||||||
Ok(Async::Ready(Item::Http2)) => (),
|
|
||||||
Ok(_) | Err(_) => panic!("Error during parsing http request"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -2,39 +2,18 @@ use std::io;
|
|||||||
use bytes::BufMut;
|
use bytes::BufMut;
|
||||||
use futures::{Async, Poll};
|
use futures::{Async, Poll};
|
||||||
use tokio_io::AsyncWrite;
|
use tokio_io::AsyncWrite;
|
||||||
use http::Version;
|
use http::{Method, Version};
|
||||||
use http::header::{HeaderValue, CONNECTION, DATE};
|
use http::header::{HeaderValue, CONNECTION, DATE};
|
||||||
|
|
||||||
use helpers;
|
use helpers;
|
||||||
use body::Body;
|
use body::{Body, Binary};
|
||||||
use helpers::SharedBytes;
|
|
||||||
use encoding::PayloadEncoder;
|
|
||||||
use httprequest::HttpMessage;
|
use httprequest::HttpMessage;
|
||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
|
use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE};
|
||||||
|
use super::shared::SharedBytes;
|
||||||
|
use super::encoding::PayloadEncoder;
|
||||||
|
|
||||||
const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific
|
const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific
|
||||||
const MAX_WRITE_BUFFER_SIZE: usize = 65_536; // max buffer size 64k
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum WriterState {
|
|
||||||
Done,
|
|
||||||
Pause,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send stream
|
|
||||||
pub trait Writer {
|
|
||||||
fn written(&self) -> u64;
|
|
||||||
|
|
||||||
fn start(&mut self, req: &mut HttpMessage, resp: &mut HttpResponse)
|
|
||||||
-> Result<WriterState, io::Error>;
|
|
||||||
|
|
||||||
fn write(&mut self, payload: &[u8]) -> Result<WriterState, io::Error>;
|
|
||||||
|
|
||||||
fn write_eof(&mut self) -> Result<WriterState, io::Error>;
|
|
||||||
|
|
||||||
fn poll_completed(&mut self, shutdown: bool) -> Poll<(), io::Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
struct Flags: u8 {
|
struct Flags: u8 {
|
||||||
@ -76,28 +55,26 @@ impl<T: AsyncWrite> H1Writer<T> {
|
|||||||
self.flags = Flags::empty();
|
self.flags = Flags::empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_inner(self) -> T {
|
|
||||||
self.stream
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disconnected(&mut self) {
|
pub fn disconnected(&mut self) {
|
||||||
self.encoder.get_mut().take();
|
self.buffer.take();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn keepalive(&self) -> bool {
|
pub fn keepalive(&self) -> bool {
|
||||||
self.flags.contains(Flags::KEEPALIVE) && !self.flags.contains(Flags::UPGRADE)
|
self.flags.contains(Flags::KEEPALIVE) && !self.flags.contains(Flags::UPGRADE)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_to_stream(&mut self) -> Result<WriterState, io::Error> {
|
fn write_to_stream(&mut self) -> io::Result<WriterState> {
|
||||||
let buffer = self.encoder.get_mut();
|
while !self.buffer.is_empty() {
|
||||||
|
match self.stream.write(self.buffer.as_ref()) {
|
||||||
while !buffer.is_empty() {
|
Ok(0) => {
|
||||||
match self.stream.write(buffer.as_ref()) {
|
self.disconnected();
|
||||||
|
return Ok(WriterState::Done);
|
||||||
|
},
|
||||||
Ok(n) => {
|
Ok(n) => {
|
||||||
let _ = buffer.split_to(n);
|
let _ = self.buffer.split_to(n);
|
||||||
},
|
},
|
||||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
|
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||||
if buffer.len() > MAX_WRITE_BUFFER_SIZE {
|
if self.buffer.len() > MAX_WRITE_BUFFER_SIZE {
|
||||||
return Ok(WriterState::Pause)
|
return Ok(WriterState::Pause)
|
||||||
} else {
|
} else {
|
||||||
return Ok(WriterState::Done)
|
return Ok(WriterState::Done)
|
||||||
@ -112,13 +89,12 @@ impl<T: AsyncWrite> H1Writer<T> {
|
|||||||
|
|
||||||
impl<T: AsyncWrite> Writer for H1Writer<T> {
|
impl<T: AsyncWrite> Writer for H1Writer<T> {
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn written(&self) -> u64 {
|
fn written(&self) -> u64 {
|
||||||
self.written
|
self.written
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start(&mut self, req: &mut HttpMessage, msg: &mut HttpResponse)
|
fn start(&mut self, req: &mut HttpMessage, msg: &mut HttpResponse) -> io::Result<WriterState> {
|
||||||
-> Result<WriterState, io::Error>
|
|
||||||
{
|
|
||||||
// prepare task
|
// prepare task
|
||||||
self.flags.insert(Flags::STARTED);
|
self.flags.insert(Flags::STARTED);
|
||||||
self.encoder = PayloadEncoder::new(self.buffer.clone(), req, msg);
|
self.encoder = PayloadEncoder::new(self.buffer.clone(), req, msg);
|
||||||
@ -143,7 +119,7 @@ impl<T: AsyncWrite> Writer for H1Writer<T> {
|
|||||||
|
|
||||||
// render message
|
// render message
|
||||||
{
|
{
|
||||||
let mut buffer = self.encoder.get_mut();
|
let mut buffer = self.buffer.get_mut();
|
||||||
if let Body::Binary(ref bytes) = body {
|
if let Body::Binary(ref bytes) = body {
|
||||||
buffer.reserve(256 + msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len());
|
buffer.reserve(256 + msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len());
|
||||||
} else {
|
} else {
|
||||||
@ -156,7 +132,11 @@ impl<T: AsyncWrite> Writer for H1Writer<T> {
|
|||||||
|
|
||||||
match body {
|
match body {
|
||||||
Body::Empty =>
|
Body::Empty =>
|
||||||
buffer.extend_from_slice(b"\r\ncontent-length: 0\r\n"),
|
if req.method != Method::HEAD {
|
||||||
|
buffer.extend_from_slice(b"\r\ncontent-length: 0\r\n");
|
||||||
|
} else {
|
||||||
|
buffer.extend_from_slice(b"\r\n");
|
||||||
|
},
|
||||||
Body::Binary(ref bytes) =>
|
Body::Binary(ref bytes) =>
|
||||||
helpers::write_content_length(bytes.len(), &mut buffer),
|
helpers::write_content_length(bytes.len(), &mut buffer),
|
||||||
_ =>
|
_ =>
|
||||||
@ -186,46 +166,46 @@ impl<T: AsyncWrite> Writer for H1Writer<T> {
|
|||||||
|
|
||||||
if let Body::Binary(bytes) = body {
|
if let Body::Binary(bytes) = body {
|
||||||
self.written = bytes.len() as u64;
|
self.written = bytes.len() as u64;
|
||||||
self.encoder.write(bytes.as_ref())?;
|
self.encoder.write(bytes)?;
|
||||||
} else {
|
} else {
|
||||||
msg.replace_body(body);
|
msg.replace_body(body);
|
||||||
}
|
}
|
||||||
Ok(WriterState::Done)
|
Ok(WriterState::Done)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&mut self, payload: &[u8]) -> Result<WriterState, io::Error> {
|
fn write(&mut self, payload: Binary) -> io::Result<WriterState> {
|
||||||
self.written += payload.len() as u64;
|
self.written += payload.len() as u64;
|
||||||
if !self.flags.contains(Flags::DISCONNECTED) {
|
if !self.flags.contains(Flags::DISCONNECTED) {
|
||||||
if self.flags.contains(Flags::STARTED) {
|
if self.flags.contains(Flags::STARTED) {
|
||||||
// TODO: add warning, write after EOF
|
// TODO: add warning, write after EOF
|
||||||
self.encoder.write(payload)?;
|
self.encoder.write(payload)?;
|
||||||
return Ok(WriterState::Done)
|
|
||||||
} else {
|
} else {
|
||||||
// might be response to EXCEPT
|
// might be response to EXCEPT
|
||||||
self.encoder.get_mut().extend_from_slice(payload)
|
self.buffer.extend_from_slice(payload.as_ref())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.encoder.len() > MAX_WRITE_BUFFER_SIZE {
|
if self.buffer.len() > MAX_WRITE_BUFFER_SIZE {
|
||||||
Ok(WriterState::Pause)
|
Ok(WriterState::Pause)
|
||||||
} else {
|
} else {
|
||||||
Ok(WriterState::Done)
|
Ok(WriterState::Done)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_eof(&mut self) -> Result<WriterState, io::Error> {
|
fn write_eof(&mut self) -> io::Result<WriterState> {
|
||||||
self.encoder.write_eof()?;
|
self.encoder.write_eof()?;
|
||||||
|
|
||||||
if !self.encoder.is_eof() {
|
if !self.encoder.is_eof() {
|
||||||
Err(io::Error::new(io::ErrorKind::Other,
|
Err(io::Error::new(io::ErrorKind::Other,
|
||||||
"Last payload item, but eof is not reached"))
|
"Last payload item, but eof is not reached"))
|
||||||
} else if self.encoder.len() > MAX_WRITE_BUFFER_SIZE {
|
} else if self.buffer.len() > MAX_WRITE_BUFFER_SIZE {
|
||||||
Ok(WriterState::Pause)
|
Ok(WriterState::Pause)
|
||||||
} else {
|
} else {
|
||||||
Ok(WriterState::Done)
|
Ok(WriterState::Done)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn poll_completed(&mut self, shutdown: bool) -> Poll<(), io::Error> {
|
fn poll_completed(&mut self, shutdown: bool) -> Poll<(), io::Error> {
|
||||||
match self.write_to_stream() {
|
match self.write_to_stream() {
|
||||||
Ok(WriterState::Done) => {
|
Ok(WriterState::Done) => {
|
@ -15,15 +15,16 @@ use tokio_io::{AsyncRead, AsyncWrite};
|
|||||||
use tokio_core::reactor::Timeout;
|
use tokio_core::reactor::Timeout;
|
||||||
|
|
||||||
use pipeline::Pipeline;
|
use pipeline::Pipeline;
|
||||||
use h2writer::H2Writer;
|
|
||||||
use worker::WorkerSettings;
|
|
||||||
use channel::{HttpHandler, HttpHandlerTask};
|
|
||||||
use error::PayloadError;
|
use error::PayloadError;
|
||||||
use encoding::PayloadType;
|
|
||||||
use httpcodes::HTTPNotFound;
|
use httpcodes::HTTPNotFound;
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
use payload::{Payload, PayloadWriter};
|
use payload::{Payload, PayloadWriter};
|
||||||
|
|
||||||
|
use super::h2writer::H2Writer;
|
||||||
|
use super::encoding::PayloadType;
|
||||||
|
use super::settings::WorkerSettings;
|
||||||
|
use super::{HttpHandler, HttpHandlerTask};
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
struct Flags: u8 {
|
struct Flags: u8 {
|
||||||
const DISCONNECTED = 0b0000_0010;
|
const DISCONNECTED = 0b0000_0010;
|
||||||
@ -44,7 +45,7 @@ pub(crate) struct Http2<T, H>
|
|||||||
|
|
||||||
enum State<T: AsyncRead + AsyncWrite> {
|
enum State<T: AsyncRead + AsyncWrite> {
|
||||||
Handshake(Handshake<T, Bytes>),
|
Handshake(Handshake<T, Bytes>),
|
||||||
Server(Connection<T, Bytes>),
|
Connection(Connection<T, Bytes>),
|
||||||
Empty,
|
Empty,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,7 +77,7 @@ impl<T, H> Http2<T, H>
|
|||||||
|
|
||||||
pub fn poll(&mut self) -> Poll<(), ()> {
|
pub fn poll(&mut self) -> Poll<(), ()> {
|
||||||
// server
|
// server
|
||||||
if let State::Server(ref mut server) = self.state {
|
if let State::Connection(ref mut conn) = self.state {
|
||||||
// keep-alive timer
|
// keep-alive timer
|
||||||
if let Some(ref mut timeout) = self.keepalive_timer {
|
if let Some(ref mut timeout) = self.keepalive_timer {
|
||||||
match timeout.poll() {
|
match timeout.poll() {
|
||||||
@ -144,7 +145,7 @@ impl<T, H> Http2<T, H>
|
|||||||
|
|
||||||
// get request
|
// get request
|
||||||
if !self.flags.contains(Flags::DISCONNECTED) {
|
if !self.flags.contains(Flags::DISCONNECTED) {
|
||||||
match server.poll() {
|
match conn.poll() {
|
||||||
Ok(Async::Ready(None)) => {
|
Ok(Async::Ready(None)) => {
|
||||||
not_ready = false;
|
not_ready = false;
|
||||||
self.flags.insert(Flags::DISCONNECTED);
|
self.flags.insert(Flags::DISCONNECTED);
|
||||||
@ -178,7 +179,8 @@ impl<T, H> Http2<T, H>
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// keep-alive disable, drop connection
|
// keep-alive disable, drop connection
|
||||||
return Ok(Async::Ready(()))
|
return conn.poll_close().map_err(
|
||||||
|
|e| error!("Error during connection close: {}", e))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// keep-alive unset, rely on operating system
|
// keep-alive unset, rely on operating system
|
||||||
@ -198,7 +200,8 @@ impl<T, H> Http2<T, H>
|
|||||||
|
|
||||||
if not_ready {
|
if not_ready {
|
||||||
if self.tasks.is_empty() && self.flags.contains(Flags::DISCONNECTED) {
|
if self.tasks.is_empty() && self.flags.contains(Flags::DISCONNECTED) {
|
||||||
return Ok(Async::Ready(()))
|
return conn.poll_close().map_err(
|
||||||
|
|e| error!("Error during connection close: {}", e))
|
||||||
} else {
|
} else {
|
||||||
return Ok(Async::NotReady)
|
return Ok(Async::NotReady)
|
||||||
}
|
}
|
||||||
@ -209,8 +212,8 @@ impl<T, H> Http2<T, H>
|
|||||||
// handshake
|
// handshake
|
||||||
self.state = if let State::Handshake(ref mut handshake) = self.state {
|
self.state = if let State::Handshake(ref mut handshake) = self.state {
|
||||||
match handshake.poll() {
|
match handshake.poll() {
|
||||||
Ok(Async::Ready(srv)) => {
|
Ok(Async::Ready(conn)) => {
|
||||||
State::Server(srv)
|
State::Connection(conn)
|
||||||
},
|
},
|
||||||
Ok(Async::NotReady) =>
|
Ok(Async::NotReady) =>
|
||||||
return Ok(Async::NotReady),
|
return Ok(Async::NotReady),
|
@ -7,15 +7,14 @@ use http::{Version, HttpTryFrom, Response};
|
|||||||
use http::header::{HeaderValue, CONNECTION, TRANSFER_ENCODING, DATE, CONTENT_LENGTH};
|
use http::header::{HeaderValue, CONNECTION, TRANSFER_ENCODING, DATE, CONTENT_LENGTH};
|
||||||
|
|
||||||
use helpers;
|
use helpers;
|
||||||
use body::Body;
|
use body::{Body, Binary};
|
||||||
use helpers::SharedBytes;
|
|
||||||
use encoding::PayloadEncoder;
|
|
||||||
use httprequest::HttpMessage;
|
use httprequest::HttpMessage;
|
||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
use h1writer::{Writer, WriterState};
|
use super::encoding::PayloadEncoder;
|
||||||
|
use super::shared::SharedBytes;
|
||||||
|
use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE};
|
||||||
|
|
||||||
const CHUNK_SIZE: usize = 16_384;
|
const CHUNK_SIZE: usize = 16_384;
|
||||||
const MAX_WRITE_BUFFER_SIZE: usize = 65_536; // max buffer size 64k
|
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
struct Flags: u8 {
|
struct Flags: u8 {
|
||||||
@ -53,15 +52,13 @@ impl H2Writer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_to_stream(&mut self) -> Result<WriterState, io::Error> {
|
fn write_to_stream(&mut self) -> io::Result<WriterState> {
|
||||||
if !self.flags.contains(Flags::STARTED) {
|
if !self.flags.contains(Flags::STARTED) {
|
||||||
return Ok(WriterState::Done)
|
return Ok(WriterState::Done)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref mut stream) = self.stream {
|
if let Some(ref mut stream) = self.stream {
|
||||||
let buffer = self.encoder.get_mut();
|
if self.buffer.is_empty() {
|
||||||
|
|
||||||
if buffer.is_empty() {
|
|
||||||
if self.flags.contains(Flags::EOF) {
|
if self.flags.contains(Flags::EOF) {
|
||||||
let _ = stream.send_data(Bytes::new(), true);
|
let _ = stream.send_data(Bytes::new(), true);
|
||||||
}
|
}
|
||||||
@ -71,7 +68,7 @@ impl H2Writer {
|
|||||||
loop {
|
loop {
|
||||||
match stream.poll_capacity() {
|
match stream.poll_capacity() {
|
||||||
Ok(Async::NotReady) => {
|
Ok(Async::NotReady) => {
|
||||||
if buffer.len() > MAX_WRITE_BUFFER_SIZE {
|
if self.buffer.len() > MAX_WRITE_BUFFER_SIZE {
|
||||||
return Ok(WriterState::Pause)
|
return Ok(WriterState::Pause)
|
||||||
} else {
|
} else {
|
||||||
return Ok(WriterState::Done)
|
return Ok(WriterState::Done)
|
||||||
@ -81,15 +78,15 @@ impl H2Writer {
|
|||||||
return Ok(WriterState::Done)
|
return Ok(WriterState::Done)
|
||||||
}
|
}
|
||||||
Ok(Async::Ready(Some(cap))) => {
|
Ok(Async::Ready(Some(cap))) => {
|
||||||
let len = buffer.len();
|
let len = self.buffer.len();
|
||||||
let bytes = buffer.split_to(cmp::min(cap, len));
|
let bytes = self.buffer.split_to(cmp::min(cap, len));
|
||||||
let eof = buffer.is_empty() && self.flags.contains(Flags::EOF);
|
let eof = self.buffer.is_empty() && self.flags.contains(Flags::EOF);
|
||||||
self.written += bytes.len() as u64;
|
self.written += bytes.len() as u64;
|
||||||
|
|
||||||
if let Err(err) = stream.send_data(bytes.freeze(), eof) {
|
if let Err(err) = stream.send_data(bytes.freeze(), eof) {
|
||||||
return Err(io::Error::new(io::ErrorKind::Other, err))
|
return Err(io::Error::new(io::ErrorKind::Other, err))
|
||||||
} else if !buffer.is_empty() {
|
} else if !self.buffer.is_empty() {
|
||||||
let cap = cmp::min(buffer.len(), CHUNK_SIZE);
|
let cap = cmp::min(self.buffer.len(), CHUNK_SIZE);
|
||||||
stream.reserve_capacity(cap);
|
stream.reserve_capacity(cap);
|
||||||
} else {
|
} else {
|
||||||
return Ok(WriterState::Pause)
|
return Ok(WriterState::Pause)
|
||||||
@ -111,11 +108,7 @@ impl Writer for H2Writer {
|
|||||||
self.written
|
self.written
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start(&mut self, req: &mut HttpMessage, msg: &mut HttpResponse)
|
fn start(&mut self, req: &mut HttpMessage, msg: &mut HttpResponse) -> io::Result<WriterState> {
|
||||||
-> Result<WriterState, io::Error>
|
|
||||||
{
|
|
||||||
// trace!("Prepare response with status: {:?}", msg.status());
|
|
||||||
|
|
||||||
// prepare response
|
// prepare response
|
||||||
self.flags.insert(Flags::STARTED);
|
self.flags.insert(Flags::STARTED);
|
||||||
self.encoder = PayloadEncoder::new(self.buffer.clone(), req, msg);
|
self.encoder = PayloadEncoder::new(self.buffer.clone(), req, msg);
|
||||||
@ -168,9 +161,9 @@ impl Writer for H2Writer {
|
|||||||
if let Body::Binary(bytes) = body {
|
if let Body::Binary(bytes) = body {
|
||||||
self.flags.insert(Flags::EOF);
|
self.flags.insert(Flags::EOF);
|
||||||
self.written = bytes.len() as u64;
|
self.written = bytes.len() as u64;
|
||||||
self.encoder.write(bytes.as_ref())?;
|
self.encoder.write(bytes)?;
|
||||||
if let Some(ref mut stream) = self.stream {
|
if let Some(ref mut stream) = self.stream {
|
||||||
stream.reserve_capacity(cmp::min(self.encoder.len(), CHUNK_SIZE));
|
stream.reserve_capacity(cmp::min(self.buffer.len(), CHUNK_SIZE));
|
||||||
}
|
}
|
||||||
Ok(WriterState::Pause)
|
Ok(WriterState::Pause)
|
||||||
} else {
|
} else {
|
||||||
@ -179,7 +172,7 @@ impl Writer for H2Writer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&mut self, payload: &[u8]) -> Result<WriterState, io::Error> {
|
fn write(&mut self, payload: Binary) -> io::Result<WriterState> {
|
||||||
self.written = payload.len() as u64;
|
self.written = payload.len() as u64;
|
||||||
|
|
||||||
if !self.flags.contains(Flags::DISCONNECTED) {
|
if !self.flags.contains(Flags::DISCONNECTED) {
|
||||||
@ -188,25 +181,25 @@ impl Writer for H2Writer {
|
|||||||
self.encoder.write(payload)?;
|
self.encoder.write(payload)?;
|
||||||
} else {
|
} else {
|
||||||
// might be response for EXCEPT
|
// might be response for EXCEPT
|
||||||
self.encoder.get_mut().extend_from_slice(payload)
|
self.buffer.extend_from_slice(payload.as_ref())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.encoder.len() > MAX_WRITE_BUFFER_SIZE {
|
if self.buffer.len() > MAX_WRITE_BUFFER_SIZE {
|
||||||
Ok(WriterState::Pause)
|
Ok(WriterState::Pause)
|
||||||
} else {
|
} else {
|
||||||
Ok(WriterState::Done)
|
Ok(WriterState::Done)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_eof(&mut self) -> Result<WriterState, io::Error> {
|
fn write_eof(&mut self) -> io::Result<WriterState> {
|
||||||
self.encoder.write_eof()?;
|
self.encoder.write_eof()?;
|
||||||
|
|
||||||
self.flags.insert(Flags::EOF);
|
self.flags.insert(Flags::EOF);
|
||||||
if !self.encoder.is_eof() {
|
if !self.encoder.is_eof() {
|
||||||
Err(io::Error::new(io::ErrorKind::Other,
|
Err(io::Error::new(io::ErrorKind::Other,
|
||||||
"Last payload item, but eof is not reached"))
|
"Last payload item, but eof is not reached"))
|
||||||
} else if self.encoder.len() > MAX_WRITE_BUFFER_SIZE {
|
} else if self.buffer.len() > MAX_WRITE_BUFFER_SIZE {
|
||||||
Ok(WriterState::Pause)
|
Ok(WriterState::Pause)
|
||||||
} else {
|
} else {
|
||||||
Ok(WriterState::Done)
|
Ok(WriterState::Done)
|
178
src/server/mod.rs
Normal file
178
src/server/mod.rs
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
//! Http server
|
||||||
|
use std::{time, io};
|
||||||
|
use std::net::Shutdown;
|
||||||
|
|
||||||
|
use futures::Poll;
|
||||||
|
use tokio_io::{AsyncRead, AsyncWrite};
|
||||||
|
use tokio_core::net::TcpStream;
|
||||||
|
|
||||||
|
mod srv;
|
||||||
|
mod worker;
|
||||||
|
mod channel;
|
||||||
|
mod encoding;
|
||||||
|
mod h1;
|
||||||
|
mod h2;
|
||||||
|
mod h1writer;
|
||||||
|
mod h2writer;
|
||||||
|
mod settings;
|
||||||
|
mod shared;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
|
pub use self::srv::HttpServer;
|
||||||
|
pub use self::settings::ServerSettings;
|
||||||
|
|
||||||
|
use body::Binary;
|
||||||
|
use error::Error;
|
||||||
|
use httprequest::{HttpMessage, HttpRequest};
|
||||||
|
use httpresponse::HttpResponse;
|
||||||
|
|
||||||
|
/// max buffer size 64k
|
||||||
|
pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536;
|
||||||
|
|
||||||
|
/// Pause accepting incoming connections
|
||||||
|
///
|
||||||
|
/// If socket contains some pending connection, they might be dropped.
|
||||||
|
/// All opened connection remains active.
|
||||||
|
#[derive(Message)]
|
||||||
|
pub struct PauseServer;
|
||||||
|
|
||||||
|
/// Resume accepting incoming connections
|
||||||
|
#[derive(Message)]
|
||||||
|
pub struct ResumeServer;
|
||||||
|
|
||||||
|
/// Stop incoming connection processing, stop all workers and exit.
|
||||||
|
///
|
||||||
|
/// If server starts with `spawn()` method, then spawned thread get terminated.
|
||||||
|
#[derive(Message)]
|
||||||
|
pub struct StopServer {
|
||||||
|
pub graceful: bool
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Low level http request handler
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
pub trait HttpHandler: 'static {
|
||||||
|
|
||||||
|
/// Handle request
|
||||||
|
fn handle(&mut self, req: HttpRequest) -> Result<Box<HttpHandlerTask>, HttpRequest>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HttpHandler for Box<HttpHandler> {
|
||||||
|
fn handle(&mut self, req: HttpRequest) -> Result<Box<HttpHandlerTask>, HttpRequest> {
|
||||||
|
self.as_mut().handle(req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait HttpHandlerTask {
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<(), Error>;
|
||||||
|
|
||||||
|
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error>;
|
||||||
|
|
||||||
|
fn disconnected(&mut self);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Conversion helper trait
|
||||||
|
pub trait IntoHttpHandler {
|
||||||
|
/// The associated type which is result of conversion.
|
||||||
|
type Handler: HttpHandler;
|
||||||
|
|
||||||
|
/// Convert into `HttpHandler` object.
|
||||||
|
fn into_handler(self, settings: ServerSettings) -> Self::Handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: HttpHandler> IntoHttpHandler for T {
|
||||||
|
type Handler = T;
|
||||||
|
|
||||||
|
fn into_handler(self, _: ServerSettings) -> Self::Handler {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum WriterState {
|
||||||
|
Done,
|
||||||
|
Pause,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stream writer
|
||||||
|
pub trait Writer {
|
||||||
|
fn written(&self) -> u64;
|
||||||
|
|
||||||
|
fn start(&mut self, req: &mut HttpMessage, resp: &mut HttpResponse) -> io::Result<WriterState>;
|
||||||
|
|
||||||
|
fn write(&mut self, payload: Binary) -> io::Result<WriterState>;
|
||||||
|
|
||||||
|
fn write_eof(&mut self) -> io::Result<WriterState>;
|
||||||
|
|
||||||
|
fn poll_completed(&mut self, shutdown: bool) -> Poll<(), io::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Low-level io stream operations
|
||||||
|
pub trait IoStream: AsyncRead + AsyncWrite + 'static {
|
||||||
|
fn shutdown(&mut self, how: Shutdown) -> io::Result<()>;
|
||||||
|
|
||||||
|
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()>;
|
||||||
|
|
||||||
|
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IoStream for TcpStream {
|
||||||
|
#[inline]
|
||||||
|
fn shutdown(&mut self, how: Shutdown) -> io::Result<()> {
|
||||||
|
TcpStream::shutdown(self, how)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
|
||||||
|
TcpStream::set_nodelay(self, nodelay)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
|
||||||
|
TcpStream::set_linger(self, dur)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature="alpn")]
|
||||||
|
use tokio_openssl::SslStream;
|
||||||
|
|
||||||
|
#[cfg(feature="alpn")]
|
||||||
|
impl IoStream for SslStream<TcpStream> {
|
||||||
|
#[inline]
|
||||||
|
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
|
||||||
|
let _ = self.get_mut().shutdown();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
|
||||||
|
self.get_mut().get_mut().set_nodelay(nodelay)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
|
||||||
|
self.get_mut().get_mut().set_linger(dur)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature="tls")]
|
||||||
|
use tokio_tls::TlsStream;
|
||||||
|
|
||||||
|
#[cfg(feature="tls")]
|
||||||
|
impl IoStream for TlsStream<TcpStream> {
|
||||||
|
#[inline]
|
||||||
|
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
|
||||||
|
let _ = self.get_mut().shutdown();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
|
||||||
|
self.get_mut().get_mut().set_nodelay(nodelay)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
|
||||||
|
self.get_mut().get_mut().set_linger(dur)
|
||||||
|
}
|
||||||
|
}
|
126
src/server/settings.rs
Normal file
126
src/server/settings.rs
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
use std::net;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::cell::{Cell, RefCell, RefMut};
|
||||||
|
|
||||||
|
use helpers;
|
||||||
|
use super::channel::Node;
|
||||||
|
use super::shared::{SharedBytes, SharedBytesPool};
|
||||||
|
|
||||||
|
/// Various server settings
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ServerSettings {
|
||||||
|
addr: Option<net::SocketAddr>,
|
||||||
|
secure: bool,
|
||||||
|
host: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ServerSettings {
|
||||||
|
fn default() -> Self {
|
||||||
|
ServerSettings {
|
||||||
|
addr: None,
|
||||||
|
secure: false,
|
||||||
|
host: "localhost:8080".to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServerSettings {
|
||||||
|
/// Crate server settings instance
|
||||||
|
pub(crate) fn new(addr: Option<net::SocketAddr>, host: &Option<String>, secure: bool)
|
||||||
|
-> ServerSettings
|
||||||
|
{
|
||||||
|
let host = if let Some(ref host) = *host {
|
||||||
|
host.clone()
|
||||||
|
} else if let Some(ref addr) = addr {
|
||||||
|
format!("{}", addr)
|
||||||
|
} else {
|
||||||
|
"localhost".to_owned()
|
||||||
|
};
|
||||||
|
ServerSettings {
|
||||||
|
addr: addr,
|
||||||
|
secure: secure,
|
||||||
|
host: host,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the socket address of the local half of this TCP connection
|
||||||
|
pub fn local_addr(&self) -> Option<net::SocketAddr> {
|
||||||
|
self.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if connection is secure(https)
|
||||||
|
pub fn secure(&self) -> bool {
|
||||||
|
self.secure
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns host header value
|
||||||
|
pub fn host(&self) -> &str {
|
||||||
|
&self.host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub(crate) struct WorkerSettings<H> {
|
||||||
|
h: RefCell<Vec<H>>,
|
||||||
|
enabled: bool,
|
||||||
|
keep_alive: u64,
|
||||||
|
bytes: Rc<SharedBytesPool>,
|
||||||
|
messages: Rc<helpers::SharedMessagePool>,
|
||||||
|
channels: Cell<usize>,
|
||||||
|
node: Node<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H> WorkerSettings<H> {
|
||||||
|
pub(crate) fn new(h: Vec<H>, keep_alive: Option<u64>) -> WorkerSettings<H> {
|
||||||
|
WorkerSettings {
|
||||||
|
h: RefCell::new(h),
|
||||||
|
enabled: if let Some(ka) = keep_alive { ka > 0 } else { false },
|
||||||
|
keep_alive: keep_alive.unwrap_or(0),
|
||||||
|
bytes: Rc::new(SharedBytesPool::new()),
|
||||||
|
messages: Rc::new(helpers::SharedMessagePool::new()),
|
||||||
|
channels: Cell::new(0),
|
||||||
|
node: Node::head(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn num_channels(&self) -> usize {
|
||||||
|
self.channels.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn head(&self) -> &Node<()> {
|
||||||
|
&self.node
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handlers(&self) -> RefMut<Vec<H>> {
|
||||||
|
self.h.borrow_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn keep_alive(&self) -> u64 {
|
||||||
|
self.keep_alive
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn keep_alive_enabled(&self) -> bool {
|
||||||
|
self.enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_shared_bytes(&self) -> SharedBytes {
|
||||||
|
SharedBytes::new(self.bytes.get_bytes(), Rc::clone(&self.bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_http_message(&self) -> helpers::SharedHttpMessage {
|
||||||
|
helpers::SharedHttpMessage::new(self.messages.get(), Rc::clone(&self.messages))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_channel(&self) {
|
||||||
|
self.channels.set(self.channels.get() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_channel(&self) {
|
||||||
|
let num = self.channels.get();
|
||||||
|
if num > 0 {
|
||||||
|
self.channels.set(num-1);
|
||||||
|
} else {
|
||||||
|
error!("Number of removed channels is bigger than added channel. Bug in actix-web");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
120
src/server/shared.rs
Normal file
120
src/server/shared.rs
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
use std::mem;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use bytes::BytesMut;
|
||||||
|
|
||||||
|
use body::Binary;
|
||||||
|
|
||||||
|
|
||||||
|
/// Internal use only! unsafe
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct SharedBytesPool(RefCell<VecDeque<Rc<BytesMut>>>);
|
||||||
|
|
||||||
|
impl SharedBytesPool {
|
||||||
|
pub fn new() -> SharedBytesPool {
|
||||||
|
SharedBytesPool(RefCell::new(VecDeque::with_capacity(128)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_bytes(&self) -> Rc<BytesMut> {
|
||||||
|
if let Some(bytes) = self.0.borrow_mut().pop_front() {
|
||||||
|
bytes
|
||||||
|
} else {
|
||||||
|
Rc::new(BytesMut::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn release_bytes(&self, mut bytes: Rc<BytesMut>) {
|
||||||
|
let v = &mut self.0.borrow_mut();
|
||||||
|
if v.len() < 128 {
|
||||||
|
Rc::get_mut(&mut bytes).unwrap().take();
|
||||||
|
v.push_front(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct SharedBytes(
|
||||||
|
Option<Rc<BytesMut>>, Option<Rc<SharedBytesPool>>);
|
||||||
|
|
||||||
|
impl Drop for SharedBytes {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(ref pool) = self.1 {
|
||||||
|
if let Some(bytes) = self.0.take() {
|
||||||
|
if Rc::strong_count(&bytes) == 1 {
|
||||||
|
pool.release_bytes(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SharedBytes {
|
||||||
|
|
||||||
|
pub fn empty() -> Self {
|
||||||
|
SharedBytes(None, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(bytes: Rc<BytesMut>, pool: Rc<SharedBytesPool>) -> SharedBytes {
|
||||||
|
SharedBytes(Some(bytes), Some(pool))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
#[allow(mutable_transmutes)]
|
||||||
|
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))]
|
||||||
|
pub fn get_mut(&self) -> &mut BytesMut {
|
||||||
|
let r: &BytesMut = self.0.as_ref().unwrap().as_ref();
|
||||||
|
unsafe{mem::transmute(r)}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.0.as_ref().unwrap().len()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.0.as_ref().unwrap().is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn as_ref(&self) -> &[u8] {
|
||||||
|
self.0.as_ref().unwrap().as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn split_to(&self, n: usize) -> BytesMut {
|
||||||
|
self.get_mut().split_to(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn take(&self) -> BytesMut {
|
||||||
|
self.get_mut().take()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn reserve(&self, cnt: usize) {
|
||||||
|
self.get_mut().reserve(cnt)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
|
||||||
|
pub fn extend(&self, data: Binary) {
|
||||||
|
self.get_mut().extend_from_slice(data.as_ref());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn extend_from_slice(&self, data: &[u8]) {
|
||||||
|
self.get_mut().extend_from_slice(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SharedBytes {
|
||||||
|
fn default() -> Self {
|
||||||
|
SharedBytes(Some(Rc::new(BytesMut::new())), None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for SharedBytes {
|
||||||
|
fn clone(&self) -> SharedBytes {
|
||||||
|
SharedBytes(self.0.clone(), self.1.clone())
|
||||||
|
}
|
||||||
|
}
|
@ -21,66 +21,17 @@ use native_tls::TlsAcceptor;
|
|||||||
use tokio_tls::TlsStream;
|
use tokio_tls::TlsStream;
|
||||||
|
|
||||||
#[cfg(feature="alpn")]
|
#[cfg(feature="alpn")]
|
||||||
use openssl::ssl::{SslMethod, SslAcceptorBuilder};
|
use openssl::ssl::{AlpnError, SslAcceptorBuilder};
|
||||||
#[cfg(feature="alpn")]
|
|
||||||
use openssl::pkcs12::ParsedPkcs12;
|
|
||||||
#[cfg(feature="alpn")]
|
#[cfg(feature="alpn")]
|
||||||
use tokio_openssl::SslStream;
|
use tokio_openssl::SslStream;
|
||||||
|
|
||||||
use helpers;
|
use helpers;
|
||||||
use channel::{HttpChannel, HttpHandler, IntoHttpHandler, IoStream, WrapperStream};
|
use super::{HttpHandler, IntoHttpHandler, IoStream};
|
||||||
use worker::{Conn, Worker, WorkerSettings, StreamHandlerType, StopWorker};
|
use super::{PauseServer, ResumeServer, StopServer};
|
||||||
|
use super::channel::{HttpChannel, WrapperStream};
|
||||||
|
use super::worker::{Conn, Worker, StreamHandlerType, StopWorker};
|
||||||
|
use super::settings::{ServerSettings, WorkerSettings};
|
||||||
|
|
||||||
/// Various server settings
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ServerSettings {
|
|
||||||
addr: Option<net::SocketAddr>,
|
|
||||||
secure: bool,
|
|
||||||
host: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ServerSettings {
|
|
||||||
fn default() -> Self {
|
|
||||||
ServerSettings {
|
|
||||||
addr: None,
|
|
||||||
secure: false,
|
|
||||||
host: "localhost:8080".to_owned(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ServerSettings {
|
|
||||||
/// Crate server settings instance
|
|
||||||
fn new(addr: Option<net::SocketAddr>, host: &Option<String>, secure: bool) -> Self {
|
|
||||||
let host = if let Some(ref host) = *host {
|
|
||||||
host.clone()
|
|
||||||
} else if let Some(ref addr) = addr {
|
|
||||||
format!("{}", addr)
|
|
||||||
} else {
|
|
||||||
"localhost".to_owned()
|
|
||||||
};
|
|
||||||
ServerSettings {
|
|
||||||
addr: addr,
|
|
||||||
secure: secure,
|
|
||||||
host: host,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the socket address of the local half of this TCP connection
|
|
||||||
pub fn local_addr(&self) -> Option<net::SocketAddr> {
|
|
||||||
self.addr
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if connection is secure(https)
|
|
||||||
pub fn secure(&self) -> bool {
|
|
||||||
self.secure
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns host header value
|
|
||||||
pub fn host(&self) -> &str {
|
|
||||||
&self.host
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An HTTP Server
|
/// An HTTP Server
|
||||||
///
|
///
|
||||||
@ -113,7 +64,13 @@ unsafe impl<T, A, H, U> Sync for HttpServer<T, A, H, U> where H: HttpHandler + '
|
|||||||
unsafe impl<T, A, H, U> Send for HttpServer<T, A, H, U> where H: HttpHandler + 'static {}
|
unsafe impl<T, A, H, U> Send for HttpServer<T, A, H, U> where H: HttpHandler + 'static {}
|
||||||
|
|
||||||
|
|
||||||
impl<T: 'static, A: 'static, H: HttpHandler + 'static, U: 'static> Actor for HttpServer<T, A, H, U> {
|
impl<T, A, H, U, V> Actor for HttpServer<T, A, H, U>
|
||||||
|
where A: 'static,
|
||||||
|
T: IoStream,
|
||||||
|
H: HttpHandler,
|
||||||
|
U: IntoIterator<Item=V> + 'static,
|
||||||
|
V: IntoHttpHandler<Handler=H>,
|
||||||
|
{
|
||||||
type Context = Context<Self>;
|
type Context = Context<Self>;
|
||||||
|
|
||||||
fn started(&mut self, ctx: &mut Self::Context) {
|
fn started(&mut self, ctx: &mut Self::Context) {
|
||||||
@ -121,13 +78,6 @@ impl<T: 'static, A: 'static, H: HttpHandler + 'static, U: 'static> Actor for Htt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: 'static, A: 'static, H: HttpHandler + 'static, U: 'static> HttpServer<T, A, H, U> {
|
|
||||||
fn update_time(&self, ctx: &mut Context<Self>) {
|
|
||||||
helpers::update_date();
|
|
||||||
ctx.run_later(Duration::new(1, 0), |slf, ctx| slf.update_time(ctx));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, A, H, U, V> HttpServer<T, A, H, U>
|
impl<T, A, H, U, V> HttpServer<T, A, H, U>
|
||||||
where A: 'static,
|
where A: 'static,
|
||||||
T: IoStream,
|
T: IoStream,
|
||||||
@ -157,6 +107,11 @@ impl<T, A, H, U, V> HttpServer<T, A, H, U>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_time(&self, ctx: &mut Context<Self>) {
|
||||||
|
helpers::update_date();
|
||||||
|
ctx.run_later(Duration::new(1, 0), |slf, ctx| slf.update_time(ctx));
|
||||||
|
}
|
||||||
|
|
||||||
/// Set number of workers to start.
|
/// Set number of workers to start.
|
||||||
///
|
///
|
||||||
/// By default http server uses number of available logical cpu as threads count.
|
/// By default http server uses number of available logical cpu as threads count.
|
||||||
@ -294,14 +249,15 @@ impl<T, A, H, U, V> HttpServer<T, A, H, U>
|
|||||||
}
|
}
|
||||||
|
|
||||||
// subscribe to os signals
|
// subscribe to os signals
|
||||||
fn subscribe_to_signals(&self, addr: &SyncAddress<HttpServer<T, A, H, U>>) {
|
fn subscribe_to_signals(&self) -> Option<SyncAddress<signal::ProcessSignals>> {
|
||||||
if self.no_signals {
|
if !self.no_signals {
|
||||||
let msg = signal::Subscribe(addr.subscriber());
|
|
||||||
if let Some(ref signals) = self.signals {
|
if let Some(ref signals) = self.signals {
|
||||||
signals.send(msg);
|
Some(signals.clone())
|
||||||
} else {
|
} else {
|
||||||
Arbiter::system_registry().get::<signal::ProcessSignals>().send(msg);
|
Some(Arbiter::system_registry().get::<signal::ProcessSignals>())
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -310,9 +266,9 @@ impl<H: HttpHandler, U, V> HttpServer<TcpStream, net::SocketAddr, H, U>
|
|||||||
where U: IntoIterator<Item=V> + 'static,
|
where U: IntoIterator<Item=V> + 'static,
|
||||||
V: IntoHttpHandler<Handler=H>,
|
V: IntoHttpHandler<Handler=H>,
|
||||||
{
|
{
|
||||||
/// Start listening for incomming connections.
|
/// Start listening for incoming connections.
|
||||||
///
|
///
|
||||||
/// This method starts number of http handler workers in seperate threads.
|
/// This method starts number of http handler workers in separate threads.
|
||||||
/// For each address this method starts separate thread which does `accept()` in a loop.
|
/// For each address this method starts separate thread which does `accept()` in a loop.
|
||||||
///
|
///
|
||||||
/// This methods panics if no socket addresses get bound.
|
/// This methods panics if no socket addresses get bound.
|
||||||
@ -340,7 +296,7 @@ impl<H: HttpHandler, U, V> HttpServer<TcpStream, net::SocketAddr, H, U>
|
|||||||
pub fn start(mut self) -> SyncAddress<Self>
|
pub fn start(mut self) -> SyncAddress<Self>
|
||||||
{
|
{
|
||||||
if self.sockets.is_empty() {
|
if self.sockets.is_empty() {
|
||||||
panic!("HttpServer::bind() has to be called befor start()");
|
panic!("HttpServer::bind() has to be called before start()");
|
||||||
} else {
|
} else {
|
||||||
let addrs: Vec<(net::SocketAddr, net::TcpListener)> =
|
let addrs: Vec<(net::SocketAddr, net::TcpListener)> =
|
||||||
self.sockets.drain().collect();
|
self.sockets.drain().collect();
|
||||||
@ -355,14 +311,14 @@ impl<H: HttpHandler, U, V> HttpServer<TcpStream, net::SocketAddr, H, U>
|
|||||||
}
|
}
|
||||||
|
|
||||||
// start http server actor
|
// start http server actor
|
||||||
HttpServer::create(|ctx| {
|
let signals = self.subscribe_to_signals();
|
||||||
self.subscribe_to_signals(&ctx.address());
|
let addr: SyncAddress<_> = Actor::start(self);
|
||||||
self
|
signals.map(|signals| signals.send(signal::Subscribe(addr.subscriber())));
|
||||||
})
|
addr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Spawn new thread and start listening for incomming connections.
|
/// Spawn new thread and start listening for incoming connections.
|
||||||
///
|
///
|
||||||
/// This method spawns new thread and starts new actix system. Other than that it is
|
/// This method spawns new thread and starts new actix system. Other than that it is
|
||||||
/// similar to `start()` method. This method blocks.
|
/// similar to `start()` method. This method blocks.
|
||||||
@ -401,7 +357,7 @@ impl<H: HttpHandler, U, V> HttpServer<TlsStream<TcpStream>, net::SocketAddr, H,
|
|||||||
where U: IntoIterator<Item=V> + 'static,
|
where U: IntoIterator<Item=V> + 'static,
|
||||||
V: IntoHttpHandler<Handler=H>,
|
V: IntoHttpHandler<Handler=H>,
|
||||||
{
|
{
|
||||||
/// Start listening for incomming tls connections.
|
/// Start listening for incoming tls connections.
|
||||||
pub fn start_tls(mut self, pkcs12: ::Pkcs12) -> io::Result<SyncAddress<Self>> {
|
pub fn start_tls(mut self, pkcs12: ::Pkcs12) -> io::Result<SyncAddress<Self>> {
|
||||||
if self.sockets.is_empty() {
|
if self.sockets.is_empty() {
|
||||||
Err(io::Error::new(io::ErrorKind::Other, "No socket addresses are bound"))
|
Err(io::Error::new(io::ErrorKind::Other, "No socket addresses are bound"))
|
||||||
@ -427,10 +383,10 @@ impl<H: HttpHandler, U, V> HttpServer<TlsStream<TcpStream>, net::SocketAddr, H,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// start http server actor
|
// start http server actor
|
||||||
Ok(HttpServer::create(|ctx| {
|
let signals = self.subscribe_to_signals();
|
||||||
self.subscribe_to_signals(&ctx.address());
|
let addr: SyncAddress<_> = Actor::start(self);
|
||||||
self
|
signals.map(|signals| signals.send(signal::Subscribe(addr.subscriber())));
|
||||||
}))
|
Ok(addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -440,26 +396,28 @@ impl<H: HttpHandler, U, V> HttpServer<SslStream<TcpStream>, net::SocketAddr, H,
|
|||||||
where U: IntoIterator<Item=V> + 'static,
|
where U: IntoIterator<Item=V> + 'static,
|
||||||
V: IntoHttpHandler<Handler=H>,
|
V: IntoHttpHandler<Handler=H>,
|
||||||
{
|
{
|
||||||
/// Start listening for incomming tls connections.
|
/// Start listening for incoming tls connections.
|
||||||
///
|
///
|
||||||
/// This method sets alpn protocols to "h2" and "http/1.1"
|
/// This method sets alpn protocols to "h2" and "http/1.1"
|
||||||
pub fn start_ssl(mut self, identity: &ParsedPkcs12) -> io::Result<SyncAddress<Self>> {
|
pub fn start_ssl(mut self, mut builder: SslAcceptorBuilder) -> io::Result<SyncAddress<Self>>
|
||||||
|
{
|
||||||
if self.sockets.is_empty() {
|
if self.sockets.is_empty() {
|
||||||
Err(io::Error::new(io::ErrorKind::Other, "No socket addresses are bound"))
|
Err(io::Error::new(io::ErrorKind::Other, "No socket addresses are bound"))
|
||||||
} else {
|
} else {
|
||||||
|
// alpn support
|
||||||
|
builder.set_alpn_protos(b"\x02h2\x08http/1.1")?;
|
||||||
|
builder.set_alpn_select_callback(|_, protos| {
|
||||||
|
const H2: &[u8] = b"\x02h2";
|
||||||
|
if protos.windows(3).any(|window| window == H2) {
|
||||||
|
Ok(b"h2")
|
||||||
|
} else {
|
||||||
|
Err(AlpnError::NOACK)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let acceptor = builder.build();
|
||||||
let addrs: Vec<(net::SocketAddr, net::TcpListener)> = self.sockets.drain().collect();
|
let addrs: Vec<(net::SocketAddr, net::TcpListener)> = self.sockets.drain().collect();
|
||||||
let settings = ServerSettings::new(Some(addrs[0].0), &self.host, false);
|
let settings = ServerSettings::new(Some(addrs[0].0), &self.host, false);
|
||||||
let acceptor = match SslAcceptorBuilder::mozilla_intermediate(
|
|
||||||
SslMethod::tls(), &identity.pkey, &identity.cert, &identity.chain)
|
|
||||||
{
|
|
||||||
Ok(mut builder) => {
|
|
||||||
match builder.set_alpn_protocols(&[b"h2", b"http/1.1"]) {
|
|
||||||
Ok(_) => builder.build(),
|
|
||||||
Err(err) => return Err(io::Error::new(io::ErrorKind::Other, err)),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(err) => return Err(io::Error::new(io::ErrorKind::Other, err))
|
|
||||||
};
|
|
||||||
let workers = self.start_workers(&settings, &StreamHandlerType::Alpn(acceptor));
|
let workers = self.start_workers(&settings, &StreamHandlerType::Alpn(acceptor));
|
||||||
|
|
||||||
// start acceptors threads
|
// start acceptors threads
|
||||||
@ -470,10 +428,10 @@ impl<H: HttpHandler, U, V> HttpServer<SslStream<TcpStream>, net::SocketAddr, H,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// start http server actor
|
// start http server actor
|
||||||
Ok(HttpServer::create(|ctx| {
|
let signals = self.subscribe_to_signals();
|
||||||
self.subscribe_to_signals(&ctx.address());
|
let addr: SyncAddress<_> = Actor::start(self);
|
||||||
self
|
signals.map(|signals| signals.send(signal::Subscribe(addr.subscriber())));
|
||||||
}))
|
Ok(addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -485,7 +443,7 @@ impl<T, A, H, U, V> HttpServer<WrapperStream<T>, A, H, U>
|
|||||||
U: IntoIterator<Item=V> + 'static,
|
U: IntoIterator<Item=V> + 'static,
|
||||||
V: IntoHttpHandler<Handler=H>,
|
V: IntoHttpHandler<Handler=H>,
|
||||||
{
|
{
|
||||||
/// Start listening for incomming connections from a stream.
|
/// Start listening for incoming connections from a stream.
|
||||||
///
|
///
|
||||||
/// This method uses only one thread for handling incoming connections.
|
/// This method uses only one thread for handling incoming connections.
|
||||||
pub fn start_incoming<S>(mut self, stream: S, secure: bool) -> SyncAddress<Self>
|
pub fn start_incoming<S>(mut self, stream: S, secure: bool) -> SyncAddress<Self>
|
||||||
@ -514,22 +472,25 @@ impl<T, A, H, U, V> HttpServer<WrapperStream<T>, A, H, U>
|
|||||||
self.h = Some(Rc::new(WorkerSettings::new(apps, self.keep_alive)));
|
self.h = Some(Rc::new(WorkerSettings::new(apps, self.keep_alive)));
|
||||||
|
|
||||||
// start server
|
// start server
|
||||||
HttpServer::create(move |ctx| {
|
let signals = self.subscribe_to_signals();
|
||||||
|
let addr: SyncAddress<_> = HttpServer::create(move |ctx| {
|
||||||
ctx.add_stream(stream.map(
|
ctx.add_stream(stream.map(
|
||||||
move |(t, _)| Conn{io: WrapperStream::new(t), peer: None, http2: false}));
|
move |(t, _)| Conn{io: WrapperStream::new(t), peer: None, http2: false}));
|
||||||
self.subscribe_to_signals(&ctx.address());
|
|
||||||
self
|
self
|
||||||
})
|
});
|
||||||
|
signals.map(|signals| signals.send(signal::Subscribe(addr.subscriber())));
|
||||||
|
addr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Signals support
|
/// Signals support
|
||||||
/// Handle `SIGINT`, `SIGTERM`, `SIGQUIT` signals and send `SystemExit(0)`
|
/// Handle `SIGINT`, `SIGTERM`, `SIGQUIT` signals and send `SystemExit(0)`
|
||||||
/// message to `System` actor.
|
/// message to `System` actor.
|
||||||
impl<T, A, H, U> Handler<signal::Signal> for HttpServer<T, A, H, U>
|
impl<T, A, H, U, V> Handler<signal::Signal> for HttpServer<T, A, H, U>
|
||||||
where T: IoStream,
|
where T: IoStream,
|
||||||
H: HttpHandler + 'static,
|
H: HttpHandler + 'static,
|
||||||
U: 'static,
|
U: IntoIterator<Item=V> + 'static,
|
||||||
|
V: IntoHttpHandler<Handler=H>,
|
||||||
A: 'static,
|
A: 'static,
|
||||||
{
|
{
|
||||||
type Result = ();
|
type Result = ();
|
||||||
@ -556,10 +517,11 @@ impl<T, A, H, U> Handler<signal::Signal> for HttpServer<T, A, H, U>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, A, H, U> Handler<io::Result<Conn<T>>> for HttpServer<T, A, H, U>
|
impl<T, A, H, U, V> Handler<io::Result<Conn<T>>> for HttpServer<T, A, H, U>
|
||||||
where T: IoStream,
|
where T: IoStream,
|
||||||
H: HttpHandler + 'static,
|
H: HttpHandler + 'static,
|
||||||
U: 'static,
|
U: IntoIterator<Item=V> + 'static,
|
||||||
|
V: IntoHttpHandler<Handler=H>,
|
||||||
A: 'static,
|
A: 'static,
|
||||||
{
|
{
|
||||||
type Result = ();
|
type Result = ();
|
||||||
@ -576,29 +538,11 @@ impl<T, A, H, U> Handler<io::Result<Conn<T>>> for HttpServer<T, A, H, U>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pause accepting incoming connections
|
impl<T, A, H, U, V> Handler<PauseServer> for HttpServer<T, A, H, U>
|
||||||
///
|
|
||||||
/// If socket contains some pending connection, they might be dropped.
|
|
||||||
/// All opened connection remains active.
|
|
||||||
#[derive(Message)]
|
|
||||||
pub struct PauseServer;
|
|
||||||
|
|
||||||
/// Resume accepting incoming connections
|
|
||||||
#[derive(Message)]
|
|
||||||
pub struct ResumeServer;
|
|
||||||
|
|
||||||
/// Stop incoming connection processing, stop all workers and exit.
|
|
||||||
///
|
|
||||||
/// If server starts with `spawn()` method, then spawned thread get terminated.
|
|
||||||
#[derive(Message)]
|
|
||||||
pub struct StopServer {
|
|
||||||
pub graceful: bool
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, A, H, U> Handler<PauseServer> for HttpServer<T, A, H, U>
|
|
||||||
where T: IoStream,
|
where T: IoStream,
|
||||||
H: HttpHandler + 'static,
|
H: HttpHandler + 'static,
|
||||||
U: 'static,
|
U: IntoIterator<Item=V> + 'static,
|
||||||
|
V: IntoHttpHandler<Handler=H>,
|
||||||
A: 'static,
|
A: 'static,
|
||||||
{
|
{
|
||||||
type Result = ();
|
type Result = ();
|
||||||
@ -612,10 +556,11 @@ impl<T, A, H, U> Handler<PauseServer> for HttpServer<T, A, H, U>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, A, H, U> Handler<ResumeServer> for HttpServer<T, A, H, U>
|
impl<T, A, H, U, V> Handler<ResumeServer> for HttpServer<T, A, H, U>
|
||||||
where T: IoStream,
|
where T: IoStream,
|
||||||
H: HttpHandler + 'static,
|
H: HttpHandler + 'static,
|
||||||
U: 'static,
|
U: IntoIterator<Item=V> + 'static,
|
||||||
|
V: IntoHttpHandler<Handler=H>,
|
||||||
A: 'static,
|
A: 'static,
|
||||||
{
|
{
|
||||||
type Result = ();
|
type Result = ();
|
||||||
@ -628,10 +573,11 @@ impl<T, A, H, U> Handler<ResumeServer> for HttpServer<T, A, H, U>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, A, H, U> Handler<StopServer> for HttpServer<T, A, H, U>
|
impl<T, A, H, U, V> Handler<StopServer> for HttpServer<T, A, H, U>
|
||||||
where T: IoStream,
|
where T: IoStream,
|
||||||
H: HttpHandler + 'static,
|
H: HttpHandler + 'static,
|
||||||
U: 'static,
|
U: IntoIterator<Item=V> + 'static,
|
||||||
|
V: IntoHttpHandler<Handler=H>,
|
||||||
A: 'static,
|
A: 'static,
|
||||||
{
|
{
|
||||||
type Result = actix::Response<Self, StopServer>;
|
type Result = actix::Response<Self, StopServer>;
|
||||||
@ -717,7 +663,7 @@ fn start_accept_thread(sock: net::TcpListener, addr: net::SocketAddr, backlog: i
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start listening for incommin commands
|
// Start listening for incoming commands
|
||||||
if let Err(err) = poll.register(®, CMD,
|
if let Err(err) = poll.register(®, CMD,
|
||||||
mio::Ready::readable(), mio::PollOpt::edge()) {
|
mio::Ready::readable(), mio::PollOpt::edge()) {
|
||||||
panic!("Can not register Registration: {}", err);
|
panic!("Can not register Registration: {}", err);
|
30
src/server/utils.rs
Normal file
30
src/server/utils.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
use std::io;
|
||||||
|
use bytes::{BytesMut, BufMut};
|
||||||
|
use futures::{Async, Poll};
|
||||||
|
|
||||||
|
use super::IoStream;
|
||||||
|
|
||||||
|
const LW_BUFFER_SIZE: usize = 4096;
|
||||||
|
const HW_BUFFER_SIZE: usize = 16_384;
|
||||||
|
|
||||||
|
|
||||||
|
pub fn read_from_io<T: IoStream>(io: &mut T, buf: &mut BytesMut) -> Poll<usize, io::Error> {
|
||||||
|
unsafe {
|
||||||
|
if buf.remaining_mut() < LW_BUFFER_SIZE {
|
||||||
|
buf.reserve(HW_BUFFER_SIZE);
|
||||||
|
}
|
||||||
|
match io.read(buf.bytes_mut()) {
|
||||||
|
Ok(n) => {
|
||||||
|
buf.advance_mut(n);
|
||||||
|
Ok(Async::Ready(n))
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
if e.kind() == io::ErrorKind::WouldBlock {
|
||||||
|
Ok(Async::NotReady)
|
||||||
|
} else {
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,5 @@
|
|||||||
use std::{net, time};
|
use std::{net, time};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::cell::{Cell, RefCell, RefMut};
|
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
use futures::unsync::oneshot;
|
use futures::unsync::oneshot;
|
||||||
use tokio_core::net::TcpStream;
|
use tokio_core::net::TcpStream;
|
||||||
@ -25,7 +24,9 @@ use actix::*;
|
|||||||
use actix::msgs::StopArbiter;
|
use actix::msgs::StopArbiter;
|
||||||
|
|
||||||
use helpers;
|
use helpers;
|
||||||
use channel::{HttpChannel, HttpHandler, Node};
|
use server::HttpHandler;
|
||||||
|
use server::channel::HttpChannel;
|
||||||
|
use server::settings::WorkerSettings;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Message)]
|
#[derive(Message)]
|
||||||
@ -43,60 +44,6 @@ pub(crate) struct StopWorker {
|
|||||||
pub graceful: Option<time::Duration>,
|
pub graceful: Option<time::Duration>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct WorkerSettings<H> {
|
|
||||||
h: RefCell<Vec<H>>,
|
|
||||||
enabled: bool,
|
|
||||||
keep_alive: u64,
|
|
||||||
bytes: Rc<helpers::SharedBytesPool>,
|
|
||||||
messages: Rc<helpers::SharedMessagePool>,
|
|
||||||
channels: Cell<usize>,
|
|
||||||
node: Node<()>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<H> WorkerSettings<H> {
|
|
||||||
pub(crate) fn new(h: Vec<H>, keep_alive: Option<u64>) -> WorkerSettings<H> {
|
|
||||||
WorkerSettings {
|
|
||||||
h: RefCell::new(h),
|
|
||||||
enabled: if let Some(ka) = keep_alive { ka > 0 } else { false },
|
|
||||||
keep_alive: keep_alive.unwrap_or(0),
|
|
||||||
bytes: Rc::new(helpers::SharedBytesPool::new()),
|
|
||||||
messages: Rc::new(helpers::SharedMessagePool::new()),
|
|
||||||
channels: Cell::new(0),
|
|
||||||
node: Node::head(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn head(&self) -> &Node<()> {
|
|
||||||
&self.node
|
|
||||||
}
|
|
||||||
pub fn handlers(&self) -> RefMut<Vec<H>> {
|
|
||||||
self.h.borrow_mut()
|
|
||||||
}
|
|
||||||
pub fn keep_alive(&self) -> u64 {
|
|
||||||
self.keep_alive
|
|
||||||
}
|
|
||||||
pub fn keep_alive_enabled(&self) -> bool {
|
|
||||||
self.enabled
|
|
||||||
}
|
|
||||||
pub fn get_shared_bytes(&self) -> helpers::SharedBytes {
|
|
||||||
helpers::SharedBytes::new(self.bytes.get_bytes(), Rc::clone(&self.bytes))
|
|
||||||
}
|
|
||||||
pub fn get_http_message(&self) -> helpers::SharedHttpMessage {
|
|
||||||
helpers::SharedHttpMessage::new(self.messages.get(), Rc::clone(&self.messages))
|
|
||||||
}
|
|
||||||
pub fn add_channel(&self) {
|
|
||||||
self.channels.set(self.channels.get()+1);
|
|
||||||
}
|
|
||||||
pub fn remove_channel(&self) {
|
|
||||||
let num = self.channels.get();
|
|
||||||
if num > 0 {
|
|
||||||
self.channels.set(num-1);
|
|
||||||
} else {
|
|
||||||
error!("Number of removed channels is bigger than added channel. Bug in actix-web");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Http worker
|
/// Http worker
|
||||||
///
|
///
|
||||||
/// Worker accepts Socket objects via unbounded channel and start requests processing.
|
/// Worker accepts Socket objects via unbounded channel and start requests processing.
|
||||||
@ -127,7 +74,7 @@ impl<H: HttpHandler + 'static> Worker<H> {
|
|||||||
tx: oneshot::Sender<bool>, dur: time::Duration) {
|
tx: oneshot::Sender<bool>, dur: time::Duration) {
|
||||||
// sleep for 1 second and then check again
|
// sleep for 1 second and then check again
|
||||||
ctx.run_later(time::Duration::new(1, 0), move |slf, ctx| {
|
ctx.run_later(time::Duration::new(1, 0), move |slf, ctx| {
|
||||||
let num = slf.settings.channels.get();
|
let num = slf.settings.num_channels();
|
||||||
if num == 0 {
|
if num == 0 {
|
||||||
let _ = tx.send(true);
|
let _ = tx.send(true);
|
||||||
Arbiter::arbiter().send(StopArbiter(0));
|
Arbiter::arbiter().send(StopArbiter(0));
|
||||||
@ -174,7 +121,7 @@ impl<H> Handler<StopWorker> for Worker<H>
|
|||||||
type Result = Response<Self, StopWorker>;
|
type Result = Response<Self, StopWorker>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: StopWorker, ctx: &mut Context<Self>) -> Self::Result {
|
fn handle(&mut self, msg: StopWorker, ctx: &mut Context<Self>) -> Self::Result {
|
||||||
let num = self.settings.channels.get();
|
let num = self.settings.num_channels();
|
||||||
if num == 0 {
|
if num == 0 {
|
||||||
info!("Shutting down http worker, 0 connections");
|
info!("Shutting down http worker, 0 connections");
|
||||||
Self::reply(Ok(true))
|
Self::reply(Ok(true))
|
28
src/test.rs
28
src/test.rs
@ -16,9 +16,7 @@ use tokio_core::reactor::Core;
|
|||||||
use net2::TcpBuilder;
|
use net2::TcpBuilder;
|
||||||
|
|
||||||
use error::Error;
|
use error::Error;
|
||||||
use server::{HttpServer, ServerSettings};
|
|
||||||
use handler::{Handler, Responder, ReplyItem};
|
use handler::{Handler, Responder, ReplyItem};
|
||||||
use channel::{HttpHandler, IntoHttpHandler};
|
|
||||||
use middleware::Middleware;
|
use middleware::Middleware;
|
||||||
use application::{Application, HttpApplication};
|
use application::{Application, HttpApplication};
|
||||||
use param::Params;
|
use param::Params;
|
||||||
@ -26,11 +24,12 @@ use router::Router;
|
|||||||
use payload::Payload;
|
use payload::Payload;
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
|
use server::{HttpServer, HttpHandler, IntoHttpHandler, ServerSettings};
|
||||||
|
|
||||||
/// The `TestServer` type.
|
/// The `TestServer` type.
|
||||||
///
|
///
|
||||||
/// `TestServer` is very simple test server that simplify process of writing
|
/// `TestServer` is very simple test server that simplify process of writing
|
||||||
/// integrational tests cases for actix web applications.
|
/// integration tests cases for actix web applications.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
@ -62,7 +61,7 @@ impl TestServer {
|
|||||||
|
|
||||||
/// Start new test server
|
/// Start new test server
|
||||||
///
|
///
|
||||||
/// This methos accepts configuration method. You can add
|
/// This method accepts configuration method. You can add
|
||||||
/// middlewares or set handlers for test application.
|
/// middlewares or set handlers for test application.
|
||||||
pub fn new<F>(config: F) -> Self
|
pub fn new<F>(config: F) -> Self
|
||||||
where F: Sync + Send + 'static + Fn(&mut TestApp<()>),
|
where F: Sync + Send + 'static + Fn(&mut TestApp<()>),
|
||||||
@ -102,7 +101,7 @@ impl TestServer {
|
|||||||
|
|
||||||
/// Start new test server with custom application state
|
/// Start new test server with custom application state
|
||||||
///
|
///
|
||||||
/// This methos accepts state factory and configuration method.
|
/// This method accepts state factory and configuration method.
|
||||||
pub fn with_state<S, FS, F>(state: FS, config: F) -> Self
|
pub fn with_state<S, FS, F>(state: FS, config: F) -> Self
|
||||||
where S: 'static,
|
where S: 'static,
|
||||||
FS: Sync + Send + 'static + Fn() -> S,
|
FS: Sync + Send + 'static + Fn() -> S,
|
||||||
@ -146,6 +145,11 @@ impl TestServer {
|
|||||||
tcp.local_addr().unwrap()
|
tcp.local_addr().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Construct test server url
|
||||||
|
pub fn addr(&self) -> net::SocketAddr {
|
||||||
|
self.addr
|
||||||
|
}
|
||||||
|
|
||||||
/// Construct test server url
|
/// Construct test server url
|
||||||
pub fn url(&self, uri: &str) -> String {
|
pub fn url(&self, uri: &str) -> String {
|
||||||
if uri.starts_with('/') {
|
if uri.starts_with('/') {
|
||||||
@ -187,6 +191,16 @@ impl<S: 'static> TestApp<S> {
|
|||||||
self.app = Some(self.app.take().unwrap().resource("/", |r| r.h(handler)));
|
self.app = Some(self.app.take().unwrap().resource("/", |r| r.h(handler)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Register handler for "/" with resource middleware
|
||||||
|
pub fn handler2<H, M>(&mut self, handler: H, mw: M)
|
||||||
|
where H: Handler<S>, M: Middleware<S>
|
||||||
|
{
|
||||||
|
self.app = Some(self.app.take().unwrap()
|
||||||
|
.resource("/", |r| {
|
||||||
|
r.middleware(mw);
|
||||||
|
r.h(handler)}));
|
||||||
|
}
|
||||||
|
|
||||||
/// Register middleware
|
/// Register middleware
|
||||||
pub fn middleware<T>(&mut self, mw: T) -> &mut TestApp<S>
|
pub fn middleware<T>(&mut self, mw: T) -> &mut TestApp<S>
|
||||||
where T: Middleware<S> + 'static
|
where T: Middleware<S> + 'static
|
||||||
@ -273,12 +287,12 @@ impl Default for TestRequest<()> {
|
|||||||
|
|
||||||
impl TestRequest<()> {
|
impl TestRequest<()> {
|
||||||
|
|
||||||
/// Create TestReqeust and set request uri
|
/// Create TestRequest and set request uri
|
||||||
pub fn with_uri(path: &str) -> TestRequest<()> {
|
pub fn with_uri(path: &str) -> TestRequest<()> {
|
||||||
TestRequest::default().uri(path)
|
TestRequest::default().uri(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create TestReqeust and set header
|
/// Create TestRequest and set header
|
||||||
pub fn with_header<K, V>(key: K, value: V) -> TestRequest<()>
|
pub fn with_header<K, V>(key: K, value: V) -> TestRequest<()>
|
||||||
where HeaderName: HttpTryFrom<K>,
|
where HeaderName: HttpTryFrom<K>,
|
||||||
HeaderValue: HttpTryFrom<V>
|
HeaderValue: HttpTryFrom<V>
|
||||||
|
270
src/ws/context.rs
Normal file
270
src/ws/context.rs
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
use std::mem;
|
||||||
|
use futures::{Async, Poll};
|
||||||
|
use futures::sync::oneshot::Sender;
|
||||||
|
use futures::unsync::oneshot;
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
use actix::{Actor, ActorState, ActorContext, AsyncContext,
|
||||||
|
Address, SyncAddress, Handler, Subscriber, ResponseType, SpawnHandle};
|
||||||
|
use actix::fut::ActorFuture;
|
||||||
|
use actix::dev::{queue, AsyncContextApi,
|
||||||
|
ContextImpl, ContextProtocol, Envelope, ToEnvelope, RemoteEnvelope};
|
||||||
|
|
||||||
|
use body::{Body, Binary};
|
||||||
|
use error::{Error, Result, ErrorInternalServerError};
|
||||||
|
use httprequest::HttpRequest;
|
||||||
|
use context::{Frame as ContextFrame, ActorHttpContext, Drain};
|
||||||
|
|
||||||
|
use ws::frame::Frame;
|
||||||
|
use ws::proto::{OpCode, CloseCode};
|
||||||
|
|
||||||
|
|
||||||
|
/// Http actor execution context
|
||||||
|
pub struct WebsocketContext<A, S=()> where A: Actor<Context=WebsocketContext<A, S>>,
|
||||||
|
{
|
||||||
|
inner: ContextImpl<A>,
|
||||||
|
stream: Option<SmallVec<[ContextFrame; 4]>>,
|
||||||
|
request: HttpRequest<S>,
|
||||||
|
disconnected: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A, S> ActorContext for WebsocketContext<A, S> where A: Actor<Context=Self>
|
||||||
|
{
|
||||||
|
fn stop(&mut self) {
|
||||||
|
self.inner.stop();
|
||||||
|
}
|
||||||
|
fn terminate(&mut self) {
|
||||||
|
self.inner.terminate()
|
||||||
|
}
|
||||||
|
fn state(&self) -> ActorState {
|
||||||
|
self.inner.state()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A, S> AsyncContext<A> for WebsocketContext<A, S> where A: Actor<Context=Self>
|
||||||
|
{
|
||||||
|
fn spawn<F>(&mut self, fut: F) -> SpawnHandle
|
||||||
|
where F: ActorFuture<Item=(), Error=(), Actor=A> + 'static
|
||||||
|
{
|
||||||
|
self.inner.spawn(fut)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait<F>(&mut self, fut: F)
|
||||||
|
where F: ActorFuture<Item=(), Error=(), Actor=A> + 'static
|
||||||
|
{
|
||||||
|
self.inner.wait(fut)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[inline]
|
||||||
|
fn waiting(&self) -> bool {
|
||||||
|
self.inner.waiting() || self.inner.state() == ActorState::Stopping ||
|
||||||
|
self.inner.state() == ActorState::Stopped
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cancel_future(&mut self, handle: SpawnHandle) -> bool {
|
||||||
|
self.inner.cancel_future(handle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
impl<A, S> AsyncContextApi<A> for WebsocketContext<A, S> where A: Actor<Context=Self> {
|
||||||
|
#[inline]
|
||||||
|
fn unsync_sender(&mut self) -> queue::unsync::UnboundedSender<ContextProtocol<A>> {
|
||||||
|
self.inner.unsync_sender()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn unsync_address(&mut self) -> Address<A> {
|
||||||
|
self.inner.unsync_address()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn sync_address(&mut self) -> SyncAddress<A> {
|
||||||
|
self.inner.sync_address()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A, S: 'static> WebsocketContext<A, S> where A: Actor<Context=Self> {
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn new(req: HttpRequest<S>, actor: A) -> WebsocketContext<A, S> {
|
||||||
|
WebsocketContext::from_request(req).actor(actor)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_request(req: HttpRequest<S>) -> WebsocketContext<A, S> {
|
||||||
|
WebsocketContext {
|
||||||
|
inner: ContextImpl::new(None),
|
||||||
|
stream: None,
|
||||||
|
request: req,
|
||||||
|
disconnected: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn actor(mut self, actor: A) -> WebsocketContext<A, S> {
|
||||||
|
self.inner.set_actor(actor);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A, S> WebsocketContext<A, S> where A: Actor<Context=Self> {
|
||||||
|
|
||||||
|
/// Write payload
|
||||||
|
#[inline]
|
||||||
|
fn write<B: Into<Binary>>(&mut self, data: B) {
|
||||||
|
if !self.disconnected {
|
||||||
|
self.add_frame(ContextFrame::Chunk(Some(data.into())));
|
||||||
|
} else {
|
||||||
|
warn!("Trying to write to disconnected response");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shared application state
|
||||||
|
#[inline]
|
||||||
|
pub fn state(&self) -> &S {
|
||||||
|
self.request.state()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Incoming request
|
||||||
|
#[inline]
|
||||||
|
pub fn request(&mut self) -> &mut HttpRequest<S> {
|
||||||
|
&mut self.request
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send text frame
|
||||||
|
pub fn text(&mut self, text: &str) {
|
||||||
|
let mut frame = Frame::message(Vec::from(text), OpCode::Text, true);
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
frame.format(&mut buf).unwrap();
|
||||||
|
|
||||||
|
self.write(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send binary frame
|
||||||
|
pub fn binary<B: Into<Binary>>(&mut self, data: B) {
|
||||||
|
let mut frame = Frame::message(data, OpCode::Binary, true);
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
frame.format(&mut buf).unwrap();
|
||||||
|
|
||||||
|
self.write(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send ping frame
|
||||||
|
pub fn ping(&mut self, message: &str) {
|
||||||
|
let mut frame = Frame::message(Vec::from(message), OpCode::Ping, true);
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
frame.format(&mut buf).unwrap();
|
||||||
|
|
||||||
|
self.write(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send pong frame
|
||||||
|
pub fn pong(&mut self, message: &str) {
|
||||||
|
let mut frame = Frame::message(Vec::from(message), OpCode::Pong, true);
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
frame.format(&mut buf).unwrap();
|
||||||
|
|
||||||
|
self.write(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send close frame
|
||||||
|
pub fn close(&mut self, code: CloseCode, reason: &str) {
|
||||||
|
let mut frame = Frame::close(code, reason);
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
frame.format(&mut buf).unwrap();
|
||||||
|
self.write(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns drain future
|
||||||
|
pub fn drain(&mut self) -> Drain<A> {
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
self.inner.modify();
|
||||||
|
self.add_frame(ContextFrame::Drain(tx));
|
||||||
|
Drain::new(rx)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if connection still open
|
||||||
|
#[inline]
|
||||||
|
pub fn connected(&self) -> bool {
|
||||||
|
!self.disconnected
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_frame(&mut self, frame: ContextFrame) {
|
||||||
|
if self.stream.is_none() {
|
||||||
|
self.stream = Some(SmallVec::new());
|
||||||
|
}
|
||||||
|
self.stream.as_mut().map(|s| s.push(frame));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A, S> WebsocketContext<A, S> where A: Actor<Context=Self> {
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn subscriber<M>(&mut self) -> Box<Subscriber<M>>
|
||||||
|
where A: Handler<M>, M: ResponseType + 'static
|
||||||
|
{
|
||||||
|
self.inner.subscriber()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn sync_subscriber<M>(&mut self) -> Box<Subscriber<M> + Send>
|
||||||
|
where A: Handler<M>,
|
||||||
|
M: ResponseType + Send + 'static, M::Item: Send, M::Error: Send,
|
||||||
|
{
|
||||||
|
self.inner.sync_subscriber()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A, S> ActorHttpContext for WebsocketContext<A, S> where A: Actor<Context=Self>, S: 'static {
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn disconnected(&mut self) {
|
||||||
|
self.disconnected = true;
|
||||||
|
self.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<Option<SmallVec<[ContextFrame; 4]>>, Error> {
|
||||||
|
let ctx: &mut WebsocketContext<A, S> = unsafe {
|
||||||
|
mem::transmute(self as &mut WebsocketContext<A, S>)
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.inner.alive() {
|
||||||
|
match self.inner.poll(ctx) {
|
||||||
|
Ok(Async::NotReady) | Ok(Async::Ready(())) => (),
|
||||||
|
Err(_) => return Err(ErrorInternalServerError("error").into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// frames
|
||||||
|
if let Some(data) = self.stream.take() {
|
||||||
|
Ok(Async::Ready(Some(data)))
|
||||||
|
} else if self.inner.alive() {
|
||||||
|
Ok(Async::NotReady)
|
||||||
|
} else {
|
||||||
|
Ok(Async::Ready(None))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A, S> ToEnvelope<A> for WebsocketContext<A, S>
|
||||||
|
where A: Actor<Context=WebsocketContext<A, S>>,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn pack<M>(msg: M, tx: Option<Sender<Result<M::Item, M::Error>>>,
|
||||||
|
channel_on_drop: bool) -> Envelope<A>
|
||||||
|
where A: Handler<M>,
|
||||||
|
M: ResponseType + Send + 'static, M::Item: Send, M::Error: Send {
|
||||||
|
RemoteEnvelope::new(msg, tx, channel_on_drop).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A, S> From<WebsocketContext<A, S>> for Body
|
||||||
|
where A: Actor<Context=WebsocketContext<A, S>>, S: 'static
|
||||||
|
{
|
||||||
|
fn from(ctx: WebsocketContext<A, S>) -> Body {
|
||||||
|
Body::Actor(Box::new(ctx))
|
||||||
|
}
|
||||||
|
}
|
@ -3,18 +3,12 @@ use std::io::{Write, Error, ErrorKind};
|
|||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
|
|
||||||
use wsproto::{OpCode, CloseCode};
|
use body::Binary;
|
||||||
|
use ws::proto::{OpCode, CloseCode};
|
||||||
|
use ws::mask::apply_mask;
|
||||||
fn apply_mask(buf: &mut [u8], mask: &[u8; 4]) {
|
|
||||||
let iter = buf.iter_mut().zip(mask.iter().cycle());
|
|
||||||
for (byte, &key) in iter {
|
|
||||||
*byte ^= key
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A struct representing a `WebSocket` frame.
|
/// A struct representing a `WebSocket` frame.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct Frame {
|
pub(crate) struct Frame {
|
||||||
finished: bool,
|
finished: bool,
|
||||||
rsv1: bool,
|
rsv1: bool,
|
||||||
@ -22,13 +16,13 @@ pub(crate) struct Frame {
|
|||||||
rsv3: bool,
|
rsv3: bool,
|
||||||
opcode: OpCode,
|
opcode: OpCode,
|
||||||
mask: Option<[u8; 4]>,
|
mask: Option<[u8; 4]>,
|
||||||
payload: Vec<u8>,
|
payload: Binary,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Frame {
|
impl Frame {
|
||||||
|
|
||||||
/// Desctructe frame
|
/// Destruct frame
|
||||||
pub fn unpack(self) -> (bool, OpCode, Vec<u8>) {
|
pub fn unpack(self) -> (bool, OpCode, Binary) {
|
||||||
(self.finished, self.opcode, self.payload)
|
(self.finished, self.opcode, self.payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,11 +49,11 @@ impl Frame {
|
|||||||
|
|
||||||
/// Create a new data frame.
|
/// Create a new data frame.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn message(data: Vec<u8>, code: OpCode, finished: bool) -> Frame {
|
pub fn message<B: Into<Binary>>(data: B, code: OpCode, finished: bool) -> Frame {
|
||||||
Frame {
|
Frame {
|
||||||
finished: finished,
|
finished: finished,
|
||||||
opcode: code,
|
opcode: code,
|
||||||
payload: data,
|
payload: data.into(),
|
||||||
.. Frame::default()
|
.. Frame::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -82,7 +76,7 @@ impl Frame {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Frame {
|
Frame {
|
||||||
payload: payload,
|
payload: payload.into(),
|
||||||
.. Frame::default()
|
.. Frame::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -212,7 +206,7 @@ impl Frame {
|
|||||||
rsv3: rsv3,
|
rsv3: rsv3,
|
||||||
opcode: opcode,
|
opcode: opcode,
|
||||||
mask: mask,
|
mask: mask,
|
||||||
payload: data,
|
payload: data.into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
(frame, header_length + length)
|
(frame, header_length + length)
|
||||||
@ -223,9 +217,7 @@ impl Frame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Write a frame out to a buffer
|
/// Write a frame out to a buffer
|
||||||
pub fn format<W>(&mut self, w: &mut W) -> Result<(), Error>
|
pub fn format<W: Write>(&mut self, w: &mut W) -> Result<(), Error> {
|
||||||
where W: Write
|
|
||||||
{
|
|
||||||
let mut one = 0u8;
|
let mut one = 0u8;
|
||||||
let code: u8 = self.opcode.into();
|
let code: u8 = self.opcode.into();
|
||||||
if self.finished {
|
if self.finished {
|
||||||
@ -251,7 +243,7 @@ impl Frame {
|
|||||||
if self.payload.len() < 126 {
|
if self.payload.len() < 126 {
|
||||||
two |= self.payload.len() as u8;
|
two |= self.payload.len() as u8;
|
||||||
let headers = [one, two];
|
let headers = [one, two];
|
||||||
try!(w.write_all(&headers));
|
w.write_all(&headers)?;
|
||||||
} else if self.payload.len() <= 65_535 {
|
} else if self.payload.len() <= 65_535 {
|
||||||
two |= 126;
|
two |= 126;
|
||||||
let length_bytes: [u8; 2] = unsafe {
|
let length_bytes: [u8; 2] = unsafe {
|
||||||
@ -259,7 +251,7 @@ impl Frame {
|
|||||||
mem::transmute(short.to_be())
|
mem::transmute(short.to_be())
|
||||||
};
|
};
|
||||||
let headers = [one, two, length_bytes[0], length_bytes[1]];
|
let headers = [one, two, length_bytes[0], length_bytes[1]];
|
||||||
try!(w.write_all(&headers));
|
w.write_all(&headers)?;
|
||||||
} else {
|
} else {
|
||||||
two |= 127;
|
two |= 127;
|
||||||
let length_bytes: [u8; 8] = unsafe {
|
let length_bytes: [u8; 8] = unsafe {
|
||||||
@ -278,16 +270,18 @@ impl Frame {
|
|||||||
length_bytes[6],
|
length_bytes[6],
|
||||||
length_bytes[7],
|
length_bytes[7],
|
||||||
];
|
];
|
||||||
try!(w.write_all(&headers));
|
w.write_all(&headers)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.mask.is_some() {
|
if self.mask.is_some() {
|
||||||
let mask = self.mask.take().unwrap();
|
let mask = self.mask.take().unwrap();
|
||||||
apply_mask(&mut self.payload, &mask);
|
let mut payload = Vec::from(self.payload.as_ref());
|
||||||
try!(w.write_all(&mask));
|
apply_mask(&mut payload, &mask);
|
||||||
|
w.write_all(&mask)?;
|
||||||
|
w.write_all(payload.as_ref())?;
|
||||||
|
} else {
|
||||||
|
w.write_all(self.payload.as_ref())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
try!(w.write_all(&self.payload));
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -301,7 +295,7 @@ impl Default for Frame {
|
|||||||
rsv3: false,
|
rsv3: false,
|
||||||
opcode: OpCode::Close,
|
opcode: OpCode::Close,
|
||||||
mask: None,
|
mask: None,
|
||||||
payload: Vec::new(),
|
payload: Binary::from(&b""[..]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -326,7 +320,8 @@ impl fmt::Display for Frame {
|
|||||||
// self.mask.map(|mask| format!("{:?}", mask)).unwrap_or("NONE".into()),
|
// self.mask.map(|mask| format!("{:?}", mask)).unwrap_or("NONE".into()),
|
||||||
self.len(),
|
self.len(),
|
||||||
self.payload.len(),
|
self.payload.len(),
|
||||||
self.payload.iter().map(|byte| format!("{:x}", byte)).collect::<String>())
|
self.payload.as_ref().iter().map(
|
||||||
|
|byte| format!("{:x}", byte)).collect::<String>())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -343,7 +338,7 @@ mod tests {
|
|||||||
println!("FRAME: {}", frame);
|
println!("FRAME: {}", frame);
|
||||||
assert!(!frame.finished);
|
assert!(!frame.finished);
|
||||||
assert_eq!(frame.opcode, OpCode::Text);
|
assert_eq!(frame.opcode, OpCode::Text);
|
||||||
assert_eq!(frame.payload, &b"1"[..]);
|
assert_eq!(frame.payload.as_ref(), &b"1"[..]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -365,7 +360,7 @@ mod tests {
|
|||||||
let frame = Frame::parse(&mut buf).unwrap().unwrap();
|
let frame = Frame::parse(&mut buf).unwrap().unwrap();
|
||||||
assert!(!frame.finished);
|
assert!(!frame.finished);
|
||||||
assert_eq!(frame.opcode, OpCode::Text);
|
assert_eq!(frame.opcode, OpCode::Text);
|
||||||
assert_eq!(frame.payload, &b"1234"[..]);
|
assert_eq!(frame.payload.as_ref(), &b"1234"[..]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -378,7 +373,7 @@ mod tests {
|
|||||||
let frame = Frame::parse(&mut buf).unwrap().unwrap();
|
let frame = Frame::parse(&mut buf).unwrap().unwrap();
|
||||||
assert!(!frame.finished);
|
assert!(!frame.finished);
|
||||||
assert_eq!(frame.opcode, OpCode::Text);
|
assert_eq!(frame.opcode, OpCode::Text);
|
||||||
assert_eq!(frame.payload, &b"1234"[..]);
|
assert_eq!(frame.payload.as_ref(), &b"1234"[..]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -390,7 +385,7 @@ mod tests {
|
|||||||
let frame = Frame::parse(&mut buf).unwrap().unwrap();
|
let frame = Frame::parse(&mut buf).unwrap().unwrap();
|
||||||
assert!(!frame.finished);
|
assert!(!frame.finished);
|
||||||
assert_eq!(frame.opcode, OpCode::Text);
|
assert_eq!(frame.opcode, OpCode::Text);
|
||||||
assert_eq!(frame.payload, vec![1u8]);
|
assert_eq!(frame.payload, vec![1u8].into());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
120
src/ws/mask.rs
Normal file
120
src/ws/mask.rs
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
//! This is code from [Tungstenite project](https://github.com/snapview/tungstenite-rs)
|
||||||
|
use std::cmp::min;
|
||||||
|
use std::mem::uninitialized;
|
||||||
|
use std::ptr::copy_nonoverlapping;
|
||||||
|
|
||||||
|
/// Mask/unmask a frame.
|
||||||
|
#[inline]
|
||||||
|
pub fn apply_mask(buf: &mut [u8], mask: &[u8; 4]) {
|
||||||
|
apply_mask_fast32(buf, mask)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A safe unoptimized mask application.
|
||||||
|
#[inline]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn apply_mask_fallback(buf: &mut [u8], mask: &[u8; 4]) {
|
||||||
|
for (i, byte) in buf.iter_mut().enumerate() {
|
||||||
|
*byte ^= mask[i & 3];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Faster version of `apply_mask()` which operates on 4-byte blocks.
|
||||||
|
#[inline]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn apply_mask_fast32(buf: &mut [u8], mask: &[u8; 4]) {
|
||||||
|
// TODO replace this with read_unaligned() as it stabilizes.
|
||||||
|
let mask_u32 = unsafe {
|
||||||
|
let mut m: u32 = uninitialized();
|
||||||
|
#[allow(trivial_casts)]
|
||||||
|
copy_nonoverlapping(mask.as_ptr(), &mut m as *mut _ as *mut u8, 4);
|
||||||
|
m
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut ptr = buf.as_mut_ptr();
|
||||||
|
let mut len = buf.len();
|
||||||
|
|
||||||
|
// Possible first unaligned block.
|
||||||
|
let head = min(len, (4 - (ptr as usize & 3)) & 3);
|
||||||
|
let mask_u32 = if head > 0 {
|
||||||
|
unsafe {
|
||||||
|
xor_mem(ptr, mask_u32, head);
|
||||||
|
ptr = ptr.offset(head as isize);
|
||||||
|
}
|
||||||
|
len -= head;
|
||||||
|
if cfg!(target_endian = "big") {
|
||||||
|
mask_u32.rotate_left(8 * head as u32)
|
||||||
|
} else {
|
||||||
|
mask_u32.rotate_right(8 * head as u32)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mask_u32
|
||||||
|
};
|
||||||
|
|
||||||
|
if len > 0 {
|
||||||
|
debug_assert_eq!(ptr as usize % 4, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Properly aligned middle of the data.
|
||||||
|
while len > 4 {
|
||||||
|
unsafe {
|
||||||
|
*(ptr as *mut u32) ^= mask_u32;
|
||||||
|
ptr = ptr.offset(4);
|
||||||
|
len -= 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Possible last block.
|
||||||
|
if len > 0 {
|
||||||
|
unsafe { xor_mem(ptr, mask_u32, len); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
// TODO: copy_nonoverlapping here compiles to call memcpy. While it is not so inefficient,
|
||||||
|
// it could be done better. The compiler does not see that len is limited to 3.
|
||||||
|
unsafe fn xor_mem(ptr: *mut u8, mask: u32, len: usize) {
|
||||||
|
let mut b: u32 = uninitialized();
|
||||||
|
#[allow(trivial_casts)]
|
||||||
|
copy_nonoverlapping(ptr, &mut b as *mut _ as *mut u8, len);
|
||||||
|
b ^= mask;
|
||||||
|
#[allow(trivial_casts)]
|
||||||
|
copy_nonoverlapping(&b as *const _ as *const u8, ptr, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{apply_mask_fallback, apply_mask_fast32};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_apply_mask() {
|
||||||
|
let mask = [
|
||||||
|
0x6d, 0xb6, 0xb2, 0x80,
|
||||||
|
];
|
||||||
|
let unmasked = vec![
|
||||||
|
0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82,
|
||||||
|
0xff, 0xfe, 0x00, 0x17, 0x74, 0xf9, 0x12, 0x03,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Check masking with proper alignment.
|
||||||
|
{
|
||||||
|
let mut masked = unmasked.clone();
|
||||||
|
apply_mask_fallback(&mut masked, &mask);
|
||||||
|
|
||||||
|
let mut masked_fast = unmasked.clone();
|
||||||
|
apply_mask_fast32(&mut masked_fast, &mask);
|
||||||
|
|
||||||
|
assert_eq!(masked, masked_fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check masking without alignment.
|
||||||
|
{
|
||||||
|
let mut masked = unmasked.clone();
|
||||||
|
apply_mask_fallback(&mut masked[1..], &mask);
|
||||||
|
|
||||||
|
let mut masked_fast = unmasked.clone();
|
||||||
|
apply_mask_fast32(&mut masked_fast[1..], &mask);
|
||||||
|
|
||||||
|
assert_eq!(masked, masked_fast);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,8 +8,9 @@
|
|||||||
//! ```rust
|
//! ```rust
|
||||||
//! # extern crate actix;
|
//! # extern crate actix;
|
||||||
//! # extern crate actix_web;
|
//! # extern crate actix_web;
|
||||||
//! use actix::*;
|
//! # use actix::*;
|
||||||
//! use actix_web::*;
|
//! # use actix_web::*;
|
||||||
|
//! use actix_web::ws;
|
||||||
//!
|
//!
|
||||||
//! // do websocket handshake and start actor
|
//! // do websocket handshake and start actor
|
||||||
//! fn ws_index(req: HttpRequest) -> Result<HttpResponse> {
|
//! fn ws_index(req: HttpRequest) -> Result<HttpResponse> {
|
||||||
@ -19,18 +20,18 @@
|
|||||||
//! struct Ws;
|
//! struct Ws;
|
||||||
//!
|
//!
|
||||||
//! impl Actor for Ws {
|
//! impl Actor for Ws {
|
||||||
//! type Context = HttpContext<Self>;
|
//! type Context = ws::WebsocketContext<Self>;
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! // Define Handler for ws::Message message
|
//! // Define Handler for ws::Message message
|
||||||
//! impl Handler<ws::Message> for Ws {
|
//! impl Handler<ws::Message> for Ws {
|
||||||
//! type Result = ();
|
//! type Result = ();
|
||||||
//!
|
//!
|
||||||
//! fn handle(&mut self, msg: ws::Message, ctx: &mut HttpContext<Self>) {
|
//! fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
|
||||||
//! match msg {
|
//! match msg {
|
||||||
//! ws::Message::Ping(msg) => ws::WsWriter::pong(ctx, &msg),
|
//! ws::Message::Ping(msg) => ctx.pong(&msg),
|
||||||
//! ws::Message::Text(text) => ws::WsWriter::text(ctx, &text),
|
//! ws::Message::Text(text) => ctx.text(&text),
|
||||||
//! ws::Message::Binary(bin) => ws::WsWriter::binary(ctx, bin),
|
//! ws::Message::Binary(bin) => ctx.binary(bin),
|
||||||
//! _ => (),
|
//! _ => (),
|
||||||
//! }
|
//! }
|
||||||
//! }
|
//! }
|
||||||
@ -42,22 +43,27 @@
|
|||||||
//! # .finish();
|
//! # .finish();
|
||||||
//! # }
|
//! # }
|
||||||
//! ```
|
//! ```
|
||||||
use std::vec::Vec;
|
|
||||||
use http::{Method, StatusCode, header};
|
|
||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
|
use http::{Method, StatusCode, header};
|
||||||
use futures::{Async, Poll, Stream};
|
use futures::{Async, Poll, Stream};
|
||||||
|
|
||||||
use actix::{Actor, AsyncContext, ResponseType, Handler};
|
use actix::{Actor, AsyncContext, ResponseType, Handler};
|
||||||
|
|
||||||
|
use body::Binary;
|
||||||
use payload::ReadAny;
|
use payload::ReadAny;
|
||||||
use error::{Error, WsHandshakeError};
|
use error::{Error, WsHandshakeError};
|
||||||
use context::HttpContext;
|
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
use httpresponse::{ConnectionType, HttpResponse, HttpResponseBuilder};
|
use httpresponse::{ConnectionType, HttpResponse, HttpResponseBuilder};
|
||||||
|
|
||||||
use wsframe;
|
mod frame;
|
||||||
use wsproto::*;
|
mod proto;
|
||||||
pub use wsproto::CloseCode;
|
mod context;
|
||||||
|
mod mask;
|
||||||
|
|
||||||
|
use ws::frame::Frame;
|
||||||
|
use ws::proto::{hash_key, OpCode};
|
||||||
|
pub use ws::proto::CloseCode;
|
||||||
|
pub use ws::context::WebsocketContext;
|
||||||
|
|
||||||
const SEC_WEBSOCKET_ACCEPT: &str = "SEC-WEBSOCKET-ACCEPT";
|
const SEC_WEBSOCKET_ACCEPT: &str = "SEC-WEBSOCKET-ACCEPT";
|
||||||
const SEC_WEBSOCKET_KEY: &str = "SEC-WEBSOCKET-KEY";
|
const SEC_WEBSOCKET_KEY: &str = "SEC-WEBSOCKET-KEY";
|
||||||
@ -69,7 +75,7 @@ const SEC_WEBSOCKET_VERSION: &str = "SEC-WEBSOCKET-VERSION";
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
Text(String),
|
Text(String),
|
||||||
Binary(Vec<u8>),
|
Binary(Binary),
|
||||||
Ping(String),
|
Ping(String),
|
||||||
Pong(String),
|
Pong(String),
|
||||||
Close,
|
Close,
|
||||||
@ -84,13 +90,13 @@ impl ResponseType for Message {
|
|||||||
|
|
||||||
/// Do websocket handshake and start actor
|
/// Do websocket handshake and start actor
|
||||||
pub fn start<A, S>(mut req: HttpRequest<S>, actor: A) -> Result<HttpResponse, Error>
|
pub fn start<A, S>(mut req: HttpRequest<S>, actor: A) -> Result<HttpResponse, Error>
|
||||||
where A: Actor<Context=HttpContext<A, S>> + Handler<Message>,
|
where A: Actor<Context=WebsocketContext<A, S>> + Handler<Message>,
|
||||||
S: 'static
|
S: 'static
|
||||||
{
|
{
|
||||||
let mut resp = handshake(&req)?;
|
let mut resp = handshake(&req)?;
|
||||||
let stream = WsStream::new(req.payload_mut().readany());
|
let stream = WsStream::new(req.payload_mut().readany());
|
||||||
|
|
||||||
let mut ctx = HttpContext::new(req, actor);
|
let mut ctx = WebsocketContext::new(req, actor);
|
||||||
ctx.add_message_stream(stream);
|
ctx.add_message_stream(stream);
|
||||||
|
|
||||||
Ok(resp.body(ctx)?)
|
Ok(resp.body(ctx)?)
|
||||||
@ -206,7 +212,7 @@ impl Stream for WsStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match wsframe::Frame::parse(&mut self.buf) {
|
match Frame::parse(&mut self.buf) {
|
||||||
Ok(Some(frame)) => {
|
Ok(Some(frame)) => {
|
||||||
// trace!("WsFrame {}", frame);
|
// trace!("WsFrame {}", frame);
|
||||||
let (_finished, opcode, payload) = frame.unpack();
|
let (_finished, opcode, payload) = frame.unpack();
|
||||||
@ -222,14 +228,17 @@ impl Stream for WsStream {
|
|||||||
},
|
},
|
||||||
OpCode::Ping =>
|
OpCode::Ping =>
|
||||||
return Ok(Async::Ready(Some(
|
return Ok(Async::Ready(Some(
|
||||||
Message::Ping(String::from_utf8_lossy(&payload).into())))),
|
Message::Ping(
|
||||||
|
String::from_utf8_lossy(payload.as_ref()).into())))),
|
||||||
OpCode::Pong =>
|
OpCode::Pong =>
|
||||||
return Ok(Async::Ready(Some(
|
return Ok(Async::Ready(Some(
|
||||||
Message::Pong(String::from_utf8_lossy(&payload).into())))),
|
Message::Pong(
|
||||||
|
String::from_utf8_lossy(payload.as_ref()).into())))),
|
||||||
OpCode::Binary =>
|
OpCode::Binary =>
|
||||||
return Ok(Async::Ready(Some(Message::Binary(payload)))),
|
return Ok(Async::Ready(Some(Message::Binary(payload)))),
|
||||||
OpCode::Text => {
|
OpCode::Text => {
|
||||||
match String::from_utf8(payload) {
|
let tmp = Vec::from(payload.as_ref());
|
||||||
|
match String::from_utf8(tmp) {
|
||||||
Ok(s) =>
|
Ok(s) =>
|
||||||
return Ok(Async::Ready(Some(Message::Text(s)))),
|
return Ok(Async::Ready(Some(Message::Text(s)))),
|
||||||
Err(_) =>
|
Err(_) =>
|
||||||
@ -262,67 +271,6 @@ impl Stream for WsStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// `WebSocket` writer
|
|
||||||
pub struct WsWriter;
|
|
||||||
|
|
||||||
impl WsWriter {
|
|
||||||
|
|
||||||
/// Send text frame
|
|
||||||
pub fn text<A, S>(ctx: &mut HttpContext<A, S>, text: &str)
|
|
||||||
where A: Actor<Context=HttpContext<A, S>>
|
|
||||||
{
|
|
||||||
let mut frame = wsframe::Frame::message(Vec::from(text), OpCode::Text, true);
|
|
||||||
let mut buf = Vec::new();
|
|
||||||
frame.format(&mut buf).unwrap();
|
|
||||||
|
|
||||||
ctx.write(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send binary frame
|
|
||||||
pub fn binary<A, S>(ctx: &mut HttpContext<A, S>, data: Vec<u8>)
|
|
||||||
where A: Actor<Context=HttpContext<A, S>>
|
|
||||||
{
|
|
||||||
let mut frame = wsframe::Frame::message(data, OpCode::Binary, true);
|
|
||||||
let mut buf = Vec::new();
|
|
||||||
frame.format(&mut buf).unwrap();
|
|
||||||
|
|
||||||
ctx.write(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send ping frame
|
|
||||||
pub fn ping<A, S>(ctx: &mut HttpContext<A, S>, message: &str)
|
|
||||||
where A: Actor<Context=HttpContext<A, S>>
|
|
||||||
{
|
|
||||||
let mut frame = wsframe::Frame::message(Vec::from(message), OpCode::Ping, true);
|
|
||||||
let mut buf = Vec::new();
|
|
||||||
frame.format(&mut buf).unwrap();
|
|
||||||
|
|
||||||
ctx.write(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send pong frame
|
|
||||||
pub fn pong<A, S>(ctx: &mut HttpContext<A, S>, message: &str)
|
|
||||||
where A: Actor<Context=HttpContext<A, S>>
|
|
||||||
{
|
|
||||||
let mut frame = wsframe::Frame::message(Vec::from(message), OpCode::Pong, true);
|
|
||||||
let mut buf = Vec::new();
|
|
||||||
frame.format(&mut buf).unwrap();
|
|
||||||
|
|
||||||
ctx.write(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send close frame
|
|
||||||
pub fn close<A, S>(ctx: &mut HttpContext<A, S>, code: CloseCode, reason: &str)
|
|
||||||
where A: Actor<Context=HttpContext<A, S>>
|
|
||||||
{
|
|
||||||
let mut frame = wsframe::Frame::close(code, reason);
|
|
||||||
let mut buf = Vec::new();
|
|
||||||
frame.format(&mut buf).unwrap();
|
|
||||||
ctx.write(buf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
@ -3,15 +3,54 @@ extern crate actix_web;
|
|||||||
extern crate tokio_core;
|
extern crate tokio_core;
|
||||||
extern crate reqwest;
|
extern crate reqwest;
|
||||||
extern crate futures;
|
extern crate futures;
|
||||||
|
extern crate h2;
|
||||||
|
extern crate http;
|
||||||
|
extern crate bytes;
|
||||||
|
extern crate flate2;
|
||||||
|
extern crate brotli2;
|
||||||
|
|
||||||
use std::{net, thread, time};
|
use std::{net, thread, time};
|
||||||
|
use std::io::Write;
|
||||||
use std::sync::{Arc, mpsc};
|
use std::sync::{Arc, mpsc};
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use futures::Future;
|
use flate2::Compression;
|
||||||
|
use flate2::write::{GzEncoder, DeflateEncoder, DeflateDecoder};
|
||||||
|
use brotli2::write::{BrotliEncoder, BrotliDecoder};
|
||||||
|
use futures::{Future, Stream};
|
||||||
|
use futures::stream::once;
|
||||||
|
use h2::client;
|
||||||
|
use bytes::{Bytes, BytesMut, BufMut};
|
||||||
|
use http::Request;
|
||||||
|
use tokio_core::net::TcpStream;
|
||||||
|
use tokio_core::reactor::Core;
|
||||||
|
use reqwest::header::{Encoding, ContentEncoding};
|
||||||
|
|
||||||
use actix_web::*;
|
use actix_web::*;
|
||||||
use actix::System;
|
use actix::System;
|
||||||
|
|
||||||
|
const STR: &str =
|
||||||
|
"Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World \
|
||||||
|
Hello World Hello World Hello World Hello World Hello World";
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_start() {
|
fn test_start() {
|
||||||
let _ = test::TestServer::unused_addr();
|
let _ = test::TestServer::unused_addr();
|
||||||
@ -33,12 +72,12 @@ fn test_start() {
|
|||||||
assert!(reqwest::get(&format!("http://{}/", addr)).unwrap().status().is_success());
|
assert!(reqwest::get(&format!("http://{}/", addr)).unwrap().status().is_success());
|
||||||
|
|
||||||
// pause
|
// pause
|
||||||
let _ = srv_addr.call_fut(dev::PauseServer).wait();
|
let _ = srv_addr.call_fut(server::PauseServer).wait();
|
||||||
thread::sleep(time::Duration::from_millis(100));
|
thread::sleep(time::Duration::from_millis(100));
|
||||||
assert!(net::TcpStream::connect(addr).is_err());
|
assert!(net::TcpStream::connect(addr).is_err());
|
||||||
|
|
||||||
// resume
|
// resume
|
||||||
let _ = srv_addr.call_fut(dev::ResumeServer).wait();
|
let _ = srv_addr.call_fut(server::ResumeServer).wait();
|
||||||
assert!(reqwest::get(&format!("http://{}/", addr)).unwrap().status().is_success());
|
assert!(reqwest::get(&format!("http://{}/", addr)).unwrap().status().is_success());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,6 +87,324 @@ fn test_simple() {
|
|||||||
assert!(reqwest::get(&srv.url("/")).unwrap().status().is_success());
|
assert!(reqwest::get(&srv.url("/")).unwrap().status().is_success());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_body() {
|
||||||
|
let srv = test::TestServer::new(
|
||||||
|
|app| app.handler(|_| httpcodes::HTTPOk.build().body(STR)));
|
||||||
|
let mut res = reqwest::get(&srv.url("/")).unwrap();
|
||||||
|
assert!(res.status().is_success());
|
||||||
|
let mut bytes = BytesMut::with_capacity(2048).writer();
|
||||||
|
let _ = res.copy_to(&mut bytes);
|
||||||
|
let bytes = bytes.into_inner();
|
||||||
|
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_body_gzip() {
|
||||||
|
let srv = test::TestServer::new(
|
||||||
|
|app| app.handler(
|
||||||
|
|_| httpcodes::HTTPOk.build()
|
||||||
|
.content_encoding(headers::ContentEncoding::Gzip)
|
||||||
|
.body(STR)));
|
||||||
|
let mut res = reqwest::get(&srv.url("/")).unwrap();
|
||||||
|
assert!(res.status().is_success());
|
||||||
|
let mut bytes = BytesMut::with_capacity(2048).writer();
|
||||||
|
let _ = res.copy_to(&mut bytes);
|
||||||
|
let bytes = bytes.into_inner();
|
||||||
|
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_body_streaming_implicit() {
|
||||||
|
let srv = test::TestServer::new(
|
||||||
|
|app| app.handler(|_| {
|
||||||
|
let body = once(Ok(Bytes::from_static(STR.as_ref())));
|
||||||
|
httpcodes::HTTPOk.build()
|
||||||
|
.content_encoding(headers::ContentEncoding::Gzip)
|
||||||
|
.body(Body::Streaming(Box::new(body)))}));
|
||||||
|
|
||||||
|
let mut res = reqwest::get(&srv.url("/")).unwrap();
|
||||||
|
assert!(res.status().is_success());
|
||||||
|
let mut bytes = BytesMut::with_capacity(2048).writer();
|
||||||
|
let _ = res.copy_to(&mut bytes);
|
||||||
|
let bytes = bytes.into_inner();
|
||||||
|
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_body_br_streaming() {
|
||||||
|
let srv = test::TestServer::new(
|
||||||
|
|app| app.handler(|_| {
|
||||||
|
let body = once(Ok(Bytes::from_static(STR.as_ref())));
|
||||||
|
httpcodes::HTTPOk.build()
|
||||||
|
.content_encoding(headers::ContentEncoding::Br)
|
||||||
|
.body(Body::Streaming(Box::new(body)))}));
|
||||||
|
|
||||||
|
let mut res = reqwest::get(&srv.url("/")).unwrap();
|
||||||
|
assert!(res.status().is_success());
|
||||||
|
let mut bytes = BytesMut::with_capacity(2048).writer();
|
||||||
|
let _ = res.copy_to(&mut bytes);
|
||||||
|
let bytes = bytes.into_inner();
|
||||||
|
|
||||||
|
let mut e = BrotliDecoder::new(Vec::with_capacity(2048));
|
||||||
|
e.write_all(bytes.as_ref()).unwrap();
|
||||||
|
let dec = e.finish().unwrap();
|
||||||
|
assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_head_empty() {
|
||||||
|
let srv = test::TestServer::new(
|
||||||
|
|app| app.handler(|_| {
|
||||||
|
httpcodes::HTTPOk.build()
|
||||||
|
.content_length(STR.len() as u64).finish()}));
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let mut res = client.head(&srv.url("/")).send().unwrap();
|
||||||
|
assert!(res.status().is_success());
|
||||||
|
let mut bytes = BytesMut::with_capacity(2048).writer();
|
||||||
|
let len = res.headers()
|
||||||
|
.get::<reqwest::header::ContentLength>().map(|ct_len| **ct_len).unwrap();
|
||||||
|
assert_eq!(len, STR.len() as u64);
|
||||||
|
let _ = res.copy_to(&mut bytes);
|
||||||
|
let bytes = bytes.into_inner();
|
||||||
|
assert!(bytes.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_head_binary() {
|
||||||
|
let srv = test::TestServer::new(
|
||||||
|
|app| app.handler(|_| {
|
||||||
|
httpcodes::HTTPOk.build()
|
||||||
|
.content_encoding(headers::ContentEncoding::Identity)
|
||||||
|
.content_length(100).body(STR)}));
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let mut res = client.head(&srv.url("/")).send().unwrap();
|
||||||
|
assert!(res.status().is_success());
|
||||||
|
let mut bytes = BytesMut::with_capacity(2048).writer();
|
||||||
|
let len = res.headers()
|
||||||
|
.get::<reqwest::header::ContentLength>().map(|ct_len| **ct_len).unwrap();
|
||||||
|
assert_eq!(len, STR.len() as u64);
|
||||||
|
let _ = res.copy_to(&mut bytes);
|
||||||
|
let bytes = bytes.into_inner();
|
||||||
|
assert!(bytes.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_head_binary2() {
|
||||||
|
let srv = test::TestServer::new(
|
||||||
|
|app| app.handler(|_| {
|
||||||
|
httpcodes::HTTPOk.build()
|
||||||
|
.content_encoding(headers::ContentEncoding::Identity)
|
||||||
|
.body(STR)
|
||||||
|
}));
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let mut res = client.head(&srv.url("/")).send().unwrap();
|
||||||
|
assert!(res.status().is_success());
|
||||||
|
let mut bytes = BytesMut::with_capacity(2048).writer();
|
||||||
|
let len = res.headers()
|
||||||
|
.get::<reqwest::header::ContentLength>().map(|ct_len| **ct_len).unwrap();
|
||||||
|
assert_eq!(len, STR.len() as u64);
|
||||||
|
let _ = res.copy_to(&mut bytes);
|
||||||
|
let bytes = bytes.into_inner();
|
||||||
|
assert!(bytes.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_body_length() {
|
||||||
|
let srv = test::TestServer::new(
|
||||||
|
|app| app.handler(|_| {
|
||||||
|
let body = once(Ok(Bytes::from_static(STR.as_ref())));
|
||||||
|
httpcodes::HTTPOk.build()
|
||||||
|
.content_length(STR.len() as u64)
|
||||||
|
.body(Body::Streaming(Box::new(body)))}));
|
||||||
|
|
||||||
|
let mut res = reqwest::get(&srv.url("/")).unwrap();
|
||||||
|
assert!(res.status().is_success());
|
||||||
|
let mut bytes = BytesMut::with_capacity(2048).writer();
|
||||||
|
let _ = res.copy_to(&mut bytes);
|
||||||
|
let bytes = bytes.into_inner();
|
||||||
|
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_body_streaming_explicit() {
|
||||||
|
let srv = test::TestServer::new(
|
||||||
|
|app| app.handler(|_| {
|
||||||
|
let body = once(Ok(Bytes::from_static(STR.as_ref())));
|
||||||
|
httpcodes::HTTPOk.build()
|
||||||
|
.chunked()
|
||||||
|
.content_encoding(headers::ContentEncoding::Gzip)
|
||||||
|
.body(Body::Streaming(Box::new(body)))}));
|
||||||
|
|
||||||
|
let mut res = reqwest::get(&srv.url("/")).unwrap();
|
||||||
|
assert!(res.status().is_success());
|
||||||
|
let mut bytes = BytesMut::with_capacity(2048).writer();
|
||||||
|
let _ = res.copy_to(&mut bytes);
|
||||||
|
let bytes = bytes.into_inner();
|
||||||
|
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_body_deflate() {
|
||||||
|
let srv = test::TestServer::new(
|
||||||
|
|app| app.handler(
|
||||||
|
|_| httpcodes::HTTPOk
|
||||||
|
.build()
|
||||||
|
.content_encoding(headers::ContentEncoding::Deflate)
|
||||||
|
.body(STR)));
|
||||||
|
let mut res = reqwest::get(&srv.url("/")).unwrap();
|
||||||
|
assert!(res.status().is_success());
|
||||||
|
let mut bytes = BytesMut::with_capacity(2048).writer();
|
||||||
|
let _ = res.copy_to(&mut bytes);
|
||||||
|
let bytes = bytes.into_inner();
|
||||||
|
|
||||||
|
let mut e = DeflateDecoder::new(Vec::new());
|
||||||
|
e.write_all(bytes.as_ref()).unwrap();
|
||||||
|
let dec = e.finish().unwrap();
|
||||||
|
assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_body_brotli() {
|
||||||
|
let srv = test::TestServer::new(
|
||||||
|
|app| app.handler(
|
||||||
|
|_| httpcodes::HTTPOk
|
||||||
|
.build()
|
||||||
|
.content_encoding(headers::ContentEncoding::Br)
|
||||||
|
.body(STR)));
|
||||||
|
let mut res = reqwest::get(&srv.url("/")).unwrap();
|
||||||
|
assert!(res.status().is_success());
|
||||||
|
let mut bytes = BytesMut::with_capacity(2048).writer();
|
||||||
|
let _ = res.copy_to(&mut bytes);
|
||||||
|
let bytes = bytes.into_inner();
|
||||||
|
|
||||||
|
let mut e = BrotliDecoder::new(Vec::with_capacity(2048));
|
||||||
|
e.write_all(bytes.as_ref()).unwrap();
|
||||||
|
let dec = e.finish().unwrap();
|
||||||
|
assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gzip_encoding() {
|
||||||
|
let srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| {
|
||||||
|
req.body()
|
||||||
|
.and_then(|bytes: Bytes| {
|
||||||
|
Ok(httpcodes::HTTPOk
|
||||||
|
.build()
|
||||||
|
.content_encoding(headers::ContentEncoding::Identity)
|
||||||
|
.body(bytes))
|
||||||
|
}).responder()}
|
||||||
|
));
|
||||||
|
|
||||||
|
let mut e = GzEncoder::new(Vec::new(), Compression::default());
|
||||||
|
e.write_all(STR.as_ref()).unwrap();
|
||||||
|
let enc = e.finish().unwrap();
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let mut res = client.post(&srv.url("/"))
|
||||||
|
.header(ContentEncoding(vec![Encoding::Gzip]))
|
||||||
|
.body(enc.clone()).send().unwrap();
|
||||||
|
let mut bytes = BytesMut::with_capacity(2048).writer();
|
||||||
|
let _ = res.copy_to(&mut bytes);
|
||||||
|
let bytes = bytes.into_inner();
|
||||||
|
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deflate_encoding() {
|
||||||
|
let srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| {
|
||||||
|
req.body()
|
||||||
|
.and_then(|bytes: Bytes| {
|
||||||
|
Ok(httpcodes::HTTPOk
|
||||||
|
.build()
|
||||||
|
.content_encoding(headers::ContentEncoding::Identity)
|
||||||
|
.body(bytes))
|
||||||
|
}).responder()}
|
||||||
|
));
|
||||||
|
|
||||||
|
let mut e = DeflateEncoder::new(Vec::new(), Compression::default());
|
||||||
|
e.write_all(STR.as_ref()).unwrap();
|
||||||
|
let enc = e.finish().unwrap();
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let mut res = client.post(&srv.url("/"))
|
||||||
|
.header(ContentEncoding(vec![Encoding::Deflate]))
|
||||||
|
.body(enc.clone()).send().unwrap();
|
||||||
|
let mut bytes = BytesMut::with_capacity(2048).writer();
|
||||||
|
let _ = res.copy_to(&mut bytes);
|
||||||
|
let bytes = bytes.into_inner();
|
||||||
|
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_brotli_encoding() {
|
||||||
|
let srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| {
|
||||||
|
req.body()
|
||||||
|
.and_then(|bytes: Bytes| {
|
||||||
|
Ok(httpcodes::HTTPOk
|
||||||
|
.build()
|
||||||
|
.content_encoding(headers::ContentEncoding::Identity)
|
||||||
|
.body(bytes))
|
||||||
|
}).responder()}
|
||||||
|
));
|
||||||
|
|
||||||
|
let mut e = BrotliEncoder::new(Vec::new(), 5);
|
||||||
|
e.write_all(STR.as_ref()).unwrap();
|
||||||
|
let enc = e.finish().unwrap();
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let mut res = client.post(&srv.url("/"))
|
||||||
|
.header(ContentEncoding(vec![Encoding::Brotli]))
|
||||||
|
.body(enc.clone()).send().unwrap();
|
||||||
|
let mut bytes = BytesMut::with_capacity(2048).writer();
|
||||||
|
let _ = res.copy_to(&mut bytes);
|
||||||
|
let bytes = bytes.into_inner();
|
||||||
|
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_h2() {
|
||||||
|
let srv = test::TestServer::new(|app| app.handler(|_|{
|
||||||
|
httpcodes::HTTPOk.build().body(STR)
|
||||||
|
}));
|
||||||
|
let addr = srv.addr();
|
||||||
|
|
||||||
|
let mut core = Core::new().unwrap();
|
||||||
|
let handle = core.handle();
|
||||||
|
let tcp = TcpStream::connect(&addr, &handle);
|
||||||
|
|
||||||
|
let tcp = tcp.then(|res| {
|
||||||
|
client::handshake(res.unwrap())
|
||||||
|
}).then(move |res| {
|
||||||
|
let (mut client, h2) = res.unwrap();
|
||||||
|
|
||||||
|
let request = Request::builder()
|
||||||
|
.uri(format!("https://{}/", addr).as_str())
|
||||||
|
.body(())
|
||||||
|
.unwrap();
|
||||||
|
let (response, _) = client.send_request(request, false).unwrap();
|
||||||
|
|
||||||
|
// Spawn a task to run the conn...
|
||||||
|
handle.spawn(h2.map_err(|e| println!("GOT ERR={:?}", e)));
|
||||||
|
|
||||||
|
response.and_then(|response| {
|
||||||
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
|
|
||||||
|
let (_, body) = response.into_parts();
|
||||||
|
|
||||||
|
body.fold(BytesMut::new(), |mut b, c| -> Result<_, h2::Error> {
|
||||||
|
b.extend(c);
|
||||||
|
Ok(b)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
let _res = core.run(tcp);
|
||||||
|
// assert_eq!(_res.unwrap(), Bytes::from_static(STR.as_ref()));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_application() {
|
fn test_application() {
|
||||||
let srv = test::TestServer::with_factory(
|
let srv = test::TestServer::with_factory(
|
||||||
@ -62,14 +419,14 @@ struct MiddlewareTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<S> middleware::Middleware<S> for MiddlewareTest {
|
impl<S> middleware::Middleware<S> for MiddlewareTest {
|
||||||
fn start(&self, _: &mut HttpRequest<S>) -> middleware::Started {
|
fn start(&self, _: &mut HttpRequest<S>) -> Result<middleware::Started> {
|
||||||
self.start.store(self.start.load(Ordering::Relaxed) + 1, Ordering::Relaxed);
|
self.start.store(self.start.load(Ordering::Relaxed) + 1, Ordering::Relaxed);
|
||||||
middleware::Started::Done
|
Ok(middleware::Started::Done)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn response(&self, _: &mut HttpRequest<S>, resp: HttpResponse) -> middleware::Response {
|
fn response(&self, _: &mut HttpRequest<S>, resp: HttpResponse) -> Result<middleware::Response> {
|
||||||
self.response.store(self.response.load(Ordering::Relaxed) + 1, Ordering::Relaxed);
|
self.response.store(self.response.load(Ordering::Relaxed) + 1, Ordering::Relaxed);
|
||||||
middleware::Response::Done(resp)
|
Ok(middleware::Response::Done(resp))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(&self, _: &mut HttpRequest<S>, _: &HttpResponse) -> middleware::Finished {
|
fn finish(&self, _: &mut HttpRequest<S>, _: &HttpResponse) -> middleware::Finished {
|
||||||
@ -100,3 +457,28 @@ fn test_middlewares() {
|
|||||||
assert_eq!(num2.load(Ordering::Relaxed), 1);
|
assert_eq!(num2.load(Ordering::Relaxed), 1);
|
||||||
assert_eq!(num3.load(Ordering::Relaxed), 1);
|
assert_eq!(num3.load(Ordering::Relaxed), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_resource_middlewares() {
|
||||||
|
let num1 = Arc::new(AtomicUsize::new(0));
|
||||||
|
let num2 = Arc::new(AtomicUsize::new(0));
|
||||||
|
let num3 = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
|
let act_num1 = Arc::clone(&num1);
|
||||||
|
let act_num2 = Arc::clone(&num2);
|
||||||
|
let act_num3 = Arc::clone(&num3);
|
||||||
|
|
||||||
|
let srv = test::TestServer::new(
|
||||||
|
move |app| app.handler2(
|
||||||
|
httpcodes::HTTPOk,
|
||||||
|
MiddlewareTest{start: Arc::clone(&act_num1),
|
||||||
|
response: Arc::clone(&act_num2),
|
||||||
|
finish: Arc::clone(&act_num3)})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(reqwest::get(&srv.url("/")).unwrap().status().is_success());
|
||||||
|
assert_eq!(num1.load(Ordering::Relaxed), 1);
|
||||||
|
assert_eq!(num2.load(Ordering::Relaxed), 1);
|
||||||
|
// assert_eq!(num3.load(Ordering::Relaxed), 1);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user