1
0
Fork 0
mirror of https://github.com/actix/actix-web.git synced 2025-06-06 23:28:47 +00:00

Merge branch 'master' into ranges-support

This commit is contained in:
qrvælet 2018-03-06 07:01:27 +01:00 committed by GitHub
commit b15a4a2ba6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
87 changed files with 3836 additions and 2618 deletions

View file

@ -47,7 +47,7 @@ script:
USE_SKEPTIC=1 cargo test --features=alpn USE_SKEPTIC=1 cargo test --features=alpn
else else
cargo clean cargo clean
cargo test cargo test -- --nocapture
# --features=alpn # --features=alpn
fi fi
@ -55,8 +55,10 @@ script:
if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; 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/http-proxy && 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/juniper && cargo check && cd ../..
cd examples/state && cargo check && cd ../.. cd examples/state && cargo check && cd ../..
cd examples/template_tera && cargo check && cd ../.. cd examples/template_tera && cargo check && cd ../..
cd examples/diesel && cargo check && cd ../.. cd examples/diesel && cargo check && cd ../..
@ -64,6 +66,7 @@ script:
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 ../..
cd examples/websocket && cargo check && cd ../.. cd examples/websocket && cargo check && cd ../..
cd examples/unix-socket && cargo check && cd ../..
fi fi
- | - |
if [[ "$TRAVIS_RUST_VERSION" == "nightly" && $CLIPPY ]]; then if [[ "$TRAVIS_RUST_VERSION" == "nightly" && $CLIPPY ]]; then
@ -73,7 +76,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" == "stable" ]]; then if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "nightly" ]]; then
cargo doc --features "alpn, tls" --no-deps && cargo doc --features "alpn, tls" --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 &&

View file

@ -1,13 +1,60 @@
# Changes # Changes
## 0.4.0 (2018-02-..) ## 0.4.5 (2018-03-xx)
* Enable compression support for `NamedFile`
* Add `ResponseError` impl for `SendRequestError`.
This improves ergonomics of http client.
## 0.4.4 (2018-03-04)
* Allow to use Arc<Vec<u8>> as response/request body
* Fix handling of requests with an encoded body with a length > 8192 #93
## 0.4.3 (2018-03-03)
* Fix request body read bug
* Fix segmentation fault #79
* Set reuse address before bind #90
## 0.4.2 (2018-03-02)
* Better naming for websockets implementation
* Add `Pattern::with_prefix()`, make it more usable outside of actix
* Add csrf middleware for filter for cross-site request forgery #89
* Fix disconnect on idle connections
## 0.4.1 (2018-03-01)
* Rename `Route::p()` to `Route::filter()`
* Better naming for http codes
* Fix payload parse in situation when socket data is not ready.
* Fix Session mutable borrow lifetime #87
## 0.4.0 (2018-02-28)
* Actix 0.5 compatibility * Actix 0.5 compatibility
* Fix request json loader * Fix request json/urlencoded loaders
* Simplify HttpServer type definition * Simplify HttpServer type definition
* Added HttpRequest::encoding() method
* Added HttpRequest::mime_type() method * Added HttpRequest::mime_type() method
* Added HttpRequest::uri_mut(), allows to modify request uri * Added HttpRequest::uri_mut(), allows to modify request uri
@ -16,11 +63,11 @@
* Added http client * Added http client
* Added basic websocket client * Added websocket client
* Added TestServer::ws(), test websockets client * Added TestServer::ws(), test websockets client
* Added TestServer test http client * Added TestServer http client support
* Allow to override content encoding on application level * Allow to override content encoding on application level

View file

@ -1,15 +1,17 @@
[package] [package]
name = "actix-web" name = "actix-web"
version = "0.4.0" version = "0.4.4"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix web framework" description = "Actix web is a small, pragmatic, extremely fast, web framework for Rust."
readme = "README.md" readme = "README.md"
keywords = ["http", "web", "framework", "async", "futures"] keywords = ["http", "web", "framework", "async", "futures"]
homepage = "https://github.com/actix/actix-web" homepage = "https://github.com/actix/actix-web"
repository = "https://github.com/actix/actix-web.git" repository = "https://github.com/actix/actix-web.git"
documentation = "https://docs.rs/actix-web/" 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::http-client",
"web-programming::websocket"]
license = "MIT/Apache-2.0" license = "MIT/Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", exclude = [".gitignore", ".travis.yml", ".cargo/config",
"appveyor.yml", "/examples/**"] "appveyor.yml", "/examples/**"]
@ -40,7 +42,7 @@ brotli2 = "^0.3.2"
failure = "0.1.1" failure = "0.1.1"
flate2 = "1.0" flate2 = "1.0"
h2 = "0.1" h2 = "0.1"
http = "^0.1.2" http = "^0.1.5"
httparse = "1.2" httparse = "1.2"
http-range = "0.1" http-range = "0.1"
libc = "0.2" libc = "0.2"
@ -53,10 +55,11 @@ rand = "0.4"
regex = "0.2" regex = "0.2"
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
sha1 = "0.4" sha1 = "0.6"
smallvec = "0.6" smallvec = "0.6"
time = "0.1" time = "0.1"
url = "1.6" encoding = "0.2"
url = { version="1.7", features=["query_encoding"] }
cookie = { version="0.10", features=["percent-encode", "secure"] } cookie = { version="0.10", features=["percent-encode", "secure"] }
# io # io
@ -78,7 +81,7 @@ openssl = { version="0.10", optional = true }
tokio-openssl = { version="0.2", optional = true } tokio-openssl = { version="0.2", optional = true }
[dependencies.actix] [dependencies.actix]
version = "0.5" version = "^0.5.1"
[dev-dependencies] [dev-dependencies]
env_logger = "0.5" env_logger = "0.5"
@ -98,16 +101,20 @@ codegen-units = 1
members = [ members = [
"./", "./",
"examples/basics", "examples/basics",
"examples/juniper",
"examples/diesel", "examples/diesel",
"examples/r2d2", "examples/r2d2",
"examples/json", "examples/json",
"examples/hello-world", "examples/hello-world",
"examples/http-proxy",
"examples/multipart", "examples/multipart",
"examples/state", "examples/state",
"examples/redis-session",
"examples/template_tera", "examples/template_tera",
"examples/tls", "examples/tls",
"examples/websocket", "examples/websocket",
"examples/websocket-chat", "examples/websocket-chat",
"examples/web-cors/backend", "examples/web-cors/backend",
"examples/unix-socket",
"tools/wsload/", "tools/wsload/",
] ]

View file

@ -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, pragmatic, open source rust web framework. Actix web is a small, pragmatic, extremely fast, web framework for Rust.
* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.github.io/actix-web/guide/qs_13.html) protocols * Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.github.io/actix-web/guide/qs_13.html) protocols
* Streaming and pipelining * Streaming and pipelining
@ -10,11 +10,13 @@ Actix web is a small, fast, pragmatic, open source rust web framework.
* Configurable [request routing](https://actix.github.io/actix-web/guide/qs_5.html) * Configurable [request routing](https://actix.github.io/actix-web/guide/qs_5.html)
* Graceful server shutdown * Graceful server shutdown
* Multipart streams * Multipart streams
* SSL support with openssl or native-tls
* 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),
[Redis sessions](https://github.com/actix/actix-redis),
[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)) [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 actor framework](https://github.com/actix/actix).
## Documentation ## Documentation
@ -48,7 +50,7 @@ fn main() {
* [Basics](https://github.com/actix/actix-web/tree/master/examples/basics/) * [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/) * [Multipart 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/)
* [Tera templates](https://github.com/actix/actix-web/tree/master/examples/template_tera/) * [Tera templates](https://github.com/actix/actix-web/tree/master/examples/template_tera/)
* [Diesel integration](https://github.com/actix/actix-web/tree/master/examples/diesel/) * [Diesel integration](https://github.com/actix/actix-web/tree/master/examples/diesel/)
@ -57,11 +59,14 @@ fn main() {
* [SockJS Server](https://github.com/actix/actix-sockjs) * [SockJS Server](https://github.com/actix/actix-sockjs)
* [Json](https://github.com/actix/actix-web/tree/master/examples/json/) * [Json](https://github.com/actix/actix-web/tree/master/examples/json/)
You may consider checking out
[this directory](https://github.com/actix/actix-web/tree/master/examples) for more examples.
## Benchmarks ## Benchmarks
* [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r15&hw=ph&test=plaintext) * [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r15&hw=ph&test=plaintext)
* Some basic benchmarks could be found in this [respository](https://github.com/fafhrd91/benchmarks). * Some basic benchmarks could be found in this [repository](https://github.com/fafhrd91/benchmarks).
## License ## License

View file

@ -22,7 +22,7 @@ fn index(mut req: HttpRequest) -> Result<HttpResponse> {
println!("{:?}", req); println!("{:?}", req);
// example of ... // example of ...
if let Ok(ch) = req.payload_mut().readany().poll() { if let Ok(ch) = req.poll() {
if let futures::Async::Ready(Some(d)) = ch { if let futures::Async::Ready(Some(d)) = ch {
println!("{}", String::from_utf8_lossy(d.as_ref())); println!("{}", String::from_utf8_lossy(d.as_ref()));
} }
@ -139,7 +139,7 @@ fn main() {
// default // default
.default_resource(|r| { .default_resource(|r| {
r.method(Method::GET).f(p404); r.method(Method::GET).f(p404);
r.route().p(pred::Not(pred::Get())).f(|req| httpcodes::HTTPMethodNotAllowed); r.route().filter(pred::Not(pred::Get())).f(|req| httpcodes::HTTPMethodNotAllowed);
})) }))
.bind("127.0.0.1:8080").expect("Can not bind to 127.0.0.1:8080") .bind("127.0.0.1:8080").expect("Can not bind to 127.0.0.1:8080")

View file

@ -0,0 +1,11 @@
[package]
name = "http-proxy"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../.."
[dependencies]
env_logger = "0.5"
futures = "0.1"
actix = "0.5"
actix-web = { path = "../../", features=["alpn"] }

View file

@ -0,0 +1,59 @@
extern crate actix;
extern crate actix_web;
extern crate futures;
extern crate env_logger;
use actix_web::*;
use futures::{Future, Stream};
/// Stream client request response and then send body to a server response
fn index(_req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
client::ClientRequest::get("https://www.rust-lang.org/en-US/")
.finish().unwrap()
.send()
.map_err(error::Error::from) // <- convert SendRequestError to an Error
.and_then(
|resp| resp.body() // <- this is MessageBody type, resolves to complete body
.from_err() // <- convet PayloadError to a Error
.and_then(|body| { // <- we got complete body, now send as server response
httpcodes::HttpOk.build()
.body(body)
.map_err(error::Error::from)
}))
.responder()
}
/// streaming client request to a streaming server response
fn streaming(_req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
// send client request
client::ClientRequest::get("https://www.rust-lang.org/en-US/")
.finish().unwrap()
.send() // <- connect to host and send request
.map_err(error::Error::from) // <- convert SendRequestError to an Error
.and_then(|resp| { // <- we received client response
httpcodes::HttpOk.build()
// read one chunk from client response and send this chunk to a server response
// .from_err() converts PayloadError to a Error
.body(Body::Streaming(Box::new(resp.from_err())))
.map_err(|e| e.into()) // HttpOk::build() mayb return HttpError, we need to convert it to a Error
})
.responder()
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init();
let sys = actix::System::new("http-proxy");
let _addr = HttpServer::new(
|| Application::new()
.middleware(middleware::Logger::default())
.resource("/streaming", |r| r.f(streaming))
.resource("/", |r| r.f(index)))
.bind("127.0.0.1:8080").unwrap()
.start();
println!("Started http server: 127.0.0.1:8080");
let _ = sys.run();
}

View file

@ -34,9 +34,9 @@ fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
const MAX_SIZE: usize = 262_144; // max payload size is 256k const MAX_SIZE: usize = 262_144; // max payload size is 256k
/// This handler manually load request payload and parse serde json /// This handler manually load request payload and parse serde json
fn index_manual(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> { fn index_manual(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
// readany() returns asynchronous stream of Bytes objects // HttpRequest is stream of Bytes objects
req.payload_mut().readany() req
// `Future::from_err` acts like `?` in that it coerces the error type from // `Future::from_err` acts like `?` in that it coerces the error type from
// the future into the final error type // the future into the final error type
.from_err() .from_err()
@ -63,8 +63,8 @@ fn index_manual(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Err
} }
/// This handler manually load request payload and parse json-rust /// This handler manually load request payload and parse json-rust
fn index_mjsonrust(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> { fn index_mjsonrust(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
req.payload_mut().readany().concat2() req.concat2()
.from_err() .from_err()
.and_then(|body| { .and_then(|body| {
// body is loaded, now we can deserialize json-rust // body is loaded, now we can deserialize json-rust

View file

@ -0,0 +1,17 @@
[package]
name = "juniper-example"
version = "0.1.0"
authors = ["pyros2097 <pyros2097@gmail.com>"]
workspace = "../.."
[dependencies]
env_logger = "0.5"
actix = "0.5"
actix-web = { path = "../../" }
futures = "0.1"
serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"
juniper = "0.9.2"

View file

@ -0,0 +1,15 @@
# juniper
Juniper integration for Actix web
### server
```bash
cd actix-web/examples/juniper
cargo run (or ``cargo watch -x run``)
# Started http server: 127.0.0.1:8080
```
### web client
[http://127.0.0.1:8080/graphiql](http://127.0.0.1:8080/graphiql)

View file

@ -0,0 +1,110 @@
//! Actix web juniper example
//!
//! A simple example integrating juniper in actix-web
extern crate serde;
extern crate serde_json;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate juniper;
extern crate futures;
extern crate actix;
extern crate actix_web;
extern crate env_logger;
use actix::*;
use actix_web::*;
use juniper::http::graphiql::graphiql_source;
use juniper::http::GraphQLRequest;
use futures::future::Future;
mod schema;
use schema::Schema;
use schema::create_schema;
struct State {
executor: Addr<Syn, GraphQLExecutor>,
}
#[derive(Serialize, Deserialize)]
pub struct GraphQLData(GraphQLRequest);
impl Message for GraphQLData {
type Result = Result<String, Error>;
}
pub struct GraphQLExecutor {
schema: std::sync::Arc<Schema>
}
impl GraphQLExecutor {
fn new(schema: std::sync::Arc<Schema>) -> GraphQLExecutor {
GraphQLExecutor {
schema: schema,
}
}
}
impl Actor for GraphQLExecutor {
type Context = SyncContext<Self>;
}
impl Handler<GraphQLData> for GraphQLExecutor {
type Result = Result<String, Error>;
fn handle(&mut self, msg: GraphQLData, _: &mut Self::Context) -> Self::Result {
let res = msg.0.execute(&self.schema, &());
let res_text = serde_json::to_string(&res)?;
Ok(res_text)
}
}
fn graphiql(_req: HttpRequest<State>) -> Result<HttpResponse> {
let html = graphiql_source("http://127.0.0.1:8080/graphql");
Ok(HttpResponse::build(StatusCode::OK)
.content_type("text/html; charset=utf-8")
.body(html).unwrap())
}
fn graphql(req: HttpRequest<State>) -> Box<Future<Item=HttpResponse, Error=Error>> {
let executor = req.state().executor.clone();
req.json()
.from_err()
.and_then(move |val: GraphQLData| {
executor.send(val)
.from_err()
.and_then(|res| {
match res {
Ok(user) => Ok(httpcodes::HTTPOk.build().body(user)?),
Err(_) => Ok(httpcodes::HTTPInternalServerError.into())
}
})
})
.responder()
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info");
let _ = env_logger::init();
let sys = actix::System::new("juniper-example");
let schema = std::sync::Arc::new(create_schema());
let addr = SyncArbiter::start(3, move || {
GraphQLExecutor::new(schema.clone())
});
// Start http server
let _addr = HttpServer::new(move || {
Application::with_state(State{executor: addr.clone()})
// enable logger
.middleware(middleware::Logger::default())
.resource("/graphql", |r| r.method(Method::POST).a(graphql))
.resource("/graphiql", |r| r.method(Method::GET).f(graphiql))})
.bind("127.0.0.1:8080").unwrap()
.start();
println!("Started http server: 127.0.0.1:8080");
let _ = sys.run();
}

View file

@ -0,0 +1,58 @@
use juniper::FieldResult;
use juniper::RootNode;
#[derive(GraphQLEnum)]
enum Episode {
NewHope,
Empire,
Jedi,
}
#[derive(GraphQLObject)]
#[graphql(description = "A humanoid creature in the Star Wars universe")]
struct Human {
id: String,
name: String,
appears_in: Vec<Episode>,
home_planet: String,
}
#[derive(GraphQLInputObject)]
#[graphql(description = "A humanoid creature in the Star Wars universe")]
struct NewHuman {
name: String,
appears_in: Vec<Episode>,
home_planet: String,
}
pub struct QueryRoot;
graphql_object!(QueryRoot: () |&self| {
field human(&executor, id: String) -> FieldResult<Human> {
Ok(Human{
id: "1234".to_owned(),
name: "Luke".to_owned(),
appears_in: vec![Episode::NewHope],
home_planet: "Mars".to_owned(),
})
}
});
pub struct MutationRoot;
graphql_object!(MutationRoot: () |&self| {
field createHuman(&executor, new_human: NewHuman) -> FieldResult<Human> {
Ok(Human{
id: "1234".to_owned(),
name: new_human.name,
appears_in: new_human.appears_in,
home_planet: new_human.home_planet,
})
}
});
pub type Schema = RootNode<'static, QueryRoot, MutationRoot>;
pub fn create_schema() -> Schema {
Schema::new(QueryRoot {}, MutationRoot {})
}

View file

@ -11,7 +11,7 @@ use futures::{Future, Stream};
use futures::future::{result, Either}; use futures::future::{result, Either};
fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>>
{ {
println!("{:?}", req); println!("{:?}", req);

View file

@ -0,0 +1,11 @@
[package]
name = "redis-session"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../.."
[dependencies]
env_logger = "0.5"
actix = "0.5"
actix-web = "0.4"
actix-redis = { version = "0.2", features = ["web"] }

View file

@ -0,0 +1,48 @@
#![allow(unused_variables)]
extern crate actix;
extern crate actix_web;
extern crate actix_redis;
extern crate env_logger;
use actix_web::*;
use actix_web::middleware::RequestSession;
use actix_redis::RedisSessionBackend;
/// simple handler
fn index(mut req: HttpRequest) -> Result<HttpResponse> {
println!("{:?}", req);
// session
if let Some(count) = req.session().get::<i32>("counter")? {
println!("SESSION value: {}", count);
req.session().set("counter", count+1)?;
} else {
req.session().set("counter", 1)?;
}
Ok("Welcome!".into())
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info,actix_redis=info");
env_logger::init();
let sys = actix::System::new("basic-example");
HttpServer::new(
|| Application::new()
// enable logger
.middleware(middleware::Logger::default())
// cookie session middleware
.middleware(middleware::SessionStorage::new(
RedisSessionBackend::new("127.0.0.1:6379", &[0; 32])
))
// register simple route, handle all methods
.resource("/", |r| r.f(index)))
.bind("0.0.0.0:8080").unwrap()
.threads(1)
.start();
let _ = sys.run();
}

View file

@ -36,8 +36,7 @@ impl Actor for MyWebSocket {
type Context = ws::WebsocketContext<Self, AppState>; type Context = ws::WebsocketContext<Self, AppState>;
} }
impl Handler<ws::Message> for MyWebSocket { impl StreamHandler<ws::Message, ws::ProtocolError> for MyWebSocket {
type Result = ();
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
self.counter += 1; self.counter += 1;
@ -46,7 +45,7 @@ impl Handler<ws::Message> for MyWebSocket {
ws::Message::Ping(msg) => ctx.pong(&msg), ws::Message::Ping(msg) => ctx.pong(&msg),
ws::Message::Text(text) => ctx.text(text), ws::Message::Text(text) => ctx.text(text),
ws::Message::Binary(bin) => ctx.binary(bin), ws::Message::Binary(bin) => ctx.binary(bin),
ws::Message::Closed | ws::Message::Error => { ws::Message::Close(_) => {
ctx.stop(); ctx.stop();
} }
_ => (), _ => (),

View file

@ -0,0 +1,10 @@
[package]
name = "unix-socket"
version = "0.1.0"
authors = ["Messense Lv <messense@icloud.com>"]
[dependencies]
env_logger = "0.5"
actix = "0.5"
actix-web = { path = "../../" }
tokio-uds = "0.1"

View file

@ -0,0 +1,14 @@
## Unix domain socket example
```bash
$ curl --unix-socket /tmp/actix-uds.socket http://localhost/
Hello world!
```
Although this will only one thread for handling incoming connections
according to the
[documentation](https://actix.github.io/actix-web/actix_web/struct.HttpServer.html#method.start_incoming).
And it does not delete the socket file (`/tmp/actix-uds.socket`) when stopping
the server so it will fail to start next time you run it unless you delete
the socket file manually.

View file

@ -0,0 +1,31 @@
extern crate actix;
extern crate actix_web;
extern crate env_logger;
extern crate tokio_uds;
use actix::*;
use actix_web::*;
use tokio_uds::UnixListener;
fn index(_req: HttpRequest) -> &'static str {
"Hello world!"
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info");
let _ = env_logger::init();
let sys = actix::System::new("unix-socket");
let listener = UnixListener::bind("/tmp/actix-uds.socket", Arbiter::handle()).expect("bind failed");
let _addr = HttpServer::new(
|| Application::new()
// enable logger
.middleware(middleware::Logger::default())
.resource("/index.html", |r| r.f(|_| "Hello world!"))
.resource("/", |r| r.f(index)))
.start_incoming(listener.incoming(), false);
println!("Started http server: /tmp/actix-uds.socket");
let _ = sys.run();
}

View file

@ -92,8 +92,7 @@ impl Handler<session::Message> for WsChatSession {
} }
/// WebSocket message handler /// WebSocket message handler
impl Handler<ws::Message> for WsChatSession { impl StreamHandler<ws::Message, ws::ProtocolError> for WsChatSession {
type Result = ();
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);
@ -161,10 +160,9 @@ impl Handler<ws::Message> for WsChatSession {
}, },
ws::Message::Binary(bin) => ws::Message::Binary(bin) =>
println!("Unexpected binary"), println!("Unexpected binary"),
ws::Message::Closed | ws::Message::Error => { ws::Message::Close(_) => {
ctx.stop(); ctx.stop();
} }
_ => (),
} }
} }
} }

View file

@ -12,7 +12,7 @@ use std::time::Duration;
use actix::*; use actix::*;
use futures::Future; use futures::Future;
use actix_web::ws::{Message, WsClientError, WsClient, WsClientWriter}; use actix_web::ws::{Message, ProtocolError, Client, ClientWriter};
fn main() { fn main() {
@ -21,8 +21,8 @@ fn main() {
let sys = actix::System::new("ws-example"); let sys = actix::System::new("ws-example");
Arbiter::handle().spawn( Arbiter::handle().spawn(
WsClient::new("http://127.0.0.1:8080/ws/") Client::new("http://127.0.0.1:8080/ws/")
.connect().unwrap() .connect()
.map_err(|e| { .map_err(|e| {
println!("Error: {}", e); println!("Error: {}", e);
() ()
@ -53,7 +53,7 @@ fn main() {
} }
struct ChatClient(WsClientWriter); struct ChatClient(ClientWriter);
#[derive(Message)] #[derive(Message)]
struct ClientCommand(String); struct ClientCommand(String);
@ -88,12 +88,12 @@ impl Handler<ClientCommand> for ChatClient {
type Result = (); type Result = ();
fn handle(&mut self, msg: ClientCommand, ctx: &mut Context<Self>) { fn handle(&mut self, msg: ClientCommand, ctx: &mut Context<Self>) {
self.0.text(msg.0.as_str()) self.0.text(msg.0)
} }
} }
/// Handle server websocket messages /// Handle server websocket messages
impl StreamHandler<Message, WsClientError> for ChatClient { impl StreamHandler<Message, ProtocolError> for ChatClient {
fn handle(&mut self, msg: Message, ctx: &mut Context<Self>) { fn handle(&mut self, msg: Message, ctx: &mut Context<Self>) {
match msg { match msg {

View file

@ -25,8 +25,7 @@ impl Actor for MyWebSocket {
} }
/// Handler for `ws::Message` /// Handler for `ws::Message`
impl Handler<ws::Message> for MyWebSocket { impl StreamHandler<ws::Message, ws::ProtocolError> for MyWebSocket {
type Result = ();
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
// process websocket messages // process websocket messages
@ -35,7 +34,7 @@ impl Handler<ws::Message> for MyWebSocket {
ws::Message::Ping(msg) => ctx.pong(&msg), ws::Message::Ping(msg) => ctx.pong(&msg),
ws::Message::Text(text) => ctx.text(text), ws::Message::Text(text) => ctx.text(text),
ws::Message::Binary(bin) => ctx.binary(bin), ws::Message::Binary(bin) => ctx.binary(bin),
ws::Message::Closed | ws::Message::Error => { ws::Message::Close(_) => {
ctx.stop(); ctx.stop();
} }
_ => (), _ => (),

View file

@ -53,7 +53,7 @@ impl<S> Middleware<S> for Headers {
fn main() { fn main() {
Application::new() Application::new()
.middleware(Headers) // <- Register middleware, this method could be called multiple times .middleware(Headers) // <- Register middleware, this method could be called multiple times
.resource("/", |r| r.h(httpcodes::HTTPOk)); .resource("/", |r| r.h(httpcodes::HttpOk));
} }
``` ```
@ -144,8 +144,8 @@ fn main() {
.header("X-Version", "0.2") .header("X-Version", "0.2")
.finish()) .finish())
.resource("/test", |r| { .resource("/test", |r| {
r.method(Method::GET).f(|req| httpcodes::HTTPOk); r.method(Method::GET).f(|req| httpcodes::HttpOk);
r.method(Method::HEAD).f(|req| httpcodes::HTTPMethodNotAllowed); r.method(Method::HEAD).f(|req| httpcodes::HttpMethodNotAllowed);
}) })
.finish(); .finish();
} }

View file

@ -12,7 +12,7 @@ We have to define sync actor and connection that this actor will use. Same appro
could be used for other databases. could be used for other databases.
```rust,ignore ```rust,ignore
use actix::prelude::*;* use actix::prelude::*;
struct DbExecutor(SqliteConnection); struct DbExecutor(SqliteConnection);
@ -36,7 +36,7 @@ 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> type Result = Result<User, Error>;
fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Self::Result fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Self::Result
{ {
@ -110,8 +110,8 @@ fn index(req: HttpRequest<State>) -> Box<Future<Item=HttpResponse, Error=Error>>
.from_err() .from_err()
.and_then(|res| { .and_then(|res| {
match res { match res {
Ok(user) => Ok(httpcodes::HTTPOk.build().json(user)?), Ok(user) => Ok(httpcodes::HttpOk.build().json(user)?),
Err(_) => Ok(httpcodes::HTTPInternalServerError.into()) Err(_) => Ok(httpcodes::HttpInternalServerError.into())
} }
}) })
.responder() .responder()

View file

@ -20,8 +20,8 @@ contains the following:
```toml ```toml
[dependencies] [dependencies]
actix = "0.4" actix = "0.5"
actix-web = "0.3" actix-web = "0.4"
``` ```
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.

View file

@ -49,12 +49,12 @@ fn main() {
HttpServer::new(|| vec![ HttpServer::new(|| vec![
Application::new() Application::new()
.prefix("/app1") .prefix("/app1")
.resource("/", |r| r.f(|r| httpcodes::HTTPOk)), .resource("/", |r| r.f(|r| httpcodes::HttpOk)),
Application::new() Application::new()
.prefix("/app2") .prefix("/app2")
.resource("/", |r| r.f(|r| httpcodes::HTTPOk)), .resource("/", |r| r.f(|r| httpcodes::HttpOk)),
Application::new() Application::new()
.resource("/", |r| r.f(|r| httpcodes::HTTPOk)), .resource("/", |r| r.f(|r| httpcodes::HttpOk)),
]); ]);
} }
``` ```

View file

@ -20,7 +20,7 @@ fn main() {
HttpServer::new( HttpServer::new(
|| Application::new() || Application::new()
.resource("/", |r| r.h(httpcodes::HTTPOk))) .resource("/", |r| r.h(httpcodes::HttpOk)))
.bind("127.0.0.1:59080").unwrap() .bind("127.0.0.1:59080").unwrap()
.start(); .start();
@ -57,7 +57,7 @@ fn main() {
let sys = actix::System::new("http-server"); let sys = actix::System::new("http-server");
let addr = HttpServer::new( let addr = HttpServer::new(
|| Application::new() || Application::new()
.resource("/", |r| r.h(httpcodes::HTTPOk))) .resource("/", |r| r.h(httpcodes::HttpOk)))
.bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0") .bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0")
.shutdown_timeout(60) // <- Set shutdown timeout to 60 seconds .shutdown_timeout(60) // <- Set shutdown timeout to 60 seconds
.start(); .start();
@ -85,7 +85,7 @@ use actix_web::*;
fn main() { fn main() {
HttpServer::new( HttpServer::new(
|| Application::new() || Application::new()
.resource("/", |r| r.h(httpcodes::HTTPOk))) .resource("/", |r| r.h(httpcodes::HttpOk)))
.threads(4); // <- Start 4 workers .threads(4); // <- Start 4 workers
} }
``` ```
@ -146,7 +146,7 @@ use actix_web::*;
fn main() { fn main() {
HttpServer::new(|| HttpServer::new(||
Application::new() Application::new()
.resource("/", |r| r.h(httpcodes::HTTPOk))) .resource("/", |r| r.h(httpcodes::HttpOk)))
.keep_alive(None); // <- Use `SO_KEEPALIVE` socket option. .keep_alive(None); // <- Use `SO_KEEPALIVE` socket option.
} }
``` ```
@ -155,7 +155,7 @@ If first option is selected then *keep alive* state
calculated based on response's *connection-type*. By default calculated based on response's *connection-type*. By default
`HttpResponse::connection_type` is not defined in that case *keep alive* `HttpResponse::connection_type` is not defined in that case *keep alive*
defined by request's http version. Keep alive is off for *HTTP/1.0* defined by request's http version. Keep alive is off for *HTTP/1.0*
and is on for *HTTP/1.1* and "HTTP/2.0". and is on for *HTTP/1.1* and *HTTP/2.0*.
*Connection type* could be change with `HttpResponseBuilder::connection_type()` method. *Connection type* could be change with `HttpResponseBuilder::connection_type()` method.
@ -165,7 +165,7 @@ and is on for *HTTP/1.1* and "HTTP/2.0".
use actix_web::*; use actix_web::*;
fn index(req: HttpRequest) -> HttpResponse { fn index(req: HttpRequest) -> HttpResponse {
HTTPOk.build() HttpOk.build()
.connection_type(headers::ConnectionType::Close) // <- Close connection .connection_type(headers::ConnectionType::Close) // <- Close connection
.force_close() // <- Alternative method .force_close() // <- Alternative method
.finish().unwrap() .finish().unwrap()

View file

@ -65,7 +65,7 @@ impl<S> Handler<S> for MyHandler {
/// Handle request /// Handle request
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result { fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
self.0 += 1; self.0 += 1;
httpcodes::HTTPOk.into() httpcodes::HttpOk.into()
} }
} }
# fn main() {} # fn main() {}
@ -90,7 +90,7 @@ impl<S> Handler<S> for MyHandler {
/// Handle request /// Handle request
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result { fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
self.0.fetch_add(1, Ordering::Relaxed); self.0.fetch_add(1, Ordering::Relaxed);
httpcodes::HTTPOk.into() httpcodes::HttpOk.into()
} }
} }

View file

@ -14,7 +14,7 @@ impl<T: Responder, E: Into<Error>> Responder for Result<T, E>
And any error that implements `ResponseError` can be converted into `Error` object. And any error that implements `ResponseError` can be converted into `Error` object.
For example if *handler* function returns `io::Error`, it would be converted For example if *handler* function returns `io::Error`, it would be converted
into `HTTPInternalServerError` response. Implementation for `io::Error` is provided into `HttpInternalServerError` response. Implementation for `io::Error` is provided
by default. by default.
```rust ```rust

View file

@ -32,7 +32,7 @@ fn main() {
Application::new() Application::new()
.resource("/prefix", |r| r.f(index)) .resource("/prefix", |r| r.f(index))
.resource("/user/{name}", .resource("/user/{name}",
|r| r.method(Method::GET).f(|req| HTTPOk)) |r| r.method(Method::GET).f(|req| HttpOk))
.finish(); .finish();
} }
``` ```
@ -52,7 +52,7 @@ returns *NOT FOUND* http resources.
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 created 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`.
Application routes incoming requests based on route criteria which is defined during Application routes incoming requests based on route criteria which is defined during
resource registration and route registration. Resource matches all routes it contains in resource registration and route registration. Resource matches all routes it contains in
@ -68,9 +68,9 @@ fn main() {
Application::new() Application::new()
.resource("/path", |resource| .resource("/path", |resource|
resource.route() resource.route()
.p(pred::Get()) .filter(pred::Get())
.p(pred::Header("content-type", "text/plain")) .filter(pred::Header("content-type", "text/plain"))
.f(|req| HTTPOk) .f(|req| HttpOk)
) )
.finish(); .finish();
} }
@ -85,7 +85,7 @@ If resource can not match any route "NOT FOUND" response get returned.
[*Route*](../actix_web/struct.Route.html) object. Route can be configured with [*Route*](../actix_web/struct.Route.html) object. Route can be configured with
builder-like pattern. Following configuration methods are available: builder-like pattern. Following configuration methods are available:
* [*Route::p()*](../actix_web/struct.Route.html#method.p) method registers new predicate, * [*Route::filter()*](../actix_web/struct.Route.html#method.filter) method registers new predicate,
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
@ -336,14 +336,14 @@ resource with the name "foo" and the pattern "{a}/{b}/{c}", you might do this.
# #
fn index(req: HttpRequest) -> HttpResponse { fn index(req: HttpRequest) -> HttpResponse {
let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource
HTTPOk.into() HttpOk.into()
} }
fn main() { fn main() {
let app = Application::new() let app = Application::new()
.resource("/test/{a}/{b}/{c}", |r| { .resource("/test/{a}/{b}/{c}", |r| {
r.name("foo"); // <- set resource name, then it could be used in `url_for` r.name("foo"); // <- set resource name, then it could be used in `url_for`
r.method(Method::GET).f(|_| httpcodes::HTTPOk); r.method(Method::GET).f(|_| httpcodes::HttpOk);
}) })
.finish(); .finish();
} }
@ -367,7 +367,7 @@ use actix_web::*;
fn index(mut req: HttpRequest) -> Result<HttpResponse> { fn index(mut req: HttpRequest) -> Result<HttpResponse> {
let url = req.url_for("youtube", &["oHg5SJYRHA0"])?; let url = req.url_for("youtube", &["oHg5SJYRHA0"])?;
assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0"); assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0");
Ok(httpcodes::HTTPOk.into()) Ok(httpcodes::HttpOk.into())
} }
fn main() { fn main() {
@ -404,7 +404,7 @@ This handler designed to be use as a handler for application's *default resource
# use actix_web::*; # use actix_web::*;
# #
# fn index(req: HttpRequest) -> httpcodes::StaticResponse { # fn index(req: HttpRequest) -> httpcodes::StaticResponse {
# httpcodes::HTTPOk # httpcodes::HttpOk
# } # }
fn main() { fn main() {
let app = Application::new() let app = Application::new()
@ -429,7 +429,7 @@ It is possible to register path normalization only for *GET* requests only
# use actix_web::*; # use actix_web::*;
# #
# fn index(req: HttpRequest) -> httpcodes::StaticResponse { # fn index(req: HttpRequest) -> httpcodes::StaticResponse {
# httpcodes::HTTPOk # httpcodes::HttpOk
# } # }
fn main() { fn main() {
let app = Application::new() let app = Application::new()
@ -502,8 +502,8 @@ fn main() {
Application::new() Application::new()
.resource("/index.html", |r| .resource("/index.html", |r|
r.route() r.route()
.p(ContentTypeHeader) .filter(ContentTypeHeader)
.h(HTTPOk)); .h(HttpOk));
} }
``` ```
@ -530,8 +530,8 @@ fn main() {
Application::new() Application::new()
.resource("/index.html", |r| .resource("/index.html", |r|
r.route() r.route()
.p(pred::Not(pred::Get())) .filter(pred::Not(pred::Get()))
.f(|req| HTTPMethodNotAllowed)) .f(|req| HttpMethodNotAllowed))
.finish(); .finish();
} }
``` ```
@ -567,8 +567,8 @@ use actix_web::httpcodes::*;
fn main() { fn main() {
Application::new() Application::new()
.default_resource(|r| { .default_resource(|r| {
r.method(Method::GET).f(|req| HTTPNotFound); r.method(Method::GET).f(|req| HttpNotFound);
r.route().p(pred::Not(pred::Get())).f(|req| HTTPMethodNotAllowed); r.route().filter(pred::Not(pred::Get())).f(|req| HttpMethodNotAllowed);
}) })
# .finish(); # .finish();
} }

View file

@ -84,7 +84,7 @@ fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
req.json().from_err() req.json().from_err()
.and_then(|val: MyObj| { .and_then(|val: MyObj| {
println!("model: {:?}", val); println!("model: {:?}", val);
Ok(httpcodes::HTTPOk.build().json(val)?) // <- send response Ok(httpcodes::HttpOk.build().json(val)?) // <- send response
}) })
.responder() .responder()
} }
@ -106,10 +106,10 @@ use futures::{Future, Stream};
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
struct MyObj {name: String, number: i32} struct MyObj {name: String, number: i32}
fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> { fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
// `concat2` will asynchronously read each chunk of the request body and // `concat2` will asynchronously read each chunk of the request body and
// return a single, concatenated, chunk // return a single, concatenated, chunk
req.payload_mut().readany().concat2() req.concat2()
// `Future::from_err` acts like `?` in that it coerces the error type from // `Future::from_err` acts like `?` in that it coerces the error type from
// the future into the final error type // the future into the final error type
.from_err() .from_err()
@ -117,7 +117,7 @@ fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
// synchronous workflow // synchronous workflow
.and_then(|body| { // <- body is loaded, now we can deserialize json .and_then(|body| { // <- body is loaded, now we can deserialize json
let obj = serde_json::from_slice::<MyObj>(&body)?; let obj = serde_json::from_slice::<MyObj>(&body)?;
Ok(httpcodes::HTTPOk.build().json(obj)?) // <- send response Ok(httpcodes::HttpOk.build().json(obj)?) // <- send response
}) })
.responder() .responder()
} }
@ -169,13 +169,18 @@ get enabled automatically.
Enabling chunked encoding for *HTTP/2.0* responses is forbidden. Enabling chunked encoding for *HTTP/2.0* responses is forbidden.
```rust ```rust
# extern crate bytes;
# extern crate actix_web; # extern crate actix_web;
# extern crate futures;
# use futures::Stream;
use actix_web::*; use actix_web::*;
use bytes::Bytes;
use futures::stream::once;
fn index(req: HttpRequest) -> HttpResponse { fn index(req: HttpRequest) -> HttpResponse {
HttpResponse::Ok() HttpResponse::Ok()
.chunked() .chunked()
.body(Body::Streaming(payload::Payload::empty().stream())).unwrap() .body(Body::Streaming(Box::new(once(Ok(Bytes::from_static(b"data")))))).unwrap()
} }
# fn main() {} # fn main() {}
``` ```
@ -246,7 +251,7 @@ fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
.from_err() .from_err()
.and_then(|params| { // <- url encoded parameters .and_then(|params| { // <- url encoded parameters
println!("==== BODY ==== {:?}", params); println!("==== BODY ==== {:?}", params);
ok(httpcodes::HTTPOk.into()) ok(httpcodes::HttpOk.into())
}) })
.responder() .responder()
} }
@ -256,21 +261,8 @@ fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
## Streaming request ## Streaming request
Actix uses [*Payload*](../actix_web/payload/struct.Payload.html) object as request payload stream. *HttpRequest* is a stream of `Bytes` objects. It could be used to read request
*HttpRequest* provides several methods, which can be used for payload access. body payload.
At the same time *Payload* implements *Stream* trait, so it could be used with various
stream combinators. Also *Payload* provides several convenience methods that return
future object that resolve to Bytes object.
* *readany()* method returns *Stream* of *Bytes* objects.
* *readexactly()* method returns *Future* that resolves when specified number of bytes
get received.
* *readline()* method returns *Future* that resolves when `\n` get received.
* *readuntil()* method returns *Future* that resolves when specified bytes string
matches in input bytes stream
In this example handle reads request payload chunk by chunk and prints every chunk. In this example handle reads request payload chunk by chunk and prints every chunk.
@ -283,9 +275,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() req.from_err()
.readany()
.from_err()
.fold((), |_, chunk| { .fold((), |_, chunk| {
println!("Chunk: {:?}", chunk); println!("Chunk: {:?}", chunk);
result::<_, error::PayloadError>(Ok(())) result::<_, error::PayloadError>(Ok(()))

View file

@ -20,10 +20,10 @@ use actix_web::test::TestRequest;
fn index(req: HttpRequest) -> HttpResponse { fn index(req: HttpRequest) -> HttpResponse {
if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) { if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) {
if let Ok(s) = hdr.to_str() { if let Ok(s) = hdr.to_str() {
return httpcodes::HTTPOk.into() return httpcodes::HttpOk.into()
} }
} }
httpcodes::HTTPBadRequest.into() httpcodes::HttpBadRequest.into()
} }
fn main() { fn main() {
@ -59,7 +59,7 @@ use actix_web::*;
use actix_web::test::TestServer; use actix_web::test::TestServer;
fn index(req: HttpRequest) -> HttpResponse { fn index(req: HttpRequest) -> HttpResponse {
httpcodes::HTTPOk.into() httpcodes::HttpOk.into()
} }
fn main() { fn main() {
@ -84,7 +84,7 @@ use actix_web::*;
use actix_web::test::TestServer; use actix_web::test::TestServer;
fn index(req: HttpRequest) -> HttpResponse { fn index(req: HttpRequest) -> HttpResponse {
httpcodes::HTTPOk.into() httpcodes::HttpOk.into()
} }
/// This function get called by http server. /// This function get called by http server.
@ -130,8 +130,7 @@ impl Actor for Ws {
type Context = ws::WebsocketContext<Self>; type Context = ws::WebsocketContext<Self>;
} }
impl Handler<ws::Message> for Ws { impl StreamHandler<ws::Message, ws::WsError> for Ws {
type Result = ();
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
match msg { match msg {

View file

@ -21,9 +21,8 @@ impl Actor for Ws {
type Context = ws::WebsocketContext<Self>; type Context = ws::WebsocketContext<Self>;
} }
/// Define Handler for ws::Message message /// Handler for ws::Message message
impl Handler<ws::Message> for Ws { impl StreamHandler<ws::Message, ws::ProtocolError> for Ws {
type Result=();
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
match msg { match msg {
@ -43,7 +42,7 @@ fn main() {
``` ```
Simple websocket echo server example is available in Simple websocket echo server example is available in
[examples directory](https://github.com/actix/actix-web/blob/master/examples/websocket.rs). [examples directory](https://github.com/actix/actix-web/blob/master/examples/websocket).
Example chat server with ability to chat over websocket connection or tcp connection Example chat server with ability to chat over websocket connection or tcp connection
is available in [websocket-chat directory](https://github.com/actix/actix-web/tree/master/examples/websocket-chat/) is available in [websocket-chat directory](https://github.com/actix/actix-web/tree/master/examples/websocket-chat/)

View file

@ -149,7 +149,7 @@ impl<S> Application<S> where S: 'static {
pub fn with_state(state: S) -> Application<S> { pub fn with_state(state: S) -> Application<S> {
Application { Application {
parts: Some(ApplicationParts { parts: Some(ApplicationParts {
state: state, state,
prefix: "/".to_owned(), prefix: "/".to_owned(),
settings: ServerSettings::default(), settings: ServerSettings::default(),
default: Resource::default_not_found(), default: Resource::default_not_found(),
@ -183,8 +183,8 @@ impl<S> Application<S> where S: 'static {
/// let app = Application::new() /// let app = Application::new()
/// .prefix("/app") /// .prefix("/app")
/// .resource("/test", |r| { /// .resource("/test", |r| {
/// r.method(Method::GET).f(|_| httpcodes::HTTPOk); /// r.method(Method::GET).f(|_| httpcodes::HttpOk);
/// r.method(Method::HEAD).f(|_| httpcodes::HTTPMethodNotAllowed); /// r.method(Method::HEAD).f(|_| httpcodes::HttpMethodNotAllowed);
/// }) /// })
/// .finish(); /// .finish();
/// } /// }
@ -226,8 +226,8 @@ impl<S> Application<S> where S: 'static {
/// fn main() { /// fn main() {
/// let app = Application::new() /// let app = Application::new()
/// .resource("/test", |r| { /// .resource("/test", |r| {
/// r.method(Method::GET).f(|_| httpcodes::HTTPOk); /// r.method(Method::GET).f(|_| httpcodes::HttpOk);
/// r.method(Method::HEAD).f(|_| httpcodes::HTTPMethodNotAllowed); /// r.method(Method::HEAD).f(|_| httpcodes::HttpMethodNotAllowed);
/// }); /// });
/// } /// }
/// ``` /// ```
@ -281,7 +281,7 @@ impl<S> Application<S> where S: 'static {
/// fn index(mut req: HttpRequest) -> Result<HttpResponse> { /// fn index(mut req: HttpRequest) -> Result<HttpResponse> {
/// let url = req.url_for("youtube", &["oHg5SJYRHA0"])?; /// let url = req.url_for("youtube", &["oHg5SJYRHA0"])?;
/// assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0"); /// assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0");
/// Ok(httpcodes::HTTPOk.into()) /// Ok(httpcodes::HttpOk.into())
/// } /// }
/// ///
/// fn main() { /// fn main() {
@ -320,9 +320,9 @@ impl<S> Application<S> where S: 'static {
/// let app = Application::new() /// let app = Application::new()
/// .handler("/app", |req: HttpRequest| { /// .handler("/app", |req: HttpRequest| {
/// match *req.method() { /// match *req.method() {
/// Method::GET => httpcodes::HTTPOk, /// Method::GET => httpcodes::HttpOk,
/// Method::POST => httpcodes::HTTPMethodNotAllowed, /// Method::POST => httpcodes::HttpMethodNotAllowed,
/// _ => httpcodes::HTTPNotFound, /// _ => httpcodes::HttpNotFound,
/// }}); /// }});
/// } /// }
/// ``` /// ```
@ -361,17 +361,17 @@ impl<S> Application<S> where S: 'static {
default: parts.default, default: parts.default,
encoding: parts.encoding, encoding: parts.encoding,
router: router.clone(), router: router.clone(),
resources: resources,
handlers: parts.handlers, handlers: parts.handlers,
resources,
} }
)); ));
HttpApplication { HttpApplication {
state: Rc::new(parts.state), state: Rc::new(parts.state),
prefix: prefix.to_owned(), prefix: prefix.to_owned(),
inner: inner,
router: router.clone(), router: router.clone(),
middlewares: Rc::new(parts.middlewares), middlewares: Rc::new(parts.middlewares),
inner,
} }
} }
@ -394,11 +394,11 @@ impl<S> Application<S> where S: 'static {
/// HttpServer::new(|| { vec![ /// HttpServer::new(|| { vec![
/// Application::with_state(State1) /// Application::with_state(State1)
/// .prefix("/app1") /// .prefix("/app1")
/// .resource("/", |r| r.h(httpcodes::HTTPOk)) /// .resource("/", |r| r.h(httpcodes::HttpOk))
/// .boxed(), /// .boxed(),
/// Application::with_state(State2) /// Application::with_state(State2)
/// .prefix("/app2") /// .prefix("/app2")
/// .resource("/", |r| r.h(httpcodes::HTTPOk)) /// .resource("/", |r| r.h(httpcodes::HttpOk))
/// .boxed() ]}) /// .boxed() ]})
/// .bind("127.0.0.1:8080").unwrap() /// .bind("127.0.0.1:8080").unwrap()
/// .run() /// .run()
@ -459,7 +459,7 @@ mod tests {
#[test] #[test]
fn test_default_resource() { fn test_default_resource() {
let mut app = Application::new() let mut app = Application::new()
.resource("/test", |r| r.h(httpcodes::HTTPOk)) .resource("/test", |r| r.h(httpcodes::HttpOk))
.finish(); .finish();
let req = TestRequest::with_uri("/test").finish(); let req = TestRequest::with_uri("/test").finish();
@ -471,7 +471,7 @@ mod tests {
assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND);
let mut app = Application::new() let mut app = Application::new()
.default_resource(|r| r.h(httpcodes::HTTPMethodNotAllowed)) .default_resource(|r| r.h(httpcodes::HttpMethodNotAllowed))
.finish(); .finish();
let req = TestRequest::with_uri("/blah").finish(); let req = TestRequest::with_uri("/blah").finish();
let resp = app.run(req); let resp = app.run(req);
@ -482,7 +482,7 @@ mod tests {
fn test_unhandled_prefix() { fn test_unhandled_prefix() {
let mut app = Application::new() let mut app = Application::new()
.prefix("/test") .prefix("/test")
.resource("/test", |r| r.h(httpcodes::HTTPOk)) .resource("/test", |r| r.h(httpcodes::HttpOk))
.finish(); .finish();
assert!(app.handle(HttpRequest::default()).is_err()); assert!(app.handle(HttpRequest::default()).is_err());
} }
@ -490,7 +490,7 @@ mod tests {
#[test] #[test]
fn test_state() { fn test_state() {
let mut app = Application::with_state(10) let mut app = Application::with_state(10)
.resource("/", |r| r.h(httpcodes::HTTPOk)) .resource("/", |r| r.h(httpcodes::HttpOk))
.finish(); .finish();
let req = HttpRequest::default().with_state(Rc::clone(&app.state), app.router.clone()); let req = HttpRequest::default().with_state(Rc::clone(&app.state), app.router.clone());
let resp = app.run(req); let resp = app.run(req);
@ -501,7 +501,7 @@ mod tests {
fn test_prefix() { fn test_prefix() {
let mut app = Application::new() let mut app = Application::new()
.prefix("/test") .prefix("/test")
.resource("/blah", |r| r.h(httpcodes::HTTPOk)) .resource("/blah", |r| r.h(httpcodes::HttpOk))
.finish(); .finish();
let req = TestRequest::with_uri("/test").finish(); let req = TestRequest::with_uri("/test").finish();
let resp = app.handle(req); let resp = app.handle(req);
@ -523,7 +523,7 @@ mod tests {
#[test] #[test]
fn test_handler() { fn test_handler() {
let mut app = Application::new() let mut app = Application::new()
.handler("/test", httpcodes::HTTPOk) .handler("/test", httpcodes::HttpOk)
.finish(); .finish();
let req = TestRequest::with_uri("/test").finish(); let req = TestRequest::with_uri("/test").finish();
@ -551,7 +551,7 @@ mod tests {
fn test_handler_prefix() { fn test_handler_prefix() {
let mut app = Application::new() let mut app = Application::new()
.prefix("/app") .prefix("/app")
.handler("/test", httpcodes::HTTPOk) .handler("/test", httpcodes::HttpOk)
.finish(); .finish();
let req = TestRequest::with_uri("/test").finish(); let req = TestRequest::with_uri("/test").finish();

View file

@ -36,6 +36,8 @@ pub enum Binary {
/// Shared string body /// Shared string body
#[doc(hidden)] #[doc(hidden)]
ArcSharedString(Arc<String>), ArcSharedString(Arc<String>),
/// Shared vec body
SharedVec(Arc<Vec<u8>>),
} }
impl Body { impl Body {
@ -115,6 +117,7 @@ impl Binary {
Binary::Slice(slice) => slice.len(), Binary::Slice(slice) => slice.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(),
Binary::SharedVec(ref s) => s.len(),
} }
} }
@ -134,8 +137,9 @@ impl Clone for Binary {
match *self { match *self {
Binary::Bytes(ref bytes) => Binary::Bytes(bytes.clone()), Binary::Bytes(ref bytes) => Binary::Bytes(bytes.clone()),
Binary::Slice(slice) => Binary::Bytes(Bytes::from(slice)), Binary::Slice(slice) => Binary::Bytes(Bytes::from(slice)),
Binary::SharedString(ref s) => Binary::Bytes(Bytes::from(s.as_str())), Binary::SharedString(ref s) => Binary::SharedString(s.clone()),
Binary::ArcSharedString(ref s) => Binary::Bytes(Bytes::from(s.as_str())), Binary::ArcSharedString(ref s) => Binary::ArcSharedString(s.clone()),
Binary::SharedVec(ref s) => Binary::SharedVec(s.clone()),
} }
} }
} }
@ -147,6 +151,7 @@ impl Into<Bytes> for Binary {
Binary::Slice(slice) => Bytes::from(slice), Binary::Slice(slice) => Bytes::from(slice),
Binary::SharedString(s) => Bytes::from(s.as_str()), Binary::SharedString(s) => Bytes::from(s.as_str()),
Binary::ArcSharedString(s) => Bytes::from(s.as_str()), Binary::ArcSharedString(s) => Bytes::from(s.as_str()),
Binary::SharedVec(s) => Bytes::from(AsRef::<[u8]>::as_ref(s.as_ref())),
} }
} }
} }
@ -217,6 +222,18 @@ impl<'a> From<&'a Arc<String>> for Binary {
} }
} }
impl From<Arc<Vec<u8>>> for Binary {
fn from(body: Arc<Vec<u8>>) -> Binary {
Binary::SharedVec(body)
}
}
impl<'a> From<&'a Arc<Vec<u8>>> for Binary {
fn from(body: &'a Arc<Vec<u8>>) -> Binary {
Binary::SharedVec(Arc::clone(body))
}
}
impl AsRef<[u8]> for Binary { impl AsRef<[u8]> for Binary {
fn as_ref(&self) -> &[u8] { fn as_ref(&self) -> &[u8] {
match *self { match *self {
@ -224,6 +241,7 @@ impl AsRef<[u8]> for Binary {
Binary::Slice(slice) => slice, Binary::Slice(slice) => slice,
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(),
Binary::SharedVec(ref s) => s.as_ref().as_ref(),
} }
} }
} }
@ -304,6 +322,15 @@ 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_shared_vec() {
let b = Arc::new(Vec::from(&b"test"[..]));
assert_eq!(Binary::from(b.clone()).len(), 4);
assert_eq!(Binary::from(b.clone()).as_ref(), &b"test"[..]);
assert_eq!(Binary::from(&b).len(), 4);
assert_eq!(Binary::from(&b).as_ref(), &b"test"[..]);
}
#[test] #[test]
fn test_bytes_mut() { fn test_bytes_mut() {
let b = BytesMut::from("test"); let b = BytesMut::from("test");

View file

@ -1,25 +1,22 @@
#![allow(unused_imports, dead_code)]
use std::{io, time}; use std::{io, time};
use std::net::{SocketAddr, Shutdown}; use std::net::Shutdown;
use std::collections::VecDeque;
use std::time::Duration;
use actix::{fut, Actor, ActorFuture, Arbiter, Context, use actix::{fut, Actor, ActorFuture, Context,
Handler, Message, ActorResponse, Supervised}; Handler, Message, ActorResponse, Supervised};
use actix::registry::ArbiterService; use actix::registry::ArbiterService;
use actix::fut::WrapFuture; use actix::fut::WrapFuture;
use actix::actors::{Connector, ConnectorError, Connect as ResolveConnect}; use actix::actors::{Connector, ConnectorError, Connect as ResolveConnect};
use http::{Uri, HttpTryFrom, Error as HttpError}; use http::{Uri, HttpTryFrom, Error as HttpError};
use futures::{Async, Future, Poll}; use futures::Poll;
use tokio_core::reactor::Timeout;
use tokio_core::net::{TcpStream, TcpStreamNew};
use tokio_io::{AsyncRead, AsyncWrite}; use tokio_io::{AsyncRead, AsyncWrite};
#[cfg(feature="alpn")] #[cfg(feature="alpn")]
use openssl::ssl::{SslMethod, SslConnector, SslVerifyMode, Error as OpensslError}; use openssl::ssl::{SslMethod, SslConnector, Error as OpensslError};
#[cfg(feature="alpn")] #[cfg(feature="alpn")]
use tokio_openssl::SslConnectorExt; use tokio_openssl::SslConnectorExt;
#[cfg(feature="alpn")]
use futures::Future;
use HAS_OPENSSL; use HAS_OPENSSL;
use server::IoStream; use server::IoStream;
@ -97,7 +94,7 @@ impl Default for ClientConnector {
fn default() -> ClientConnector { fn default() -> ClientConnector {
#[cfg(feature="alpn")] #[cfg(feature="alpn")]
{ {
let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); let builder = SslConnector::builder(SslMethod::tls()).unwrap();
ClientConnector { ClientConnector {
connector: builder.build() connector: builder.build()
} }
@ -154,9 +151,7 @@ impl ClientConnector {
/// } /// }
/// ``` /// ```
pub fn with_connector(connector: SslConnector) -> ClientConnector { pub fn with_connector(connector: SslConnector) -> ClientConnector {
ClientConnector { ClientConnector { connector }
connector: connector
}
} }
} }
@ -201,7 +196,7 @@ impl Handler<Connect> for ClientConnector {
if proto.is_secure() { if proto.is_secure() {
fut::Either::A( fut::Either::A(
_act.connector.connect_async(&host, stream) _act.connector.connect_async(&host, stream)
.map_err(|e| ClientConnectorError::SslError(e)) .map_err(ClientConnectorError::SslError)
.map(|stream| Connection{stream: Box::new(stream)}) .map(|stream| Connection{stream: Box::new(stream)})
.into_actor(_act)) .into_actor(_act))
} else { } else {

View file

@ -8,7 +8,24 @@ mod writer;
pub use self::pipeline::{SendRequest, SendRequestError}; pub use self::pipeline::{SendRequest, SendRequestError};
pub use self::request::{ClientRequest, ClientRequestBuilder}; pub use self::request::{ClientRequest, ClientRequestBuilder};
pub use self::response::{ClientResponse, ResponseBody, JsonResponse, UrlEncoded}; pub use self::response::ClientResponse;
pub use self::connector::{Connect, Connection, ClientConnector, ClientConnectorError}; pub use self::connector::{Connect, Connection, ClientConnector, ClientConnectorError};
pub(crate) use self::writer::HttpClientWriter; pub(crate) use self::writer::HttpClientWriter;
pub(crate) use self::parser::{HttpResponseParser, HttpResponseParserError}; pub(crate) use self::parser::{HttpResponseParser, HttpResponseParserError};
use httpcodes;
use httpresponse::HttpResponse;
use error::ResponseError;
/// Convert `SendRequestError` to a `HttpResponse`
impl ResponseError for SendRequestError {
fn error_response(&self) -> HttpResponse {
match *self {
SendRequestError::Connector(_) => httpcodes::HttpBadGateway.into(),
_ => httpcodes::HttpInternalServerError.into(),
}
}
}

View file

@ -37,25 +37,22 @@ impl HttpResponseParser {
where T: IoStream where T: IoStream
{ {
// 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() { if buf.is_empty() {
match utils::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"); return Err(HttpResponseParserError::Disconnect),
return Err(HttpResponseParserError::Disconnect);
},
Ok(Async::Ready(_)) => (), Ok(Async::Ready(_)) => (),
Ok(Async::NotReady) => Ok(Async::NotReady) =>
return Ok(Async::NotReady), return Ok(Async::NotReady),
Err(err) => Err(err) =>
return Err(HttpResponseParserError::Error(err.into())) return Err(HttpResponseParserError::Error(err.into()))
} }
false }
} else {
true
};
loop { loop {
match HttpResponseParser::parse_message(buf).map_err(HttpResponseParserError::Error)? { match HttpResponseParser::parse_message(buf)
.map_err(HttpResponseParserError::Error)?
{
Async::Ready((msg, decoder)) => { Async::Ready((msg, decoder)) => {
self.decoder = decoder; self.decoder = decoder;
return Ok(Async::Ready(msg)); return Ok(Async::Ready(msg));
@ -64,15 +61,13 @@ impl HttpResponseParser {
if buf.capacity() >= MAX_BUFFER_SIZE { if buf.capacity() >= MAX_BUFFER_SIZE {
return Err(HttpResponseParserError::Error(ParseError::TooLarge)); return Err(HttpResponseParserError::Error(ParseError::TooLarge));
} }
if read {
match utils::read_from_io(io, buf) { match utils::read_from_io(io, buf) {
Ok(Async::Ready(0)) => return Err(HttpResponseParserError::Disconnect), Ok(Async::Ready(0)) =>
return Err(HttpResponseParserError::Disconnect),
Ok(Async::Ready(_)) => (), Ok(Async::Ready(_)) => (),
Ok(Async::NotReady) => return Ok(Async::NotReady), Ok(Async::NotReady) => return Ok(Async::NotReady),
Err(err) => return Err(HttpResponseParserError::Error(err.into())), Err(err) =>
} return Err(HttpResponseParserError::Error(err.into())),
} else {
return Ok(Async::NotReady)
} }
}, },
} }
@ -83,20 +78,44 @@ impl HttpResponseParser {
-> Poll<Option<Bytes>, PayloadError> -> Poll<Option<Bytes>, PayloadError>
where T: IoStream where T: IoStream
{ {
if let Some(ref mut decoder) = self.decoder { if self.decoder.is_some() {
loop {
// read payload // read payload
match utils::read_from_io(io, buf) { let not_ready = match utils::read_from_io(io, buf) {
Ok(Async::Ready(0)) => return Err(PayloadError::Incomplete), Ok(Async::Ready(0)) => {
Err(err) => return Err(err.into()), if buf.is_empty() {
_ => (), return Err(PayloadError::Incomplete)
}
true
}
Err(err) => return Err(err.into()),
Ok(Async::NotReady) => true,
_ => false,
};
match self.decoder.as_mut().unwrap().decode(buf) {
Ok(Async::Ready(Some(b))) =>
return Ok(Async::Ready(Some(b))),
Ok(Async::Ready(None)) => {
self.decoder.take();
return Ok(Async::Ready(None))
}
Ok(Async::NotReady) => {
if not_ready {
return Ok(Async::NotReady)
}
}
Err(err) => return Err(err.into()),
}
} }
decoder.decode(buf).map_err(|e| e.into())
} else { } else {
Ok(Async::Ready(None)) Ok(Async::Ready(None))
} }
} }
fn parse_message(buf: &mut BytesMut) -> Poll<(ClientResponse, Option<Decoder>), ParseError> { fn parse_message(buf: &mut BytesMut)
-> Poll<(ClientResponse, Option<Decoder>), ParseError>
{
// Parse http message // Parse http message
let bytes_ptr = buf.as_ref().as_ptr() as usize; let bytes_ptr = buf.as_ref().as_ptr() as usize;
let mut headers: [httparse::Header; MAX_HEADERS] = let mut headers: [httparse::Header; MAX_HEADERS] =
@ -137,7 +156,9 @@ impl HttpResponseParser {
} }
} }
let decoder = if let Some(len) = hdrs.get(header::CONTENT_LENGTH) { let decoder = if status == StatusCode::SWITCHING_PROTOCOLS {
Some(Decoder::eof())
} else if let Some(len) = hdrs.get(header::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>() {
@ -153,25 +174,19 @@ impl HttpResponseParser {
} else if chunked(&hdrs)? { } else if chunked(&hdrs)? {
// Chunked encoding // Chunked encoding
Some(Decoder::chunked()) Some(Decoder::chunked())
} else if hdrs.contains_key(header::UPGRADE) {
Some(Decoder::eof())
} else { } else {
None None
}; };
if let Some(decoder) = decoder { if let Some(decoder) = decoder {
//let info = PayloadInfo {
//tx: PayloadType::new(&hdrs, psender),
// decoder: decoder,
//};
Ok(Async::Ready( Ok(Async::Ready(
(ClientResponse::new( (ClientResponse::new(
ClientMessage{status: status, version: version, ClientMessage{status, version,
headers: hdrs, cookies: None}), Some(decoder)))) headers: hdrs, cookies: None}), Some(decoder))))
} else { } else {
Ok(Async::Ready( Ok(Async::Ready(
(ClientResponse::new( (ClientResponse::new(
ClientMessage{status: status, version: version, ClientMessage{status, version,
headers: hdrs, cookies: None}), None))) headers: hdrs, cookies: None}), None)))
} }
} }

View file

@ -1,5 +1,6 @@
use std::{io, mem}; use std::{io, mem};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use http::header::CONTENT_ENCODING;
use futures::{Async, Future, Poll}; use futures::{Async, Future, Poll};
use futures::unsync::oneshot; use futures::unsync::oneshot;
@ -8,9 +9,12 @@ use actix::prelude::*;
use error::Error; use error::Error;
use body::{Body, BodyStream}; use body::{Body, BodyStream};
use context::{Frame, ActorHttpContext}; use context::{Frame, ActorHttpContext};
use headers::ContentEncoding;
use httpmessage::HttpMessage;
use error::PayloadError; use error::PayloadError;
use server::WriterState; use server::WriterState;
use server::shared::SharedBytes; use server::shared::SharedBytes;
use server::encoding::PayloadStream;
use super::{ClientRequest, ClientResponse}; use super::{ClientRequest, ClientResponse};
use super::{Connect, Connection, ClientConnector, ClientConnectorError}; use super::{Connect, Connection, ClientConnector, ClientConnectorError};
use super::HttpClientWriter; use super::HttpClientWriter;
@ -60,16 +64,13 @@ impl SendRequest {
pub(crate) fn with_connector(req: ClientRequest, conn: Addr<Unsync, ClientConnector>) pub(crate) fn with_connector(req: ClientRequest, conn: Addr<Unsync, ClientConnector>)
-> SendRequest -> SendRequest
{ {
SendRequest{ SendRequest{req, conn, state: State::New}
req: req,
state: State::New,
conn: conn}
} }
pub(crate) fn with_connection(req: ClientRequest, conn: Connection) -> SendRequest pub(crate) fn with_connection(req: ClientRequest, conn: Connection) -> SendRequest
{ {
SendRequest{ SendRequest{
req: req, req,
state: State::Connection(conn), state: State::Connection(conn),
conn: ClientConnector::from_registry()} conn: ClientConnector::from_registry()}
} }
@ -100,7 +101,7 @@ impl Future for SendRequest {
Err(_) => return Err(SendRequestError::Connector( Err(_) => return Err(SendRequestError::Connector(
ClientConnectorError::Disconnected)) ClientConnectorError::Disconnected))
}, },
State::Connection(stream) => { State::Connection(conn) => {
let mut writer = HttpClientWriter::new(SharedBytes::default()); let mut writer = HttpClientWriter::new(SharedBytes::default());
writer.start(&mut self.req)?; writer.start(&mut self.req)?;
@ -110,15 +111,15 @@ impl Future for SendRequest {
_ => IoBody::Done, _ => IoBody::Done,
}; };
let mut pl = Box::new(Pipeline { let pl = Box::new(Pipeline {
body: body, body, conn, writer,
conn: stream, parser: Some(HttpResponseParser::default()),
writer: writer,
parser: HttpResponseParser::default(),
parser_buf: BytesMut::new(), parser_buf: BytesMut::new(),
disconnected: false, disconnected: false,
running: RunningState::Running,
drain: None, drain: None,
decompress: None,
should_decompress: self.req.response_decompress(),
write_state: RunningState::Running,
}); });
self.state = State::Send(pl); self.state = State::Send(pl);
}, },
@ -150,11 +151,13 @@ pub(crate) struct Pipeline {
body: IoBody, body: IoBody,
conn: Connection, conn: Connection,
writer: HttpClientWriter, writer: HttpClientWriter,
parser: HttpResponseParser, parser: Option<HttpResponseParser>,
parser_buf: BytesMut, parser_buf: BytesMut,
disconnected: bool, disconnected: bool,
running: RunningState,
drain: Option<oneshot::Sender<()>>, drain: Option<oneshot::Sender<()>>,
decompress: Option<PayloadStream>,
should_decompress: bool,
write_state: RunningState,
} }
enum IoBody { enum IoBody {
@ -163,7 +166,7 @@ enum IoBody {
Done, Done,
} }
#[derive(PartialEq)] #[derive(Debug, PartialEq)]
enum RunningState { enum RunningState {
Running, Running,
Paused, Paused,
@ -189,25 +192,89 @@ impl Pipeline {
#[inline] #[inline]
pub fn parse(&mut self) -> Poll<ClientResponse, HttpResponseParserError> { pub fn parse(&mut self) -> Poll<ClientResponse, HttpResponseParserError> {
self.parser.parse(&mut self.conn, &mut self.parser_buf) match self.parser.as_mut().unwrap().parse(&mut self.conn, &mut self.parser_buf) {
Ok(Async::Ready(resp)) => {
// check content-encoding
if self.should_decompress {
if let Some(enc) = resp.headers().get(CONTENT_ENCODING) {
if let Ok(enc) = enc.to_str() {
match ContentEncoding::from(enc) {
ContentEncoding::Auto | ContentEncoding::Identity => (),
enc => self.decompress = Some(PayloadStream::new(enc)),
}
}
}
}
Ok(Async::Ready(resp))
}
val => val,
}
} }
#[inline] #[inline]
pub fn poll(&mut self) -> Poll<Option<Bytes>, PayloadError> { pub fn poll(&mut self) -> Poll<Option<Bytes>, PayloadError> {
self.poll_write() let mut need_run = false;
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e).as_str()))?;
Ok(self.parser.parse_payload(&mut self.conn, &mut self.parser_buf)?) // need write?
if let Async::NotReady = self.poll_write()
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e)))?
{
need_run = true;
}
// need read?
if self.parser.is_some() {
loop {
match self.parser.as_mut().unwrap()
.parse_payload(&mut self.conn, &mut self.parser_buf)?
{
Async::Ready(Some(b)) => {
if let Some(ref mut decompress) = self.decompress {
match decompress.feed_data(b) {
Ok(Some(b)) => return Ok(Async::Ready(Some(b))),
Ok(None) => return Ok(Async::NotReady),
Err(ref err) if err.kind() == io::ErrorKind::WouldBlock =>
continue,
Err(err) => return Err(err.into()),
}
} else {
return Ok(Async::Ready(Some(b)))
}
},
Async::Ready(None) => {
let _ = self.parser.take();
break
}
Async::NotReady => return Ok(Async::NotReady),
}
}
}
// eof
if let Some(mut decompress) = self.decompress.take() {
let res = decompress.feed_eof();
if let Some(b) = res? {
return Ok(Async::Ready(Some(b)))
}
}
if need_run {
Ok(Async::NotReady)
} else {
Ok(Async::Ready(None))
}
} }
#[inline] #[inline]
pub fn poll_write(&mut self) -> Poll<(), Error> { pub fn poll_write(&mut self) -> Poll<(), Error> {
if self.running == RunningState::Done { if self.write_state == RunningState::Done {
return Ok(Async::Ready(())) return Ok(Async::Ready(()))
} }
let mut done = false; let mut done = false;
if self.drain.is_none() && self.running != RunningState::Paused { if self.drain.is_none() && self.write_state != RunningState::Paused {
'outter: loop { 'outter: loop {
let result = match mem::replace(&mut self.body, IoBody::Done) { let result = match mem::replace(&mut self.body, IoBody::Done) {
IoBody::Payload(mut body) => { IoBody::Payload(mut body) => {
@ -243,6 +310,7 @@ impl Pipeline {
match frame { match frame {
Frame::Chunk(None) => { Frame::Chunk(None) => {
// info.context = Some(ctx); // info.context = Some(ctx);
self.disconnected = true;
self.writer.write_eof()?; self.writer.write_eof()?;
break 'outter break 'outter
}, },
@ -253,7 +321,7 @@ impl Pipeline {
} }
self.body = IoBody::Actor(ctx); self.body = IoBody::Actor(ctx);
if self.drain.is_some() { if self.drain.is_some() {
self.running.resume(); self.write_state.resume();
break break
} }
res.unwrap() res.unwrap()
@ -270,6 +338,7 @@ impl Pipeline {
} }
}, },
IoBody::Done => { IoBody::Done => {
self.disconnected = true;
done = true; done = true;
break break
} }
@ -277,11 +346,11 @@ impl Pipeline {
match result { match result {
WriterState::Pause => { WriterState::Pause => {
self.running.pause(); self.write_state.pause();
break break
} }
WriterState::Done => { WriterState::Done => {
self.running.resume() self.write_state.resume()
}, },
} }
} }
@ -290,14 +359,18 @@ impl Pipeline {
// flush io but only if we need to // flush io but only if we need to
match self.writer.poll_completed(&mut self.conn, false) { match self.writer.poll_completed(&mut self.conn, false) {
Ok(Async::Ready(_)) => { Ok(Async::Ready(_)) => {
self.running.resume(); if self.disconnected {
self.write_state = RunningState::Done;
} else {
self.write_state.resume();
}
// resolve drain futures // resolve drain futures
if let Some(tx) = self.drain.take() { if let Some(tx) = self.drain.take() {
let _ = tx.send(()); let _ = tx.send(());
} }
// restart io processing // restart io processing
if !done { if !done || self.write_state == RunningState::Done {
self.poll_write() self.poll_write()
} else { } else {
Ok(Async::NotReady) Ok(Async::NotReady)

View file

@ -4,7 +4,7 @@ use std::io::Write;
use actix::{Addr, Unsync}; use actix::{Addr, Unsync};
use cookie::{Cookie, CookieJar}; use cookie::{Cookie, CookieJar};
use bytes::{BytesMut, BufMut}; use bytes::{BytesMut, BufMut};
use http::{HeaderMap, Method, Version, Uri, HttpTryFrom, Error as HttpError}; use http::{uri, HeaderMap, Method, Version, Uri, HttpTryFrom, Error as HttpError};
use http::header::{self, HeaderName, HeaderValue}; use http::header::{self, HeaderName, HeaderValue};
use serde_json; use serde_json;
use serde::Serialize; use serde::Serialize;
@ -25,6 +25,16 @@ pub struct ClientRequest {
chunked: bool, chunked: bool,
upgrade: bool, upgrade: bool,
encoding: ContentEncoding, encoding: ContentEncoding,
response_decompress: bool,
buffer_capacity: Option<(usize, usize)>,
conn: ConnectionType,
}
enum ConnectionType {
Default,
Connector(Addr<Unsync, ClientConnector>),
Connection(Connection),
} }
impl Default for ClientRequest { impl Default for ClientRequest {
@ -39,6 +49,9 @@ impl Default for ClientRequest {
chunked: false, chunked: false,
upgrade: false, upgrade: false,
encoding: ContentEncoding::Auto, encoding: ContentEncoding::Auto,
response_decompress: true,
buffer_capacity: None,
conn: ConnectionType::Default,
} }
} }
} }
@ -89,6 +102,7 @@ impl ClientRequest {
request: Some(ClientRequest::default()), request: Some(ClientRequest::default()),
err: None, err: None,
cookies: None, cookies: None,
default_headers: true,
} }
} }
@ -158,6 +172,16 @@ impl ClientRequest {
self.encoding self.encoding
} }
/// Decompress response payload
#[inline]
pub fn response_decompress(&self) -> bool {
self.response_decompress
}
pub fn buffer_capacity(&self) -> Option<(usize, usize)> {
self.buffer_capacity
}
/// Get body os this response /// Get body os this response
#[inline] #[inline]
pub fn body(&self) -> &Body { pub fn body(&self) -> &Body {
@ -175,18 +199,14 @@ impl ClientRequest {
} }
/// Send request /// Send request
pub fn send(self) -> SendRequest { ///
SendRequest::new(self) /// This method returns future that resolves to a ClientResponse
pub fn send(mut self) -> SendRequest {
match mem::replace(&mut self.conn, ConnectionType::Default) {
ConnectionType::Default => SendRequest::new(self),
ConnectionType::Connector(conn) => SendRequest::with_connector(self, conn),
ConnectionType::Connection(conn) => SendRequest::with_connection(self, conn),
} }
/// Send request using custom connector
pub fn with_connector(self, conn: Addr<Unsync, ClientConnector>) -> SendRequest {
SendRequest::with_connector(self, conn)
}
/// Send request using existing Connection
pub fn with_connection(self, conn: Connection) -> SendRequest {
SendRequest::with_connection(self, conn)
} }
} }
@ -216,6 +236,7 @@ pub struct ClientRequestBuilder {
request: Option<ClientRequest>, request: Option<ClientRequest>,
err: Option<HttpError>, err: Option<HttpError>,
cookies: Option<CookieJar>, cookies: Option<CookieJar>,
default_headers: bool,
} }
impl ClientRequestBuilder { impl ClientRequestBuilder {
@ -409,6 +430,48 @@ impl ClientRequestBuilder {
self self
} }
/// Do not add default request headers.
/// By default `Accept-Encoding` header is set.
pub fn no_default_headers(&mut self) -> &mut Self {
self.default_headers = false;
self
}
/// Disable automatic decompress response body
pub fn disable_decompress(&mut self) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.response_decompress = false;
}
self
}
/// Set write buffer capacity
pub fn buffer_capacity(&mut self,
low_watermark: usize,
high_watermark: usize) -> &mut Self
{
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.buffer_capacity = Some((low_watermark, high_watermark));
}
self
}
/// Send request using custom connector
pub fn with_connector(&mut self, conn: Addr<Unsync, ClientConnector>) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.conn = ConnectionType::Connector(conn);
}
self
}
/// Send request using existing Connection
pub fn with_connection(&mut self, conn: Connection) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.conn = ConnectionType::Connection(conn);
}
self
}
/// 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: FnOnce(&mut ClientRequestBuilder) where F: FnOnce(&mut ClientRequestBuilder)
@ -437,6 +500,23 @@ impl ClientRequestBuilder {
return Err(e) return Err(e)
} }
if self.default_headers {
// enable br only for https
let https =
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.uri.scheme_part()
.map(|s| s == &uri::Scheme::HTTPS).unwrap_or(true)
} else {
true
};
if https {
self.header(header::ACCEPT_ENCODING, "br, gzip, deflate");
} else {
self.header(header::ACCEPT_ENCODING, "gzip, deflate");
}
}
let mut request = self.request.take().expect("cannot reuse request builder"); let mut request = self.request.take().expect("cannot reuse request builder");
// set cookies // set cookies
@ -482,6 +562,7 @@ impl ClientRequestBuilder {
request: self.request.take(), request: self.request.take(),
err: self.err.take(), err: self.err.take(),
cookies: self.cookies.take(), cookies: self.cookies.take(),
default_headers: self.default_headers,
} }
} }
} }

View file

@ -1,20 +1,15 @@
use std::{fmt, str}; use std::{fmt, str};
use std::rc::Rc; use std::rc::Rc;
use std::cell::UnsafeCell; use std::cell::UnsafeCell;
use std::collections::HashMap;
use bytes::{Bytes, BytesMut}; use bytes::Bytes;
use cookie::Cookie; use cookie::Cookie;
use futures::{Async, Future, Poll, Stream}; use futures::{Async, Poll, Stream};
use http::{HeaderMap, StatusCode, Version}; use http::{HeaderMap, StatusCode, Version};
use http::header::{self, HeaderValue}; use http::header::{self, HeaderValue};
use mime::Mime;
use serde_json;
use serde::de::DeserializeOwned;
use url::form_urlencoded;
// use multipart::Multipart; use httpmessage::HttpMessage;
use error::{CookieParseError, ParseError, PayloadError, JsonPayloadError, UrlencodedError}; use error::{CookieParseError, PayloadError};
use super::pipeline::Pipeline; use super::pipeline::Pipeline;
@ -41,6 +36,14 @@ impl Default for ClientMessage {
/// An HTTP Client response /// An HTTP Client response
pub struct ClientResponse(Rc<UnsafeCell<ClientMessage>>, Option<Box<Pipeline>>); pub struct ClientResponse(Rc<UnsafeCell<ClientMessage>>, Option<Box<Pipeline>>);
impl HttpMessage for ClientResponse {
/// Get the headers from the response.
#[inline]
fn headers(&self) -> &HeaderMap {
&self.as_ref().headers
}
}
impl ClientResponse { impl ClientResponse {
pub(crate) fn new(msg: ClientMessage) -> ClientResponse { pub(crate) fn new(msg: ClientMessage) -> ClientResponse {
@ -68,30 +71,12 @@ impl ClientResponse {
self.as_ref().version self.as_ref().version
} }
/// Get the headers from the response.
#[inline]
pub fn headers(&self) -> &HeaderMap {
&self.as_ref().headers
}
/// Get a mutable reference to the headers.
#[inline]
pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.as_mut().headers
}
/// Get the status from the server. /// Get the status from the server.
#[inline] #[inline]
pub fn status(&self) -> StatusCode { pub fn status(&self) -> StatusCode {
self.as_ref().status self.as_ref().status
} }
/// Set the `StatusCode` for this response.
#[inline]
pub fn set_status(&mut self, status: StatusCode) {
self.as_mut().status = status
}
/// Load request cookies. /// Load request cookies.
pub fn cookies(&self) -> Result<&Vec<Cookie<'static>>, CookieParseError> { pub fn cookies(&self) -> Result<&Vec<Cookie<'static>>, CookieParseError> {
if self.as_ref().cookies.is_none() { if self.as_ref().cookies.is_none() {
@ -120,83 +105,6 @@ impl ClientResponse {
} }
None None
} }
/// Read the request content type. If request does not contain
/// *Content-Type* header, empty str get returned.
pub fn content_type(&self) -> &str {
if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) {
if let Ok(content_type) = content_type.to_str() {
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 has chunked transfer encoding
pub fn chunked(&self) -> Result<bool, ParseError> {
if let Some(encodings) = self.headers().get(header::TRANSFER_ENCODING) {
if let Ok(s) = encodings.to_str() {
Ok(s.to_lowercase().contains("chunked"))
} else {
Err(ParseError::Header)
}
} else {
Ok(false)
}
}
/// Load request body.
///
/// By default only 256Kb payload reads to a memory, then connection get dropped
/// and `PayloadError` get returned. Use `ResponseBody::limit()`
/// method to change upper limit.
pub fn body(self) -> ResponseBody {
ResponseBody::new(self)
}
// /// Return stream to http payload processes as multipart.
// ///
// /// Content-type: multipart/form-data;
// pub fn multipart(mut self) -> Multipart {
// Multipart::from_response(&mut self)
// }
/// Parse `application/x-www-form-urlencoded` encoded body.
/// Return `UrlEncoded` future. It resolves to a `HashMap<String, String>` which
/// contains decoded parameters.
///
/// Returns error:
///
/// * content type is not `application/x-www-form-urlencoded`
/// * transfer encoding is `chunked`.
/// * content-length is greater than 256k
pub fn urlencoded(self) -> UrlEncoded {
UrlEncoded::new(self)
}
/// Parse `application/json` encoded body.
/// Return `JsonResponse<T>` future. It resolves to a `T` value.
///
/// Returns error:
///
/// * content type is not `application/json`
/// * content length is greater than 256k
pub fn json<T: DeserializeOwned>(self) -> JsonResponse<T> {
JsonResponse::from_response(self)
}
} }
impl fmt::Debug for ClientResponse { impl fmt::Debug for ClientResponse {
@ -229,230 +137,3 @@ impl Stream for ClientResponse {
} }
} }
} }
/// Future that resolves to a complete response body.
#[must_use = "ResponseBody does nothing unless polled"]
pub struct ResponseBody {
limit: usize,
resp: Option<ClientResponse>,
fut: Option<Box<Future<Item=Bytes, Error=PayloadError>>>,
}
impl ResponseBody {
/// Create `ResponseBody` for request.
pub fn new(resp: ClientResponse) -> Self {
ResponseBody {
limit: 262_144,
resp: Some(resp),
fut: None,
}
}
/// Change max size of payload. By default max size is 256Kb
pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit;
self
}
}
impl Future for ResponseBody {
type Item = Bytes;
type Error = PayloadError;
fn poll(&mut self) -> Poll<Bytes, PayloadError> {
if let Some(resp) = self.resp.take() {
if let Some(len) = resp.headers().get(header::CONTENT_LENGTH) {
if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<usize>() {
if len > self.limit {
return Err(PayloadError::Overflow);
}
} else {
return Err(PayloadError::Overflow);
}
}
}
let limit = self.limit;
let fut = resp.from_err()
.fold(BytesMut::new(), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
Err(PayloadError::Overflow)
} else {
body.extend_from_slice(&chunk);
Ok(body)
}
})
.map(|bytes| bytes.freeze());
self.fut = Some(Box::new(fut));
}
self.fut.as_mut().expect("ResponseBody could not be used second time").poll()
}
}
/// Client response json parser that resolves to a deserialized `T` value.
///
/// Returns error:
///
/// * content type is not `application/json`
/// * content length is greater than 256k
#[must_use = "JsonResponse does nothing unless polled"]
pub struct JsonResponse<T: DeserializeOwned>{
limit: usize,
ct: &'static str,
resp: Option<ClientResponse>,
fut: Option<Box<Future<Item=T, Error=JsonPayloadError>>>,
}
impl<T: DeserializeOwned> JsonResponse<T> {
/// Create `JsonResponse` for request.
pub fn from_response(resp: ClientResponse) -> Self {
JsonResponse{
limit: 262_144,
resp: Some(resp),
ct: "application/json",
fut: None,
}
}
/// Change max size of payload. By default max size is 256Kb
pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit;
self
}
/// Set allowed content type.
///
/// By default *application/json* content type is used. Set content type
/// to empty string if you want to disable content type check.
pub fn content_type(mut self, ct: &'static str) -> Self {
self.ct = ct;
self
}
}
impl<T: DeserializeOwned + 'static> Future for JsonResponse<T> {
type Item = T;
type Error = JsonPayloadError;
fn poll(&mut self) -> Poll<T, JsonPayloadError> {
if let Some(resp) = self.resp.take() {
if let Some(len) = resp.headers().get(header::CONTENT_LENGTH) {
if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<usize>() {
if len > self.limit {
return Err(JsonPayloadError::Overflow);
}
} else {
return Err(JsonPayloadError::Overflow);
}
}
}
// check content-type
if !self.ct.is_empty() && resp.content_type() != self.ct {
return Err(JsonPayloadError::ContentType)
}
let limit = self.limit;
let fut = resp.from_err()
.fold(BytesMut::new(), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
Err(JsonPayloadError::Overflow)
} else {
body.extend_from_slice(&chunk);
Ok(body)
}
})
.and_then(|body| Ok(serde_json::from_slice::<T>(&body)?));
self.fut = Some(Box::new(fut));
}
self.fut.as_mut().expect("JsonResponse could not be used second time").poll()
}
}
/// Future that resolves to a parsed urlencoded values.
#[must_use = "UrlEncoded does nothing unless polled"]
pub struct UrlEncoded {
resp: Option<ClientResponse>,
limit: usize,
fut: Option<Box<Future<Item=HashMap<String, String>, Error=UrlencodedError>>>,
}
impl UrlEncoded {
pub fn new(resp: ClientResponse) -> UrlEncoded {
UrlEncoded{resp: Some(resp),
limit: 262_144,
fut: None}
}
/// Change max size of payload. By default max size is 256Kb
pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit;
self
}
}
impl Future for UrlEncoded {
type Item = HashMap<String, String>;
type Error = UrlencodedError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(resp) = self.resp.take() {
if resp.chunked().unwrap_or(false) {
return Err(UrlencodedError::Chunked)
} else if let Some(len) = resp.headers().get(header::CONTENT_LENGTH) {
if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<u64>() {
if len > 262_144 {
return Err(UrlencodedError::Overflow);
}
} else {
return Err(UrlencodedError::UnknownLength);
}
} else {
return Err(UrlencodedError::UnknownLength);
}
}
// check content type
let mut encoding = false;
if let Some(content_type) = resp.headers().get(header::CONTENT_TYPE) {
if let Ok(content_type) = content_type.to_str() {
if content_type.to_lowercase() == "application/x-www-form-urlencoded" {
encoding = true;
}
}
}
if !encoding {
return Err(UrlencodedError::ContentType);
}
// urlencoded body
let limit = self.limit;
let fut = resp.from_err()
.fold(BytesMut::new(), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
Err(UrlencodedError::Overflow)
} else {
body.extend_from_slice(&chunk);
Ok(body)
}
})
.and_then(|body| {
let mut m = HashMap::new();
for (k, v) in form_urlencoded::parse(&body) {
m.insert(k.into(), v.into());
}
Ok(m)
});
self.fut = Some(Box::new(fut));
}
self.fut.as_mut().expect("UrlEncoded could not be used second time").poll()
}
}

View file

@ -1,4 +1,5 @@
#![allow(dead_code)] #![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))]
use std::io::{self, Write}; use std::io::{self, Write};
use std::cell::RefCell; use std::cell::RefCell;
use std::fmt::Write as FmtWrite; use std::fmt::Write as FmtWrite;
@ -48,14 +49,14 @@ pub(crate) struct HttpClientWriter {
impl HttpClientWriter { impl HttpClientWriter {
pub fn new(buf: SharedBytes) -> HttpClientWriter { pub fn new(buffer: SharedBytes) -> HttpClientWriter {
let encoder = ContentEncoder::Identity(TransferEncoding::eof(buf.clone())); let encoder = ContentEncoder::Identity(TransferEncoding::eof(buffer.clone()));
HttpClientWriter { HttpClientWriter {
flags: Flags::empty(), flags: Flags::empty(),
written: 0, written: 0,
headers_size: 0, headers_size: 0,
buffer: buf, buffer,
encoder: encoder, encoder,
low: LOW_WATERMARK, low: LOW_WATERMARK,
high: HIGH_WATERMARK, high: HIGH_WATERMARK,
} }
@ -65,9 +66,9 @@ impl HttpClientWriter {
self.buffer.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)
} // }
/// Set write buffer capacity /// Set write buffer capacity
pub fn set_buffer_capacity(&mut self, low_watermark: usize, high_watermark: usize) { pub fn set_buffer_capacity(&mut self, low_watermark: usize, high_watermark: usize) {
@ -105,6 +106,9 @@ impl HttpClientWriter {
// prepare task // prepare task
self.flags.insert(Flags::STARTED); self.flags.insert(Flags::STARTED);
self.encoder = content_encoder(self.buffer.clone(), msg); self.encoder = content_encoder(self.buffer.clone(), msg);
if let Some(capacity) = msg.buffer_capacity() {
self.set_buffer_capacity(capacity.0, capacity.1);
}
// render message // render message
{ {

View file

@ -164,6 +164,7 @@ impl<A, S> HttpContext<A, S> where A: Actor<Context=Self> {
self.stream = Some(SmallVec::new()); self.stream = Some(SmallVec::new());
} }
self.stream.as_mut().map(|s| s.push(frame)); self.stream.as_mut().map(|s| s.push(frame));
self.inner.modify();
} }
/// Handle of the running future /// Handle of the running future
@ -230,10 +231,7 @@ pub struct Drain<A> {
impl<A> Drain<A> { impl<A> Drain<A> {
pub fn new(fut: oneshot::Receiver<()>) -> Self { pub fn new(fut: oneshot::Receiver<()>) -> Self {
Drain { Drain { fut, _a: PhantomData }
fut: fut,
_a: PhantomData
}
} }
} }

View file

@ -24,7 +24,7 @@ use body::Body;
use handler::Responder; use handler::Responder;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
use httpcodes::{self, HTTPBadRequest, HTTPMethodNotAllowed, HTTPExpectationFailed}; use httpcodes::{self, HttpExpectationFailed};
/// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) /// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html)
/// for actix web operations /// for actix web operations
@ -90,14 +90,13 @@ impl<T: ResponseError> From<T> for Error {
} else { } else {
None None
}; };
Error { cause: Box::new(err), backtrace: backtrace } Error { cause: Box::new(err), backtrace }
} }
} }
/// Compatibility for `failure::Error` /// Compatibility for `failure::Error`
impl<T> ResponseError for failure::Compat<T> impl<T> ResponseError for failure::Compat<T>
where T: fmt::Display + fmt::Debug + Sync + Send + 'static where T: fmt::Display + fmt::Debug + Sync + Send + 'static { }
{ }
impl From<failure::Error> for Error { impl From<failure::Error> for Error {
fn from(err: failure::Error) -> Error { fn from(err: failure::Error) -> Error {
@ -293,6 +292,9 @@ pub enum MultipartError {
/// Multipart boundary is not found /// Multipart boundary is not found
#[fail(display="Multipart boundary is not found")] #[fail(display="Multipart boundary is not found")]
Boundary, Boundary,
/// Multipart stream is incomplete
#[fail(display="Multipart stream is incomplete")]
Incomplete,
/// Error during field parsing /// Error during field parsing
#[fail(display="{}", _0)] #[fail(display="{}", _0)]
Parse(#[cause] ParseError), Parse(#[cause] ParseError),
@ -333,57 +335,26 @@ pub enum ExpectError {
} }
impl ResponseError for ExpectError { impl ResponseError for ExpectError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
HTTPExpectationFailed.with_body("Unknown Expect") HttpExpectationFailed.with_body("Unknown Expect")
} }
} }
/// Websocket handshake errors /// A set of error that can occure during parsing content type
#[derive(Fail, PartialEq, Debug)] #[derive(Fail, PartialEq, Debug)]
pub enum WsHandshakeError { pub enum ContentTypeError {
/// Only get method is allowed /// Can not parse content type
#[fail(display="Method not allowed")] #[fail(display="Can not parse content type")]
GetMethodRequired, ParseError,
/// Upgrade header if not set to websocket /// Unknown content encoding
#[fail(display="Websocket upgrade is expected")] #[fail(display="Unknown content encoding")]
NoWebsocketUpgrade, UnknownEncoding,
/// Connection header is not set to upgrade
#[fail(display="Connection upgrade is expected")]
NoConnectionUpgrade,
/// Websocket version header is not set
#[fail(display="Websocket version header is required")]
NoVersionHeader,
/// Unsupported websocket version
#[fail(display="Unsupported version")]
UnsupportedVersion,
/// Websocket key is not set or wrong
#[fail(display="Unknown websocket key")]
BadWebsocketKey,
} }
impl ResponseError for WsHandshakeError { /// Return `BadRequest` for `ContentTypeError`
impl ResponseError for ContentTypeError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
match *self { HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty)
WsHandshakeError::GetMethodRequired => {
HTTPMethodNotAllowed
.build()
.header(header::ALLOW, "GET")
.finish()
.unwrap()
}
WsHandshakeError::NoWebsocketUpgrade =>
HTTPBadRequest.with_reason("No WebSocket UPGRADE header found"),
WsHandshakeError::NoConnectionUpgrade =>
HTTPBadRequest.with_reason("No CONNECTION upgrade"),
WsHandshakeError::NoVersionHeader =>
HTTPBadRequest.with_reason("Websocket version header is required"),
WsHandshakeError::UnsupportedVersion =>
HTTPBadRequest.with_reason("Unsupported version"),
WsHandshakeError::BadWebsocketKey =>
HTTPBadRequest.with_reason("Handshake error"),
}
} }
} }
@ -402,6 +373,9 @@ pub enum UrlencodedError {
/// Content type error /// Content type error
#[fail(display="Content type error")] #[fail(display="Content type error")]
ContentType, ContentType,
/// Parse error
#[fail(display="Parse error")]
Parse,
/// Payload error /// Payload error
#[fail(display="Error that occur during reading payload: {}", _0)] #[fail(display="Error that occur during reading payload: {}", _0)]
Payload(#[cause] PayloadError), Payload(#[cause] PayloadError),
@ -412,9 +386,9 @@ impl ResponseError for UrlencodedError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
match *self { match *self {
UrlencodedError::Overflow => httpcodes::HTTPPayloadTooLarge.into(), UrlencodedError::Overflow => httpcodes::HttpPayloadTooLarge.into(),
UrlencodedError::UnknownLength => httpcodes::HTTPLengthRequired.into(), UrlencodedError::UnknownLength => httpcodes::HttpLengthRequired.into(),
_ => httpcodes::HTTPBadRequest.into(), _ => httpcodes::HttpBadRequest.into(),
} }
} }
} }
@ -447,8 +421,8 @@ impl ResponseError for JsonPayloadError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
match *self { match *self {
JsonPayloadError::Overflow => httpcodes::HTTPPayloadTooLarge.into(), JsonPayloadError::Overflow => httpcodes::HttpPayloadTooLarge.into(),
_ => httpcodes::HTTPBadRequest.into(), _ => httpcodes::HttpBadRequest.into(),
} }
} }
} }
@ -536,10 +510,10 @@ unsafe impl<T> Sync for InternalError<T> {}
unsafe impl<T> Send for InternalError<T> {} unsafe impl<T> Send for InternalError<T> {}
impl<T> InternalError<T> { impl<T> InternalError<T> {
pub fn new(err: T, status: StatusCode) -> Self { pub fn new(cause: T, status: StatusCode) -> Self {
InternalError { InternalError {
cause: err, cause,
status: status, status,
backtrace: Backtrace::new(), backtrace: Backtrace::new(),
} }
} }
@ -739,22 +713,6 @@ mod tests {
assert_eq!(resp.status(), StatusCode::EXPECTATION_FAILED); assert_eq!(resp.status(), StatusCode::EXPECTATION_FAILED);
} }
#[test]
fn test_wserror_http_response() {
let resp: HttpResponse = WsHandshakeError::GetMethodRequired.error_response();
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
let resp: HttpResponse = WsHandshakeError::NoWebsocketUpgrade.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp: HttpResponse = WsHandshakeError::NoConnectionUpgrade.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp: HttpResponse = WsHandshakeError::NoVersionHeader.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp: HttpResponse = WsHandshakeError::UnsupportedVersion.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp: HttpResponse = WsHandshakeError::BadWebsocketKey.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
}
macro_rules! from { macro_rules! from {
($from:expr => $error:pat) => { ($from:expr => $error:pat) => {
match ParseError::from($from) { match ParseError::from($from) {

View file

@ -143,10 +143,7 @@ pub struct Directory{
impl Directory { impl Directory {
pub fn new(base: PathBuf, path: PathBuf) -> Directory { pub fn new(base: PathBuf, path: PathBuf) -> Directory {
Directory { Directory { base, path }
base: base,
path: path
}
} }
fn can_list(&self, entry: &io::Result<DirEntry>) -> bool { fn can_list(&self, entry: &io::Result<DirEntry>) -> bool {
@ -205,7 +202,7 @@ impl Responder for Directory {
<ul>\ <ul>\
{}\ {}\
</ul></body>\n</html>", index_of, index_of, body); </ul></body>\n</html>", index_of, index_of, body);
Ok(HTTPOk.build() Ok(HttpOk.build()
.content_type("text/html; charset=utf-8") .content_type("text/html; charset=utf-8")
.body(html).unwrap()) .body(html).unwrap())
} }
@ -330,7 +327,7 @@ impl<S> Handler<S> for StaticFiles {
} }
new_path.push_str(redir_index); new_path.push_str(redir_index);
Ok(FilesystemElement::Redirect( Ok(FilesystemElement::Redirect(
HTTPFound HttpFound
.build() .build()
.header::<_, &str>("LOCATION", &new_path) .header::<_, &str>("LOCATION", &new_path)
.finish().unwrap())) .finish().unwrap()))

View file

@ -215,7 +215,7 @@ impl<S, H, R> WrapHandler<S, H, R>
S: 'static, S: 'static,
{ {
pub fn new(h: H) -> Self { pub fn new(h: H) -> Self {
WrapHandler{h: h, s: PhantomData} WrapHandler{h, s: PhantomData}
} }
} }
@ -225,7 +225,7 @@ impl<S, H, R> RouteHandler<S> for WrapHandler<S, H, R>
S: 'static, S: 'static,
{ {
fn handle(&mut self, req: HttpRequest<S>) -> Reply { fn handle(&mut self, req: HttpRequest<S>) -> Reply {
let req2 = req.clone_without_state(); let req2 = req.without_state();
match self.h.handle(req).respond_to(req2) { match self.h.handle(req).respond_to(req2) {
Ok(reply) => reply.into(), Ok(reply) => reply.into(),
Err(err) => Reply::response(err.into()), Err(err) => Reply::response(err.into()),
@ -266,7 +266,7 @@ impl<S, H, F, R, E> RouteHandler<S> for AsyncHandler<S, H, F, R, E>
S: 'static, S: 'static,
{ {
fn handle(&mut self, req: HttpRequest<S>) -> Reply { fn handle(&mut self, req: HttpRequest<S>) -> Reply {
let req2 = req.clone_without_state(); let req2 = req.without_state();
let fut = (self.h)(req) let fut = (self.h)(req)
.map_err(|e| e.into()) .map_err(|e| e.into())
.then(move |r| { .then(move |r| {
@ -309,7 +309,7 @@ impl<S, H, F, R, E> RouteHandler<S> for AsyncHandler<S, H, F, R, E>
/// # use actix_web::*; /// # use actix_web::*;
/// # /// #
/// # fn index(req: HttpRequest) -> httpcodes::StaticResponse { /// # fn index(req: HttpRequest) -> httpcodes::StaticResponse {
/// # httpcodes::HTTPOk /// # httpcodes::HttpOk
/// # } /// # }
/// fn main() { /// fn main() {
/// let app = Application::new() /// let app = Application::new()
@ -345,10 +345,10 @@ impl NormalizePath {
/// Create new `NormalizePath` 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,
merge: merge, merge,
redirect,
re_merge: Regex::new("//+").unwrap(), re_merge: Regex::new("//+").unwrap(),
redirect: redirect,
not_found: StatusCode::NOT_FOUND, not_found: StatusCode::NOT_FOUND,
} }
} }
@ -395,7 +395,6 @@ impl<S> Handler<S> for NormalizePath {
} }
} }
} else if p.ends_with('/') { } else if p.ends_with('/') {
println!("=== {:?}", p);
// try to remove trailing slash // try to remove trailing slash
let p = p.as_ref().trim_right_matches('/'); let p = p.as_ref().trim_right_matches('/');
if router.has_route(p) { if router.has_route(p) {

View file

@ -8,7 +8,7 @@ use time;
use bytes::{BufMut, BytesMut}; use bytes::{BufMut, BytesMut};
use http::Version; use http::Version;
use httprequest::HttpMessage; use httprequest::HttpInnerMessage;
// "Sun, 06 Nov 1994 08:49:37 GMT".len() // "Sun, 06 Nov 1994 08:49:37 GMT".len()
pub(crate) const DATE_VALUE_LENGTH: usize = 29; pub(crate) const DATE_VALUE_LENGTH: usize = 29;
@ -67,7 +67,7 @@ impl fmt::Write for CachedDate {
} }
/// Internal use only! unsafe /// Internal use only! unsafe
pub(crate) struct SharedMessagePool(RefCell<VecDeque<Rc<HttpMessage>>>); pub(crate) struct SharedMessagePool(RefCell<VecDeque<Rc<HttpInnerMessage>>>);
impl SharedMessagePool { impl SharedMessagePool {
pub fn new() -> SharedMessagePool { pub fn new() -> SharedMessagePool {
@ -75,16 +75,16 @@ impl SharedMessagePool {
} }
#[inline] #[inline]
pub fn get(&self) -> Rc<HttpMessage> { pub fn get(&self) -> Rc<HttpInnerMessage> {
if let Some(msg) = self.0.borrow_mut().pop_front() { if let Some(msg) = self.0.borrow_mut().pop_front() {
msg msg
} else { } else {
Rc::new(HttpMessage::default()) Rc::new(HttpInnerMessage::default())
} }
} }
#[inline] #[inline]
pub fn release(&self, mut msg: Rc<HttpMessage>) { pub fn release(&self, mut msg: Rc<HttpInnerMessage>) {
let v = &mut self.0.borrow_mut(); let v = &mut self.0.borrow_mut();
if v.len() < 128 { if v.len() < 128 {
Rc::get_mut(&mut msg).unwrap().reset(); Rc::get_mut(&mut msg).unwrap().reset();
@ -93,10 +93,10 @@ impl SharedMessagePool {
} }
} }
pub(crate) struct SharedHttpMessage( pub(crate) struct SharedHttpInnerMessage(
Option<Rc<HttpMessage>>, Option<Rc<SharedMessagePool>>); Option<Rc<HttpInnerMessage>>, Option<Rc<SharedMessagePool>>);
impl Drop for SharedHttpMessage { impl Drop for SharedHttpInnerMessage {
fn drop(&mut self) { fn drop(&mut self) {
if let Some(ref pool) = self.1 { if let Some(ref pool) = self.1 {
if let Some(msg) = self.0.take() { if let Some(msg) = self.0.take() {
@ -108,56 +108,56 @@ impl Drop for SharedHttpMessage {
} }
} }
impl Deref for SharedHttpMessage { impl Deref for SharedHttpInnerMessage {
type Target = HttpMessage; type Target = HttpInnerMessage;
fn deref(&self) -> &HttpMessage { fn deref(&self) -> &HttpInnerMessage {
self.get_ref() self.get_ref()
} }
} }
impl DerefMut for SharedHttpMessage { impl DerefMut for SharedHttpInnerMessage {
fn deref_mut(&mut self) -> &mut HttpMessage { fn deref_mut(&mut self) -> &mut HttpInnerMessage {
self.get_mut() self.get_mut()
} }
} }
impl Clone for SharedHttpMessage { impl Clone for SharedHttpInnerMessage {
fn clone(&self) -> SharedHttpMessage { fn clone(&self) -> SharedHttpInnerMessage {
SharedHttpMessage(self.0.clone(), self.1.clone()) SharedHttpInnerMessage(self.0.clone(), self.1.clone())
} }
} }
impl Default for SharedHttpMessage { impl Default for SharedHttpInnerMessage {
fn default() -> SharedHttpMessage { fn default() -> SharedHttpInnerMessage {
SharedHttpMessage(Some(Rc::new(HttpMessage::default())), None) SharedHttpInnerMessage(Some(Rc::new(HttpInnerMessage::default())), None)
} }
} }
impl SharedHttpMessage { impl SharedHttpInnerMessage {
pub fn from_message(msg: HttpMessage) -> SharedHttpMessage { pub fn from_message(msg: HttpInnerMessage) -> SharedHttpInnerMessage {
SharedHttpMessage(Some(Rc::new(msg)), None) SharedHttpInnerMessage(Some(Rc::new(msg)), None)
} }
pub fn new(msg: Rc<HttpMessage>, pool: Rc<SharedMessagePool>) -> SharedHttpMessage { pub fn new(msg: Rc<HttpInnerMessage>, pool: Rc<SharedMessagePool>) -> SharedHttpInnerMessage {
SharedHttpMessage(Some(msg), Some(pool)) SharedHttpInnerMessage(Some(msg), Some(pool))
} }
#[inline(always)] #[inline(always)]
#[allow(mutable_transmutes)] #[allow(mutable_transmutes)]
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))] #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))]
pub fn get_mut(&self) -> &mut HttpMessage { pub fn get_mut(&self) -> &mut HttpInnerMessage {
let r: &HttpMessage = self.0.as_ref().unwrap().as_ref(); let r: &HttpInnerMessage = self.0.as_ref().unwrap().as_ref();
unsafe{mem::transmute(r)} unsafe{mem::transmute(r)}
} }
#[inline(always)] #[inline(always)]
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))] #[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
pub fn get_ref(&self) -> &HttpMessage { pub fn get_ref(&self) -> &HttpInnerMessage {
self.0.as_ref().unwrap() self.0.as_ref().unwrap()
} }
} }

View file

@ -7,67 +7,174 @@ use handler::{Reply, Handler, RouteHandler, Responder};
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::{HttpResponse, HttpResponseBuilder}; use httpresponse::{HttpResponse, HttpResponseBuilder};
pub const HttpOk: StaticResponse = StaticResponse(StatusCode::OK);
pub const HttpCreated: StaticResponse = StaticResponse(StatusCode::CREATED);
pub const HttpAccepted: StaticResponse = StaticResponse(StatusCode::ACCEPTED);
pub const HttpNonAuthoritativeInformation: StaticResponse =
StaticResponse(StatusCode::NON_AUTHORITATIVE_INFORMATION);
pub const HttpNoContent: StaticResponse = StaticResponse(StatusCode::NO_CONTENT);
pub const HttpResetContent: StaticResponse = StaticResponse(StatusCode::RESET_CONTENT);
pub const HttpPartialContent: StaticResponse = StaticResponse(StatusCode::PARTIAL_CONTENT);
pub const HttpMultiStatus: StaticResponse = StaticResponse(StatusCode::MULTI_STATUS);
pub const HttpAlreadyReported: StaticResponse = StaticResponse(StatusCode::ALREADY_REPORTED);
pub const HttpMultipleChoices: StaticResponse = StaticResponse(StatusCode::MULTIPLE_CHOICES);
pub const HttpMovedPermanenty: StaticResponse = StaticResponse(StatusCode::MOVED_PERMANENTLY);
pub const HttpFound: StaticResponse = StaticResponse(StatusCode::FOUND);
pub const HttpSeeOther: StaticResponse = StaticResponse(StatusCode::SEE_OTHER);
pub const HttpNotModified: StaticResponse = StaticResponse(StatusCode::NOT_MODIFIED);
pub const HttpUseProxy: StaticResponse = StaticResponse(StatusCode::USE_PROXY);
pub const HttpTemporaryRedirect: StaticResponse =
StaticResponse(StatusCode::TEMPORARY_REDIRECT);
pub const HttpPermanentRedirect: StaticResponse =
StaticResponse(StatusCode::PERMANENT_REDIRECT);
pub const HttpBadRequest: StaticResponse = StaticResponse(StatusCode::BAD_REQUEST);
pub const HttpUnauthorized: StaticResponse = StaticResponse(StatusCode::UNAUTHORIZED);
pub const HttpPaymentRequired: StaticResponse = StaticResponse(StatusCode::PAYMENT_REQUIRED);
pub const HttpForbidden: StaticResponse = StaticResponse(StatusCode::FORBIDDEN);
pub const HttpNotFound: StaticResponse = StaticResponse(StatusCode::NOT_FOUND);
pub const HttpMethodNotAllowed: StaticResponse =
StaticResponse(StatusCode::METHOD_NOT_ALLOWED);
pub const HttpNotAcceptable: StaticResponse = StaticResponse(StatusCode::NOT_ACCEPTABLE);
pub const HttpProxyAuthenticationRequired: StaticResponse =
StaticResponse(StatusCode::PROXY_AUTHENTICATION_REQUIRED);
pub const HttpRequestTimeout: StaticResponse = StaticResponse(StatusCode::REQUEST_TIMEOUT);
pub const HttpConflict: StaticResponse = StaticResponse(StatusCode::CONFLICT);
pub const HttpGone: StaticResponse = StaticResponse(StatusCode::GONE);
pub const HttpLengthRequired: StaticResponse = StaticResponse(StatusCode::LENGTH_REQUIRED);
pub const HttpPreconditionFailed: StaticResponse =
StaticResponse(StatusCode::PRECONDITION_FAILED);
pub const HttpPayloadTooLarge: StaticResponse = StaticResponse(StatusCode::PAYLOAD_TOO_LARGE);
pub const HttpUriTooLong: StaticResponse = StaticResponse(StatusCode::URI_TOO_LONG);
pub const HttpUnsupportedMediaType: StaticResponse =
StaticResponse(StatusCode::UNSUPPORTED_MEDIA_TYPE);
pub const HttpRangeNotSatisfiable: StaticResponse =
StaticResponse(StatusCode::RANGE_NOT_SATISFIABLE);
pub const HttpExpectationFailed: StaticResponse =
StaticResponse(StatusCode::EXPECTATION_FAILED);
pub const HttpInternalServerError: StaticResponse =
StaticResponse(StatusCode::INTERNAL_SERVER_ERROR);
pub const HttpNotImplemented: StaticResponse = StaticResponse(StatusCode::NOT_IMPLEMENTED);
pub const HttpBadGateway: StaticResponse = StaticResponse(StatusCode::BAD_GATEWAY);
pub const HttpServiceUnavailable: StaticResponse =
StaticResponse(StatusCode::SERVICE_UNAVAILABLE);
pub const HttpGatewayTimeout: StaticResponse =
StaticResponse(StatusCode::GATEWAY_TIMEOUT);
pub const HttpVersionNotSupported: StaticResponse =
StaticResponse(StatusCode::HTTP_VERSION_NOT_SUPPORTED);
pub const HttpVariantAlsoNegotiates: StaticResponse =
StaticResponse(StatusCode::VARIANT_ALSO_NEGOTIATES);
pub const HttpInsufficientStorage: StaticResponse =
StaticResponse(StatusCode::INSUFFICIENT_STORAGE);
pub const HttpLoopDetected: StaticResponse = StaticResponse(StatusCode::LOOP_DETECTED);
#[doc(hidden)]
pub const HTTPOk: StaticResponse = StaticResponse(StatusCode::OK); pub const HTTPOk: StaticResponse = StaticResponse(StatusCode::OK);
#[doc(hidden)]
pub const HTTPCreated: StaticResponse = StaticResponse(StatusCode::CREATED); pub const HTTPCreated: StaticResponse = StaticResponse(StatusCode::CREATED);
#[doc(hidden)]
pub const HTTPAccepted: StaticResponse = StaticResponse(StatusCode::ACCEPTED); pub const HTTPAccepted: StaticResponse = StaticResponse(StatusCode::ACCEPTED);
#[doc(hidden)]
pub const HTTPNonAuthoritativeInformation: StaticResponse = pub const HTTPNonAuthoritativeInformation: StaticResponse =
StaticResponse(StatusCode::NON_AUTHORITATIVE_INFORMATION); StaticResponse(StatusCode::NON_AUTHORITATIVE_INFORMATION);
#[doc(hidden)]
pub const HTTPNoContent: StaticResponse = StaticResponse(StatusCode::NO_CONTENT); pub const HTTPNoContent: StaticResponse = StaticResponse(StatusCode::NO_CONTENT);
#[doc(hidden)]
pub const HTTPResetContent: StaticResponse = StaticResponse(StatusCode::RESET_CONTENT); pub const HTTPResetContent: StaticResponse = StaticResponse(StatusCode::RESET_CONTENT);
#[doc(hidden)]
pub const HTTPPartialContent: StaticResponse = StaticResponse(StatusCode::PARTIAL_CONTENT); pub const HTTPPartialContent: StaticResponse = StaticResponse(StatusCode::PARTIAL_CONTENT);
#[doc(hidden)]
pub const HTTPMultiStatus: StaticResponse = StaticResponse(StatusCode::MULTI_STATUS); pub const HTTPMultiStatus: StaticResponse = StaticResponse(StatusCode::MULTI_STATUS);
#[doc(hidden)]
pub const HTTPAlreadyReported: StaticResponse = StaticResponse(StatusCode::ALREADY_REPORTED); pub const HTTPAlreadyReported: StaticResponse = StaticResponse(StatusCode::ALREADY_REPORTED);
#[doc(hidden)]
pub const HTTPMultipleChoices: StaticResponse = StaticResponse(StatusCode::MULTIPLE_CHOICES); pub const HTTPMultipleChoices: StaticResponse = StaticResponse(StatusCode::MULTIPLE_CHOICES);
#[doc(hidden)]
pub const HTTPMovedPermanenty: StaticResponse = StaticResponse(StatusCode::MOVED_PERMANENTLY); pub const HTTPMovedPermanenty: StaticResponse = StaticResponse(StatusCode::MOVED_PERMANENTLY);
#[doc(hidden)]
pub const HTTPFound: StaticResponse = StaticResponse(StatusCode::FOUND); pub const HTTPFound: StaticResponse = StaticResponse(StatusCode::FOUND);
#[doc(hidden)]
pub const HTTPSeeOther: StaticResponse = StaticResponse(StatusCode::SEE_OTHER); pub const HTTPSeeOther: StaticResponse = StaticResponse(StatusCode::SEE_OTHER);
#[doc(hidden)]
pub const HTTPNotModified: StaticResponse = StaticResponse(StatusCode::NOT_MODIFIED); pub const HTTPNotModified: StaticResponse = StaticResponse(StatusCode::NOT_MODIFIED);
#[doc(hidden)]
pub const HTTPUseProxy: StaticResponse = StaticResponse(StatusCode::USE_PROXY); pub const HTTPUseProxy: StaticResponse = StaticResponse(StatusCode::USE_PROXY);
#[doc(hidden)]
pub const HTTPTemporaryRedirect: StaticResponse = pub const HTTPTemporaryRedirect: StaticResponse =
StaticResponse(StatusCode::TEMPORARY_REDIRECT); StaticResponse(StatusCode::TEMPORARY_REDIRECT);
#[doc(hidden)]
pub const HTTPPermanentRedirect: StaticResponse = pub const HTTPPermanentRedirect: StaticResponse =
StaticResponse(StatusCode::PERMANENT_REDIRECT); StaticResponse(StatusCode::PERMANENT_REDIRECT);
#[doc(hidden)]
pub const HTTPBadRequest: StaticResponse = StaticResponse(StatusCode::BAD_REQUEST); pub const HTTPBadRequest: StaticResponse = StaticResponse(StatusCode::BAD_REQUEST);
#[doc(hidden)]
pub const HTTPUnauthorized: StaticResponse = StaticResponse(StatusCode::UNAUTHORIZED); pub const HTTPUnauthorized: StaticResponse = StaticResponse(StatusCode::UNAUTHORIZED);
#[doc(hidden)]
pub const HTTPPaymentRequired: StaticResponse = StaticResponse(StatusCode::PAYMENT_REQUIRED); pub const HTTPPaymentRequired: StaticResponse = StaticResponse(StatusCode::PAYMENT_REQUIRED);
#[doc(hidden)]
pub const HTTPForbidden: StaticResponse = StaticResponse(StatusCode::FORBIDDEN); pub const HTTPForbidden: StaticResponse = StaticResponse(StatusCode::FORBIDDEN);
#[doc(hidden)]
pub const HTTPNotFound: StaticResponse = StaticResponse(StatusCode::NOT_FOUND); pub const HTTPNotFound: StaticResponse = StaticResponse(StatusCode::NOT_FOUND);
#[doc(hidden)]
pub const HTTPMethodNotAllowed: StaticResponse = pub const HTTPMethodNotAllowed: StaticResponse =
StaticResponse(StatusCode::METHOD_NOT_ALLOWED); StaticResponse(StatusCode::METHOD_NOT_ALLOWED);
#[doc(hidden)]
pub const HTTPNotAcceptable: StaticResponse = StaticResponse(StatusCode::NOT_ACCEPTABLE); pub const HTTPNotAcceptable: StaticResponse = StaticResponse(StatusCode::NOT_ACCEPTABLE);
#[doc(hidden)]
pub const HTTPProxyAuthenticationRequired: StaticResponse = pub const HTTPProxyAuthenticationRequired: StaticResponse =
StaticResponse(StatusCode::PROXY_AUTHENTICATION_REQUIRED); StaticResponse(StatusCode::PROXY_AUTHENTICATION_REQUIRED);
#[doc(hidden)]
pub const HTTPRequestTimeout: StaticResponse = StaticResponse(StatusCode::REQUEST_TIMEOUT); pub const HTTPRequestTimeout: StaticResponse = StaticResponse(StatusCode::REQUEST_TIMEOUT);
#[doc(hidden)]
pub const HTTPConflict: StaticResponse = StaticResponse(StatusCode::CONFLICT); pub const HTTPConflict: StaticResponse = StaticResponse(StatusCode::CONFLICT);
#[doc(hidden)]
pub const HTTPGone: StaticResponse = StaticResponse(StatusCode::GONE); pub const HTTPGone: StaticResponse = StaticResponse(StatusCode::GONE);
#[doc(hidden)]
pub const HTTPLengthRequired: StaticResponse = StaticResponse(StatusCode::LENGTH_REQUIRED); pub const HTTPLengthRequired: StaticResponse = StaticResponse(StatusCode::LENGTH_REQUIRED);
#[doc(hidden)]
pub const HTTPPreconditionFailed: StaticResponse = pub const HTTPPreconditionFailed: StaticResponse =
StaticResponse(StatusCode::PRECONDITION_FAILED); StaticResponse(StatusCode::PRECONDITION_FAILED);
#[doc(hidden)]
pub const HTTPPayloadTooLarge: StaticResponse = StaticResponse(StatusCode::PAYLOAD_TOO_LARGE); pub const HTTPPayloadTooLarge: StaticResponse = StaticResponse(StatusCode::PAYLOAD_TOO_LARGE);
#[doc(hidden)]
pub const HTTPUriTooLong: StaticResponse = StaticResponse(StatusCode::URI_TOO_LONG); pub const HTTPUriTooLong: StaticResponse = StaticResponse(StatusCode::URI_TOO_LONG);
#[doc(hidden)]
pub const HTTPUnsupportedMediaType: StaticResponse = pub const HTTPUnsupportedMediaType: StaticResponse =
StaticResponse(StatusCode::UNSUPPORTED_MEDIA_TYPE); StaticResponse(StatusCode::UNSUPPORTED_MEDIA_TYPE);
#[doc(hidden)]
pub const HTTPRangeNotSatisfiable: StaticResponse = pub const HTTPRangeNotSatisfiable: StaticResponse =
StaticResponse(StatusCode::RANGE_NOT_SATISFIABLE); StaticResponse(StatusCode::RANGE_NOT_SATISFIABLE);
#[doc(hidden)]
pub const HTTPExpectationFailed: StaticResponse = pub const HTTPExpectationFailed: StaticResponse =
StaticResponse(StatusCode::EXPECTATION_FAILED); StaticResponse(StatusCode::EXPECTATION_FAILED);
#[doc(hidden)]
pub const HTTPInternalServerError: StaticResponse = pub const HTTPInternalServerError: StaticResponse =
StaticResponse(StatusCode::INTERNAL_SERVER_ERROR); StaticResponse(StatusCode::INTERNAL_SERVER_ERROR);
#[doc(hidden)]
pub const HTTPNotImplemented: StaticResponse = StaticResponse(StatusCode::NOT_IMPLEMENTED); pub const HTTPNotImplemented: StaticResponse = StaticResponse(StatusCode::NOT_IMPLEMENTED);
#[doc(hidden)]
pub const HTTPBadGateway: StaticResponse = StaticResponse(StatusCode::BAD_GATEWAY); pub const HTTPBadGateway: StaticResponse = StaticResponse(StatusCode::BAD_GATEWAY);
#[doc(hidden)]
pub const HTTPServiceUnavailable: StaticResponse = pub const HTTPServiceUnavailable: StaticResponse =
StaticResponse(StatusCode::SERVICE_UNAVAILABLE); StaticResponse(StatusCode::SERVICE_UNAVAILABLE);
#[doc(hidden)]
pub const HTTPGatewayTimeout: StaticResponse = pub const HTTPGatewayTimeout: StaticResponse =
StaticResponse(StatusCode::GATEWAY_TIMEOUT); StaticResponse(StatusCode::GATEWAY_TIMEOUT);
#[doc(hidden)]
pub const HTTPVersionNotSupported: StaticResponse = pub const HTTPVersionNotSupported: StaticResponse =
StaticResponse(StatusCode::HTTP_VERSION_NOT_SUPPORTED); StaticResponse(StatusCode::HTTP_VERSION_NOT_SUPPORTED);
#[doc(hidden)]
pub const HTTPVariantAlsoNegotiates: StaticResponse = pub const HTTPVariantAlsoNegotiates: StaticResponse =
StaticResponse(StatusCode::VARIANT_ALSO_NEGOTIATES); StaticResponse(StatusCode::VARIANT_ALSO_NEGOTIATES);
#[doc(hidden)]
pub const HTTPInsufficientStorage: StaticResponse = pub const HTTPInsufficientStorage: StaticResponse =
StaticResponse(StatusCode::INSUFFICIENT_STORAGE); StaticResponse(StatusCode::INSUFFICIENT_STORAGE);
#[doc(hidden)]
pub const HTTPLoopDetected: StaticResponse = StaticResponse(StatusCode::LOOP_DETECTED); pub const HTTPLoopDetected: StaticResponse = StaticResponse(StatusCode::LOOP_DETECTED);

596
src/httpmessage.rs Normal file
View file

@ -0,0 +1,596 @@
use std::str;
use std::collections::HashMap;
use bytes::{Bytes, BytesMut};
use futures::{Future, Stream, Poll};
use http_range::HttpRange;
use serde::de::DeserializeOwned;
use mime::Mime;
use url::form_urlencoded;
use encoding::all::UTF_8;
use encoding::EncodingRef;
use encoding::label::encoding_from_whatwg_label;
use http::{header, HeaderMap};
use json::JsonBody;
use multipart::Multipart;
use error::{ParseError, ContentTypeError,
HttpRangeError, PayloadError, UrlencodedError};
/// Trait that implements general purpose operations on http messages
pub trait HttpMessage {
/// Read the message headers.
fn headers(&self) -> &HeaderMap;
/// Read the request content type. If request does not contain
/// *Content-Type* header, empty str get returned.
fn content_type(&self) -> &str {
if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) {
if let Ok(content_type) = content_type.to_str() {
return content_type.split(';').next().unwrap().trim()
}
}
""
}
/// Get content type encoding
///
/// UTF-8 is used by default, If request charset is not set.
fn encoding(&self) -> Result<EncodingRef, ContentTypeError> {
if let Some(mime_type) = self.mime_type()? {
if let Some(charset) = mime_type.get_param("charset") {
if let Some(enc) = encoding_from_whatwg_label(charset.as_str()) {
Ok(enc)
} else {
Err(ContentTypeError::UnknownEncoding)
}
} else {
Ok(UTF_8)
}
} else {
Ok(UTF_8)
}
}
/// Convert the request content type to a known mime type.
fn mime_type(&self) -> Result<Option<Mime>, ContentTypeError> {
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) => Ok(Some(mt)),
Err(_) => Err(ContentTypeError::ParseError),
};
} else {
return Err(ContentTypeError::ParseError)
}
}
Ok(None)
}
/// Check if request has chunked transfer encoding
fn chunked(&self) -> Result<bool, ParseError> {
if let Some(encodings) = self.headers().get(header::TRANSFER_ENCODING) {
if let Ok(s) = encodings.to_str() {
Ok(s.to_lowercase().contains("chunked"))
} else {
Err(ParseError::Header)
}
} else {
Ok(false)
}
}
/// Parses Range HTTP header string as per RFC 2616.
/// `size` is full size of response (file).
fn range(&self, size: u64) -> Result<Vec<HttpRange>, HttpRangeError> {
if let Some(range) = self.headers().get(header::RANGE) {
HttpRange::parse(unsafe{str::from_utf8_unchecked(range.as_bytes())}, size)
.map_err(|e| e.into())
} else {
Ok(Vec::new())
}
}
/// Load http message body.
///
/// By default only 256Kb payload reads to a memory, then `PayloadError::Overflow`
/// get returned. Use `MessageBody::limit()` method to change upper limit.
///
/// ## Server example
///
/// ```rust
/// # extern crate bytes;
/// # extern crate actix_web;
/// # extern crate futures;
/// # #[macro_use] extern crate serde_derive;
/// use actix_web::*;
/// use bytes::Bytes;
/// use futures::future::Future;
///
/// fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
/// req.body() // <- get Body future
/// .limit(1024) // <- change max size of the body to a 1kb
/// .from_err()
/// .and_then(|bytes: Bytes| { // <- complete body
/// println!("==== BODY ==== {:?}", bytes);
/// Ok(httpcodes::HttpOk.into())
/// }).responder()
/// }
/// # fn main() {}
/// ```
fn body(self) -> MessageBody<Self>
where Self: Stream<Item=Bytes, Error=PayloadError> + Sized
{
MessageBody::new(self)
}
/// Parse `application/x-www-form-urlencoded` encoded body.
/// Return `UrlEncoded` future. It resolves to a `HashMap<String, String>` which
/// contains decoded parameters.
///
/// Returns error:
///
/// * content type is not `application/x-www-form-urlencoded`
/// * transfer encoding is `chunked`.
/// * content-length is greater than 256k
///
/// ## Server example
///
/// ```rust
/// # extern crate actix_web;
/// # extern crate futures;
/// use actix_web::*;
/// use futures::future::{Future, ok};
///
/// fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
/// req.urlencoded() // <- get UrlEncoded future
/// .from_err()
/// .and_then(|params| { // <- url encoded parameters
/// println!("==== BODY ==== {:?}", params);
/// ok(httpcodes::HttpOk.into())
/// })
/// .responder()
/// }
/// # fn main() {}
/// ```
fn urlencoded(self) -> UrlEncoded<Self>
where Self: Stream<Item=Bytes, Error=PayloadError> + Sized
{
UrlEncoded::new(self)
}
/// Parse `application/json` encoded body.
/// Return `JsonBody<T>` future. It resolves to a `T` value.
///
/// Returns error:
///
/// * content type is not `application/json`
/// * content length is greater than 256k
///
/// ## Server example
///
/// ```rust
/// # extern crate actix_web;
/// # extern crate futures;
/// # #[macro_use] extern crate serde_derive;
/// use actix_web::*;
/// use futures::future::{Future, ok};
///
/// #[derive(Deserialize, Debug)]
/// struct MyObj {
/// name: String,
/// }
///
/// fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
/// req.json() // <- get JsonBody future
/// .from_err()
/// .and_then(|val: MyObj| { // <- deserialized value
/// println!("==== BODY ==== {:?}", val);
/// Ok(httpcodes::HttpOk.into())
/// }).responder()
/// }
/// # fn main() {}
/// ```
fn json<T: DeserializeOwned>(self) -> JsonBody<Self, T>
where Self: Stream<Item=Bytes, Error=PayloadError> + Sized
{
JsonBody::new(self)
}
/// Return stream to http payload processes as multipart.
///
/// Content-type: multipart/form-data;
///
/// ## Server example
///
/// ```rust
/// # extern crate actix;
/// # extern crate actix_web;
/// # extern crate env_logger;
/// # extern crate futures;
/// # use std::str;
/// # use actix::*;
/// # use actix_web::*;
/// # use futures::{Future, Stream};
/// # use futures::future::{ok, result, Either};
/// fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
/// req.multipart().from_err() // <- get multipart stream for current request
/// .and_then(|item| match item { // <- iterate over multipart items
/// multipart::MultipartItem::Field(field) => {
/// // Field in turn is stream of *Bytes* object
/// Either::A(field.from_err()
/// .map(|c| println!("-- CHUNK: \n{:?}", str::from_utf8(&c)))
/// .finish())
/// },
/// multipart::MultipartItem::Nested(mp) => {
/// // Or item could be nested Multipart stream
/// Either::B(ok(()))
/// }
/// })
/// .finish() // <- Stream::finish() combinator from actix
/// .map(|_| httpcodes::HTTPOk.into())
/// .responder()
/// }
/// # fn main() {}
/// ```
fn multipart(self) -> Multipart<Self>
where Self: Stream<Item=Bytes, Error=PayloadError> + Sized
{
let boundary = Multipart::boundary(self.headers());
Multipart::new(boundary, self)
}
}
/// Future that resolves to a complete http message body.
pub struct MessageBody<T> {
limit: usize,
req: Option<T>,
fut: Option<Box<Future<Item=Bytes, Error=PayloadError>>>,
}
impl<T> MessageBody<T> {
/// Create `RequestBody` for request.
pub fn new(req: T) -> MessageBody<T> {
MessageBody {
limit: 262_144,
req: Some(req),
fut: None,
}
}
/// Change max size of payload. By default max size is 256Kb
pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit;
self
}
}
impl<T> Future for MessageBody<T>
where T: HttpMessage + Stream<Item=Bytes, Error=PayloadError> + 'static
{
type Item = Bytes;
type Error = PayloadError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(req) = self.req.take() {
if let Some(len) = req.headers().get(header::CONTENT_LENGTH) {
if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<usize>() {
if len > self.limit {
return Err(PayloadError::Overflow);
}
} else {
return Err(PayloadError::UnknownLength);
}
} else {
return Err(PayloadError::UnknownLength);
}
}
// future
let limit = self.limit;
self.fut = Some(Box::new(
req.from_err()
.fold(BytesMut::new(), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
Err(PayloadError::Overflow)
} else {
body.extend_from_slice(&chunk);
Ok(body)
}
})
.map(|body| body.freeze())
));
}
self.fut.as_mut().expect("UrlEncoded could not be used second time").poll()
}
}
/// Future that resolves to a parsed urlencoded values.
pub struct UrlEncoded<T> {
req: Option<T>,
limit: usize,
fut: Option<Box<Future<Item=HashMap<String, String>, Error=UrlencodedError>>>,
}
impl<T> UrlEncoded<T> {
pub fn new(req: T) -> UrlEncoded<T> {
UrlEncoded {
req: Some(req),
limit: 262_144,
fut: None,
}
}
/// Change max size of payload. By default max size is 256Kb
pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit;
self
}
}
impl<T> Future for UrlEncoded<T>
where T: HttpMessage + Stream<Item=Bytes, Error=PayloadError> + 'static
{
type Item = HashMap<String, String>;
type Error = UrlencodedError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(req) = self.req.take() {
if req.chunked().unwrap_or(false) {
return Err(UrlencodedError::Chunked)
} else if let Some(len) = req.headers().get(header::CONTENT_LENGTH) {
if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<u64>() {
if len > 262_144 {
return Err(UrlencodedError::Overflow);
}
} else {
return Err(UrlencodedError::UnknownLength)
}
} else {
return Err(UrlencodedError::UnknownLength)
}
}
// check content type
if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" {
return Err(UrlencodedError::ContentType)
}
let encoding = req.encoding().map_err(|_| UrlencodedError::ContentType)?;
// future
let limit = self.limit;
let fut = req.from_err()
.fold(BytesMut::new(), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
Err(UrlencodedError::Overflow)
} else {
body.extend_from_slice(&chunk);
Ok(body)
}
})
.and_then(move |body| {
let mut m = HashMap::new();
let parsed = form_urlencoded::parse_with_encoding(
&body, Some(encoding), false).map_err(|_| UrlencodedError::Parse)?;
for (k, v) in parsed {
m.insert(k.into(), v.into());
}
Ok(m)
});
self.fut = Some(Box::new(fut));
}
self.fut.as_mut().expect("UrlEncoded could not be used second time").poll()
}
}
#[cfg(test)]
mod tests {
use super::*;
use mime;
use encoding::Encoding;
use encoding::all::ISO_8859_2;
use futures::Async;
use http::{Method, Version, Uri};
use httprequest::HttpRequest;
use std::str::FromStr;
use std::iter::FromIterator;
use test::TestRequest;
#[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().unwrap(), Some(mime::APPLICATION_JSON));
let req = HttpRequest::default();
assert_eq!(req.mime_type().unwrap(), None);
let req = TestRequest::with_header(
"content-type", "application/json; charset=utf-8").finish();
let mt = req.mime_type().unwrap().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]
fn test_mime_type_error() {
let req = TestRequest::with_header(
"content-type", "applicationadfadsfasdflknadsfklnadsfjson").finish();
assert_eq!(Err(ContentTypeError::ParseError), req.mime_type());
}
#[test]
fn test_encoding() {
let req = HttpRequest::default();
assert_eq!(UTF_8.name(), req.encoding().unwrap().name());
let req = TestRequest::with_header(
"content-type", "application/json").finish();
assert_eq!(UTF_8.name(), req.encoding().unwrap().name());
let req = TestRequest::with_header(
"content-type", "application/json; charset=ISO-8859-2").finish();
assert_eq!(ISO_8859_2.name(), req.encoding().unwrap().name());
}
#[test]
fn test_encoding_error() {
let req = TestRequest::with_header(
"content-type", "applicatjson").finish();
assert_eq!(Some(ContentTypeError::ParseError), req.encoding().err());
let req = TestRequest::with_header(
"content-type", "application/json; charset=kkkttktk").finish();
assert_eq!(Some(ContentTypeError::UnknownEncoding), req.encoding().err());
}
#[test]
fn test_no_request_range_header() {
let req = HttpRequest::default();
let ranges = req.range(100).unwrap();
assert!(ranges.is_empty());
}
#[test]
fn test_request_range_header() {
let req = TestRequest::with_header(header::RANGE, "bytes=0-4").finish();
let ranges = req.range(100).unwrap();
assert_eq!(ranges.len(), 1);
assert_eq!(ranges[0].start, 0);
assert_eq!(ranges[0].length, 5);
}
#[test]
fn test_chunked() {
let req = HttpRequest::default();
assert!(!req.chunked().unwrap());
let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish();
assert!(req.chunked().unwrap());
let mut headers = HeaderMap::new();
let s = unsafe{str::from_utf8_unchecked(b"some va\xadscc\xacas0xsdasdlue".as_ref())};
headers.insert(header::TRANSFER_ENCODING,
header::HeaderValue::from_str(s).unwrap());
let req = HttpRequest::new(
Method::GET, Uri::from_str("/").unwrap(),
Version::HTTP_11, headers, None);
assert!(req.chunked().is_err());
}
impl PartialEq for UrlencodedError {
fn eq(&self, other: &UrlencodedError) -> bool {
match *self {
UrlencodedError::Chunked => match *other {
UrlencodedError::Chunked => true,
_ => false,
},
UrlencodedError::Overflow => match *other {
UrlencodedError::Overflow => true,
_ => false,
},
UrlencodedError::UnknownLength => match *other {
UrlencodedError::UnknownLength => true,
_ => false,
},
UrlencodedError::ContentType => match *other {
UrlencodedError::ContentType => true,
_ => false,
},
_ => false,
}
}
}
#[test]
fn test_urlencoded_error() {
let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish();
assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::Chunked);
let req = TestRequest::with_header(
header::CONTENT_TYPE, "application/x-www-form-urlencoded")
.header(header::CONTENT_LENGTH, "xxxx")
.finish();
assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::UnknownLength);
let req = TestRequest::with_header(
header::CONTENT_TYPE, "application/x-www-form-urlencoded")
.header(header::CONTENT_LENGTH, "1000000")
.finish();
assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::Overflow);
let req = TestRequest::with_header(
header::CONTENT_TYPE, "text/plain")
.header(header::CONTENT_LENGTH, "10")
.finish();
assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::ContentType);
}
#[test]
fn test_urlencoded() {
let mut req = TestRequest::with_header(
header::CONTENT_TYPE, "application/x-www-form-urlencoded")
.header(header::CONTENT_LENGTH, "11")
.finish();
req.payload_mut().unread_data(Bytes::from_static(b"hello=world"));
let result = req.urlencoded().poll().ok().unwrap();
assert_eq!(result, Async::Ready(
HashMap::from_iter(vec![("hello".to_owned(), "world".to_owned())])));
let mut req = TestRequest::with_header(
header::CONTENT_TYPE, "application/x-www-form-urlencoded; charset=utf-8")
.header(header::CONTENT_LENGTH, "11")
.finish();
req.payload_mut().unread_data(Bytes::from_static(b"hello=world"));
let result = req.urlencoded().poll().ok().unwrap();
assert_eq!(result, Async::Ready(
HashMap::from_iter(vec![("hello".to_owned(), "world".to_owned())])));
}
#[test]
fn test_message_body() {
let req = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").finish();
match req.body().poll().err().unwrap() {
PayloadError::UnknownLength => (),
_ => panic!("error"),
}
let req = TestRequest::with_header(header::CONTENT_LENGTH, "1000000").finish();
match req.body().poll().err().unwrap() {
PayloadError::Overflow => (),
_ => panic!("error"),
}
let mut req = HttpRequest::default();
req.payload_mut().unread_data(Bytes::from_static(b"test"));
match req.body().poll().ok().unwrap() {
Async::Ready(bytes) => assert_eq!(bytes, Bytes::from_static(b"test")),
_ => panic!("error"),
}
let mut req = HttpRequest::default();
req.payload_mut().unread_data(Bytes::from_static(b"11111111111111"));
match req.body().limit(5).poll().err().unwrap() {
PayloadError::Overflow => (),
_ => panic!("error"),
}
}
}

View file

@ -1,29 +1,25 @@
//! HTTP Request message related code. //! HTTP Request message related code.
use std::{str, fmt, mem}; use std::{io, cmp, str, fmt, mem};
use std::rc::Rc; use std::rc::Rc;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::collections::HashMap; use bytes::Bytes;
use bytes::{Bytes, BytesMut};
use cookie::Cookie; use cookie::Cookie;
use futures::{Async, Future, Stream, Poll}; use futures::{Async, Stream, Poll};
use http_range::HttpRange; use failure;
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};
use tokio_io::AsyncRead;
use info::ConnectionInfo; use info::ConnectionInfo;
use param::Params; use param::Params;
use router::Router; use router::Router;
use payload::{Payload, ReadAny}; use payload::Payload;
use json::JsonBody; use httpmessage::HttpMessage;
use multipart::Multipart; use helpers::SharedHttpInnerMessage;
use helpers::SharedHttpMessage; use error::{UrlGenerationError, CookieParseError, PayloadError};
use error::{ParseError, UrlGenerationError,
CookieParseError, HttpRangeError, PayloadError, UrlencodedError};
pub struct HttpMessage { pub struct HttpInnerMessage {
pub version: Version, pub version: Version,
pub method: Method, pub method: Method,
pub uri: Uri, pub uri: Uri,
@ -38,10 +34,10 @@ pub struct HttpMessage {
pub info: Option<ConnectionInfo<'static>>, pub info: Option<ConnectionInfo<'static>>,
} }
impl Default for HttpMessage { impl Default for HttpInnerMessage {
fn default() -> HttpMessage { fn default() -> HttpInnerMessage {
HttpMessage { HttpInnerMessage {
method: Method::GET, method: Method::GET,
uri: Uri::default(), uri: Uri::default(),
version: Version::HTTP_11, version: Version::HTTP_11,
@ -58,7 +54,7 @@ impl Default for HttpMessage {
} }
} }
impl HttpMessage { impl HttpInnerMessage {
/// Checks if a connection should be kept alive. /// Checks if a connection should be kept alive.
#[inline] #[inline]
@ -94,7 +90,7 @@ impl HttpMessage {
} }
/// An HTTP Request /// An HTTP Request
pub struct HttpRequest<S=()>(SharedHttpMessage, Option<Rc<S>>, Option<Router>); pub struct HttpRequest<S=()>(SharedHttpInnerMessage, Option<Rc<S>>, Option<Router>);
impl HttpRequest<()> { impl HttpRequest<()> {
/// Construct a new Request. /// Construct a new Request.
@ -103,17 +99,17 @@ impl HttpRequest<()> {
version: Version, headers: HeaderMap, payload: Option<Payload>) -> HttpRequest version: Version, headers: HeaderMap, payload: Option<Payload>) -> HttpRequest
{ {
HttpRequest( HttpRequest(
SharedHttpMessage::from_message(HttpMessage { SharedHttpInnerMessage::from_message(HttpInnerMessage {
method: method, method,
uri: uri, uri,
version: version, version,
headers: headers, headers,
payload,
params: Params::new(), params: Params::new(),
query: Params::new(), query: Params::new(),
query_loaded: false, query_loaded: false,
cookies: None, cookies: None,
addr: None, addr: None,
payload: payload,
extensions: Extensions::new(), extensions: Extensions::new(),
info: None, info: None,
}), }),
@ -124,7 +120,7 @@ impl HttpRequest<()> {
#[inline(always)] #[inline(always)]
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))] #[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
pub(crate) fn from_message(msg: SharedHttpMessage) -> HttpRequest { pub(crate) fn from_message(msg: SharedHttpInnerMessage) -> HttpRequest {
HttpRequest(msg, None, None) HttpRequest(msg, None, None)
} }
@ -141,6 +137,14 @@ impl HttpRequest<()> {
} }
} }
impl<S> HttpMessage for HttpRequest<S> {
#[inline]
fn headers(&self) -> &HeaderMap {
&self.as_ref().headers
}
}
impl<S> HttpRequest<S> { impl<S> HttpRequest<S> {
#[inline] #[inline]
@ -151,26 +155,26 @@ impl<S> HttpRequest<S> {
#[inline] #[inline]
/// Construct new http request without state. /// Construct new http request without state.
pub(crate) fn clone_without_state(&self) -> HttpRequest { pub(crate) fn without_state(&self) -> HttpRequest {
HttpRequest(self.0.clone(), None, None) HttpRequest(self.0.clone(), None, None)
} }
// get mutable reference for inner message /// get mutable reference for inner message
// mutable reference should not be returned as result for request's method /// mutable reference should not be returned as result for request's method
#[inline(always)] #[inline(always)]
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))] #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))]
pub(crate) fn as_mut(&self) -> &mut HttpMessage { pub(crate) fn as_mut(&self) -> &mut HttpInnerMessage {
self.0.get_mut() self.0.get_mut()
} }
#[inline(always)] #[inline(always)]
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))] #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))]
fn as_ref(&self) -> &HttpMessage { fn as_ref(&self) -> &HttpInnerMessage {
self.0.get_ref() self.0.get_ref()
} }
#[inline] #[inline]
pub(crate) fn get_inner(&mut self) -> &mut HttpMessage { pub(crate) fn get_inner(&mut self) -> &mut HttpInnerMessage {
self.as_mut() self.as_mut()
} }
@ -214,12 +218,6 @@ impl<S> HttpRequest<S> {
self.as_ref().version self.as_ref().version
} }
/// Read the Request Headers.
#[inline]
pub fn headers(&self) -> &HeaderMap {
&self.as_ref().headers
}
#[doc(hidden)] #[doc(hidden)]
#[inline] #[inline]
pub fn headers_mut(&mut self) -> &mut HeaderMap { pub fn headers_mut(&mut self) -> &mut HeaderMap {
@ -251,14 +249,14 @@ impl<S> HttpRequest<S> {
/// # /// #
/// fn index(req: HttpRequest) -> HttpResponse { /// fn index(req: HttpRequest) -> HttpResponse {
/// let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource /// let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource
/// HTTPOk.into() /// HttpOk.into()
/// } /// }
/// ///
/// fn main() { /// fn main() {
/// let app = Application::new() /// let app = Application::new()
/// .resource("/test/{one}/{two}/{three}", |r| { /// .resource("/test/{one}/{two}/{three}", |r| {
/// r.name("foo"); // <- set resource name, then it could be used in `url_for` /// r.name("foo"); // <- set resource name, then it could be used in `url_for`
/// r.method(Method::GET).f(|_| httpcodes::HTTPOk); /// r.method(Method::GET).f(|_| httpcodes::HttpOk);
/// }) /// })
/// .finish(); /// .finish();
/// } /// }
@ -367,7 +365,7 @@ impl<S> HttpRequest<S> {
/// Get mutable reference to request's Params. /// Get mutable reference to request's Params.
#[inline] #[inline]
pub(crate) fn match_info_mut(&mut self) -> &mut Params { pub fn match_info_mut(&mut self) -> &mut Params {
unsafe{ mem::transmute(&mut self.as_mut().params) } unsafe{ mem::transmute(&mut self.as_mut().params) }
} }
@ -376,30 +374,6 @@ impl<S> HttpRequest<S> {
self.as_ref().keep_alive() self.as_ref().keep_alive()
} }
/// Read the request content type. If request does not contain
/// *Content-Type* header, empty str get returned.
pub fn content_type(&self) -> &str {
if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) {
if let Ok(content_type) = content_type.to_str() {
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) {
@ -410,33 +384,8 @@ impl<S> HttpRequest<S> {
self.as_ref().method == Method::CONNECT self.as_ref().method == Method::CONNECT
} }
/// Check if request has chunked transfer encoding #[cfg(test)]
pub fn chunked(&self) -> Result<bool, ParseError> { pub(crate) fn payload(&self) -> &Payload {
if let Some(encodings) = self.headers().get(header::TRANSFER_ENCODING) {
if let Ok(s) = encodings.to_str() {
Ok(s.to_lowercase().contains("chunked"))
} else {
Err(ParseError::Header)
}
} else {
Ok(false)
}
}
/// Parses Range HTTP header string as per RFC 2616.
/// `size` is full size of response (file).
pub fn range(&self, size: u64) -> Result<Vec<HttpRange>, HttpRangeError> {
if let Some(range) = self.headers().get(header::RANGE) {
HttpRange::parse(unsafe{str::from_utf8_unchecked(range.as_bytes())}, size)
.map_err(|e| e.into())
} else {
Ok(Vec::new())
}
}
/// Returns reference to the associated http payload.
#[inline]
pub fn payload(&self) -> &Payload {
let msg = self.as_mut(); let msg = self.as_mut();
if msg.payload.is_none() { if msg.payload.is_none() {
msg.payload = Some(Payload::empty()); msg.payload = Some(Payload::empty());
@ -444,157 +393,21 @@ impl<S> HttpRequest<S> {
msg.payload.as_ref().unwrap() msg.payload.as_ref().unwrap()
} }
/// Returns mutable reference to the associated http payload. #[cfg(test)]
#[inline] pub(crate) fn payload_mut(&mut self) -> &mut Payload {
pub fn payload_mut(&mut self) -> &mut Payload {
let msg = self.as_mut(); let msg = self.as_mut();
if msg.payload.is_none() { if msg.payload.is_none() {
msg.payload = Some(Payload::empty()); msg.payload = Some(Payload::empty());
} }
msg.payload.as_mut().unwrap() msg.payload.as_mut().unwrap()
} }
/// Load request body.
///
/// By default only 256Kb payload reads to a memory, then `BAD REQUEST`
/// http response get returns to a peer. Use `RequestBody::limit()`
/// method to change upper limit.
///
/// ```rust
/// # extern crate bytes;
/// # extern crate actix_web;
/// # extern crate futures;
/// # #[macro_use] extern crate serde_derive;
/// use actix_web::*;
/// use bytes::Bytes;
/// use futures::future::Future;
///
/// fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
/// req.body() // <- get Body future
/// .limit(1024) // <- change max size of the body to a 1kb
/// .from_err()
/// .and_then(|bytes: Bytes| { // <- complete body
/// println!("==== BODY ==== {:?}", bytes);
/// Ok(httpcodes::HTTPOk.into())
/// }).responder()
/// }
/// # fn main() {}
/// ```
pub fn body(&self) -> RequestBody {
RequestBody::from_request(self)
}
/// Return stream to http payload processes as multipart.
///
/// Content-type: multipart/form-data;
///
/// ```rust
/// # extern crate actix;
/// # extern crate actix_web;
/// # extern crate env_logger;
/// # extern crate futures;
/// # use std::str;
/// # use actix::*;
/// # use actix_web::*;
/// # use futures::{Future, Stream};
/// # use futures::future::{ok, result, Either};
/// fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
/// req.multipart().from_err() // <- get multipart stream for current request
/// .and_then(|item| match item { // <- iterate over multipart items
/// multipart::MultipartItem::Field(field) => {
/// // Field in turn is stream of *Bytes* object
/// Either::A(field.from_err()
/// .map(|c| println!("-- CHUNK: \n{:?}", str::from_utf8(&c)))
/// .finish())
/// },
/// multipart::MultipartItem::Nested(mp) => {
/// // Or item could be nested Multipart stream
/// Either::B(ok(()))
/// }
/// })
/// .finish() // <- Stream::finish() combinator from actix
/// .map(|_| httpcodes::HTTPOk.into())
/// .responder()
/// }
/// # fn main() {}
/// ```
pub fn multipart(&mut self) -> Multipart {
Multipart::from_request(self)
}
/// Parse `application/x-www-form-urlencoded` encoded body.
/// Return `UrlEncoded` future. It resolves to a `HashMap<String, String>` which
/// contains decoded parameters.
///
/// Returns error:
///
/// * content type is not `application/x-www-form-urlencoded`
/// * transfer encoding is `chunked`.
/// * content-length is greater than 256k
///
/// ```rust
/// # extern crate actix_web;
/// # extern crate futures;
/// use actix_web::*;
/// use futures::future::{Future, ok};
///
/// fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
/// req.urlencoded() // <- get UrlEncoded future
/// .from_err()
/// .and_then(|params| { // <- url encoded parameters
/// println!("==== BODY ==== {:?}", params);
/// ok(httpcodes::HTTPOk.into())
/// })
/// .responder()
/// }
/// # fn main() {}
/// ```
pub fn urlencoded(&self) -> UrlEncoded {
UrlEncoded::from(self.payload().clone(),
self.headers(),
self.chunked().unwrap_or(false))
}
/// Parse `application/json` encoded body.
/// Return `JsonBody<T>` future. It resolves to a `T` value.
///
/// Returns error:
///
/// * content type is not `application/json`
/// * content length is greater than 256k
///
/// ```rust
/// # extern crate actix_web;
/// # extern crate futures;
/// # #[macro_use] extern crate serde_derive;
/// use actix_web::*;
/// use futures::future::{Future, ok};
///
/// #[derive(Deserialize, Debug)]
/// struct MyObj {
/// name: String,
/// }
///
/// fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
/// req.json() // <- get JsonBody future
/// .from_err()
/// .and_then(|val: MyObj| { // <- deserialized value
/// println!("==== BODY ==== {:?}", val);
/// Ok(httpcodes::HTTPOk.into())
/// }).responder()
/// }
/// # fn main() {}
/// ```
pub fn json<T: DeserializeOwned>(&self) -> JsonBody<S, T> {
JsonBody::from_request(self)
}
} }
impl Default for HttpRequest<()> { impl Default for HttpRequest<()> {
/// Construct default request /// Construct default request
fn default() -> HttpRequest { fn default() -> HttpRequest {
HttpRequest(SharedHttpMessage::default(), None, None) HttpRequest(SharedHttpInnerMessage::default(), None, None)
} }
} }
@ -604,6 +417,56 @@ impl<S> Clone for HttpRequest<S> {
} }
} }
impl<S> Stream for HttpRequest<S> {
type Item = Bytes;
type Error = PayloadError;
fn poll(&mut self) -> Poll<Option<Bytes>, PayloadError> {
let msg = self.as_mut();
if msg.payload.is_none() {
Ok(Async::Ready(None))
} else {
msg.payload.as_mut().unwrap().poll()
}
}
}
impl<S> io::Read for HttpRequest<S> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if self.as_mut().payload.is_some() {
match self.as_mut().payload.as_mut().unwrap().poll() {
Ok(Async::Ready(Some(mut b))) => {
let i = cmp::min(b.len(), buf.len());
buf.copy_from_slice(&b.split_to(i)[..i]);
if !b.is_empty() {
self.as_mut().payload.as_mut().unwrap().unread_data(b);
}
if i < buf.len() {
match self.read(&mut buf[i..]) {
Ok(n) => Ok(i + n),
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => Ok(i),
Err(e) => Err(e),
}
} else {
Ok(i)
}
}
Ok(Async::Ready(None)) => Ok(0),
Ok(Async::NotReady) =>
Err(io::Error::new(io::ErrorKind::WouldBlock, "Not ready")),
Err(e) =>
Err(io::Error::new(io::ErrorKind::Other, failure::Error::from(e).compat())),
}
} else {
Ok(0)
}
}
}
impl<S> AsyncRead for HttpRequest<S> {}
impl<S> fmt::Debug for HttpRequest<S> { impl<S> fmt::Debug for HttpRequest<S> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let res = write!(f, "\nHttpRequest {:?} {}:{}\n", let res = write!(f, "\nHttpRequest {:?} {}:{}\n",
@ -627,158 +490,10 @@ impl<S> fmt::Debug for HttpRequest<S> {
} }
} }
/// Future that resolves to a parsed urlencoded values.
pub struct UrlEncoded {
pl: Payload,
body: BytesMut,
error: Option<UrlencodedError>,
}
impl UrlEncoded {
pub fn from(pl: Payload, headers: &HeaderMap, chunked: bool) -> UrlEncoded {
let mut encoded = UrlEncoded {
pl: pl,
body: BytesMut::new(),
error: None
};
if chunked {
encoded.error = Some(UrlencodedError::Chunked);
} else if let Some(len) = headers.get(header::CONTENT_LENGTH) {
if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<u64>() {
if len > 262_144 {
encoded.error = Some(UrlencodedError::Overflow);
}
} else {
encoded.error = Some(UrlencodedError::UnknownLength);
}
} else {
encoded.error = Some(UrlencodedError::UnknownLength);
}
}
// check content type
if encoded.error.is_none() {
if let Some(content_type) = headers.get(header::CONTENT_TYPE) {
if let Ok(content_type) = content_type.to_str() {
if content_type.to_lowercase() == "application/x-www-form-urlencoded" {
return encoded
}
}
}
encoded.error = Some(UrlencodedError::ContentType);
return encoded
}
encoded
}
}
impl Future for UrlEncoded {
type Item = HashMap<String, String>;
type Error = UrlencodedError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(err) = self.error.take() {
return Err(err)
}
loop {
return match self.pl.poll() {
Ok(Async::NotReady) => Ok(Async::NotReady),
Ok(Async::Ready(None)) => {
let mut m = HashMap::new();
for (k, v) in form_urlencoded::parse(&self.body) {
m.insert(k.into(), v.into());
}
Ok(Async::Ready(m))
},
Ok(Async::Ready(Some(item))) => {
self.body.extend_from_slice(&item);
continue
},
Err(err) => Err(err.into()),
}
}
}
}
/// Future that resolves to a complete request body.
pub struct RequestBody {
pl: ReadAny,
body: BytesMut,
limit: usize,
req: Option<HttpRequest<()>>,
}
impl RequestBody {
/// Create `RequestBody` for request.
pub fn from_request<S>(req: &HttpRequest<S>) -> RequestBody {
let pl = req.payload().readany();
RequestBody {
pl: pl,
body: BytesMut::new(),
limit: 262_144,
req: Some(req.clone_without_state())
}
}
/// Change max size of payload. By default max size is 256Kb
pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit;
self
}
}
impl Future for RequestBody {
type Item = Bytes;
type Error = PayloadError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(req) = self.req.take() {
if let Some(len) = req.headers().get(header::CONTENT_LENGTH) {
if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<usize>() {
if len > self.limit {
return Err(PayloadError::Overflow);
}
} else {
return Err(PayloadError::UnknownLength);
}
} else {
return Err(PayloadError::UnknownLength);
}
}
}
loop {
return match self.pl.poll() {
Ok(Async::NotReady) => Ok(Async::NotReady),
Ok(Async::Ready(None)) => {
Ok(Async::Ready(self.body.take().freeze()))
},
Ok(Async::Ready(Some(chunk))) => {
if (self.body.len() + chunk.len()) > self.limit {
Err(PayloadError::Overflow)
} else {
self.body.extend_from_slice(&chunk);
continue
}
},
Err(err) => Err(err),
}
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use mime;
use http::{Uri, HttpTryFrom}; use http::{Uri, HttpTryFrom};
use std::str::FromStr;
use router::Pattern; use router::Pattern;
use resource::Resource; use resource::Resource;
use test::TestRequest; use test::TestRequest;
@ -791,31 +506,6 @@ 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_uri_mut() { fn test_uri_mut() {
let mut req = HttpRequest::default(); let mut req = HttpRequest::default();
@ -853,22 +543,6 @@ mod tests {
assert!(cookie.is_none()); assert!(cookie.is_none());
} }
#[test]
fn test_no_request_range_header() {
let req = HttpRequest::default();
let ranges = req.range(100).unwrap();
assert!(ranges.is_empty());
}
#[test]
fn test_request_range_header() {
let req = TestRequest::with_header(header::RANGE, "bytes=0-4").finish();
let ranges = req.range(100).unwrap();
assert_eq!(ranges.len(), 1);
assert_eq!(ranges[0].start, 0);
assert_eq!(ranges[0].length, 5);
}
#[test] #[test]
fn test_request_query() { fn test_request_query() {
let req = TestRequest::with_uri("/?id=test").finish(); let req = TestRequest::with_uri("/?id=test").finish();
@ -891,102 +565,6 @@ mod tests {
assert_eq!(req.match_info().get("key"), Some("value")); assert_eq!(req.match_info().get("key"), Some("value"));
} }
#[test]
fn test_chunked() {
let req = HttpRequest::default();
assert!(!req.chunked().unwrap());
let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish();
assert!(req.chunked().unwrap());
let mut headers = HeaderMap::new();
let s = unsafe{str::from_utf8_unchecked(b"some va\xadscc\xacas0xsdasdlue".as_ref())};
headers.insert(header::TRANSFER_ENCODING,
header::HeaderValue::from_str(s).unwrap());
let req = HttpRequest::new(
Method::GET, Uri::from_str("/").unwrap(),
Version::HTTP_11, headers, None);
assert!(req.chunked().is_err());
}
impl PartialEq for UrlencodedError {
fn eq(&self, other: &UrlencodedError) -> bool {
match *self {
UrlencodedError::Chunked => match *other {
UrlencodedError::Chunked => true,
_ => false,
},
UrlencodedError::Overflow => match *other {
UrlencodedError::Overflow => true,
_ => false,
},
UrlencodedError::UnknownLength => match *other {
UrlencodedError::UnknownLength => true,
_ => false,
},
UrlencodedError::ContentType => match *other {
UrlencodedError::ContentType => true,
_ => false,
},
_ => false,
}
}
}
#[test]
fn test_urlencoded_error() {
let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish();
assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::Chunked);
let req = TestRequest::with_header(
header::CONTENT_TYPE, "application/x-www-form-urlencoded")
.header(header::CONTENT_LENGTH, "xxxx")
.finish();
assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::UnknownLength);
let req = TestRequest::with_header(
header::CONTENT_TYPE, "application/x-www-form-urlencoded")
.header(header::CONTENT_LENGTH, "1000000")
.finish();
assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::Overflow);
let req = TestRequest::with_header(
header::CONTENT_TYPE, "text/plain")
.header(header::CONTENT_LENGTH, "10")
.finish();
assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::ContentType);
}
#[test]
fn test_request_body() {
let req = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").finish();
match req.body().poll().err().unwrap() {
PayloadError::UnknownLength => (),
_ => panic!("error"),
}
let req = TestRequest::with_header(header::CONTENT_LENGTH, "1000000").finish();
match req.body().poll().err().unwrap() {
PayloadError::Overflow => (),
_ => panic!("error"),
}
let mut req = HttpRequest::default();
req.payload_mut().unread_data(Bytes::from_static(b"test"));
match req.body().poll().ok().unwrap() {
Async::Ready(bytes) => assert_eq!(bytes, Bytes::from_static(b"test")),
_ => panic!("error"),
}
let mut req = HttpRequest::default();
req.payload_mut().unread_data(Bytes::from_static(b"11111111111111"));
match req.body().limit(5).poll().err().unwrap() {
PayloadError::Overflow => (),
_ => panic!("error"),
}
}
#[test] #[test]
fn test_url_for() { fn test_url_for() {
let req = TestRequest::with_header(header::HOST, "www.rust-lang.org") let req = TestRequest::with_header(header::HOST, "www.rust-lang.org")

View file

@ -252,7 +252,7 @@ impl HttpResponseBuilder {
/// use http::header; /// use http::header;
/// ///
/// fn index(req: HttpRequest) -> Result<HttpResponse> { /// fn index(req: HttpRequest) -> Result<HttpResponse> {
/// Ok(HTTPOk.build() /// Ok(HttpOk.build()
/// .header("X-TEST", "value") /// .header("X-TEST", "value")
/// .header(header::CONTENT_TYPE, "application/json") /// .header(header::CONTENT_TYPE, "application/json")
/// .finish()?) /// .finish()?)
@ -372,7 +372,7 @@ impl HttpResponseBuilder {
/// use actix_web::headers::Cookie; /// use actix_web::headers::Cookie;
/// ///
/// fn index(req: HttpRequest) -> Result<HttpResponse> { /// fn index(req: HttpRequest) -> Result<HttpResponse> {
/// Ok(HTTPOk.build() /// Ok(HttpOk.build()
/// .cookie( /// .cookie(
/// Cookie::build("name", "value") /// Cookie::build("name", "value")
/// .domain("www.rust-lang.org") /// .domain("www.rust-lang.org")
@ -659,11 +659,11 @@ impl InnerHttpResponse {
#[inline] #[inline]
fn new(status: StatusCode, body: Body) -> InnerHttpResponse { fn new(status: StatusCode, body: Body) -> InnerHttpResponse {
InnerHttpResponse { InnerHttpResponse {
status,
body,
version: None, version: None,
headers: HeaderMap::with_capacity(16), headers: HeaderMap::with_capacity(16),
status: status,
reason: None, reason: None,
body: body,
chunked: None, chunked: None,
encoding: None, encoding: None,
connection_type: None, connection_type: None,
@ -753,7 +753,7 @@ mod tests {
Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None);
let cookies = req.cookies().unwrap(); let cookies = req.cookies().unwrap();
let resp = httpcodes::HTTPOk let resp = httpcodes::HttpOk
.build() .build()
.cookie(headers::Cookie::build("name", "value") .cookie(headers::Cookie::build("name", "value")
.domain("www.rust-lang.org") .domain("www.rust-lang.org")

View file

@ -1,5 +1,6 @@
use std::str::FromStr; use std::str::FromStr;
use http::header::{self, HeaderName}; use http::header::{self, HeaderName};
use httpmessage::HttpMessage;
use httprequest::HttpRequest; use httprequest::HttpRequest;
const X_FORWARDED_FOR: &str = "X-FORWARDED-FOR"; const X_FORWARDED_FOR: &str = "X-FORWARDED-FOR";
@ -110,8 +111,8 @@ impl<'a> ConnectionInfo<'a> {
ConnectionInfo { ConnectionInfo {
scheme: scheme.unwrap_or("http"), scheme: scheme.unwrap_or("http"),
host: host.unwrap_or("localhost"), host: host.unwrap_or("localhost"),
remote: remote, remote,
peer: peer, peer,
} }
} }

View file

@ -1,4 +1,4 @@
use bytes::BytesMut; use bytes::{Bytes, BytesMut};
use futures::{Poll, Future, Stream}; use futures::{Poll, Future, Stream};
use http::header::CONTENT_LENGTH; use http::header::CONTENT_LENGTH;
@ -6,8 +6,9 @@ use serde_json;
use serde::Serialize; use serde::Serialize;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use error::{Error, JsonPayloadError}; use error::{Error, JsonPayloadError, PayloadError};
use handler::Responder; use handler::Responder;
use httpmessage::HttpMessage;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
@ -54,6 +55,9 @@ impl<T: Serialize> Responder for Json<T> {
/// * content type is not `application/json` /// * content type is not `application/json`
/// * content length is greater than 256k /// * content length is greater than 256k
/// ///
///
/// # Server example
///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// # extern crate futures; /// # extern crate futures;
@ -71,25 +75,25 @@ impl<T: Serialize> Responder for Json<T> {
/// .from_err() /// .from_err()
/// .and_then(|val: MyObj| { // <- deserialized value /// .and_then(|val: MyObj| { // <- deserialized value
/// println!("==== BODY ==== {:?}", val); /// println!("==== BODY ==== {:?}", val);
/// Ok(httpcodes::HTTPOk.into()) /// Ok(httpcodes::HttpOk.into())
/// }).responder() /// }).responder()
/// } /// }
/// # fn main() {} /// # fn main() {}
/// ``` /// ```
pub struct JsonBody<S, T: DeserializeOwned>{ pub struct JsonBody<T, U: DeserializeOwned>{
limit: usize, limit: usize,
ct: &'static str, ct: &'static str,
req: Option<HttpRequest<S>>, req: Option<T>,
fut: Option<Box<Future<Item=T, Error=JsonPayloadError>>>, fut: Option<Box<Future<Item=U, Error=JsonPayloadError>>>,
} }
impl<S, T: DeserializeOwned> JsonBody<S, T> { impl<T, U: DeserializeOwned> JsonBody<T, U> {
/// Create `JsonBody` for request. /// Create `JsonBody` for request.
pub fn from_request(req: &HttpRequest<S>) -> Self { pub fn new(req: T) -> Self {
JsonBody{ JsonBody{
limit: 262_144, limit: 262_144,
req: Some(req.clone()), req: Some(req),
fut: None, fut: None,
ct: "application/json", ct: "application/json",
} }
@ -111,11 +115,13 @@ impl<S, T: DeserializeOwned> JsonBody<S, T> {
} }
} }
impl<S, T: DeserializeOwned + 'static> Future for JsonBody<S, T> { impl<T, U: DeserializeOwned + 'static> Future for JsonBody<T, U>
type Item = T; where T: HttpMessage + Stream<Item=Bytes, Error=PayloadError> + 'static
{
type Item = U;
type Error = JsonPayloadError; type Error = JsonPayloadError;
fn poll(&mut self) -> Poll<T, JsonPayloadError> { fn poll(&mut self) -> Poll<U, JsonPayloadError> {
if let Some(req) = self.req.take() { if let Some(req) = self.req.take() {
if let Some(len) = req.headers().get(CONTENT_LENGTH) { if let Some(len) = req.headers().get(CONTENT_LENGTH) {
if let Ok(s) = len.to_str() { if let Ok(s) = len.to_str() {
@ -134,8 +140,7 @@ impl<S, T: DeserializeOwned + 'static> Future for JsonBody<S, T> {
} }
let limit = self.limit; let limit = self.limit;
let fut = req.payload().readany() let fut = req.from_err()
.from_err()
.fold(BytesMut::new(), move |mut body, chunk| { .fold(BytesMut::new(), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit { if (body.len() + chunk.len()) > limit {
Err(JsonPayloadError::Overflow) Err(JsonPayloadError::Overflow)
@ -144,7 +149,7 @@ impl<S, T: DeserializeOwned + 'static> Future for JsonBody<S, T> {
Ok(body) Ok(body)
} }
}) })
.and_then(|body| Ok(serde_json::from_slice::<T>(&body)?)); .and_then(|body| Ok(serde_json::from_slice::<U>(&body)?));
self.fut = Some(Box::new(fut)); self.fut = Some(Box::new(fut));
} }
@ -189,27 +194,31 @@ mod tests {
#[test] #[test]
fn test_json_body() { fn test_json_body() {
let mut req = HttpRequest::default(); let req = HttpRequest::default();
let mut json = req.json::<MyObject>(); let mut json = req.json::<MyObject>();
assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType);
let mut json = req.json::<MyObject>().content_type("text/json"); let mut req = HttpRequest::default();
req.headers_mut().insert(header::CONTENT_TYPE, req.headers_mut().insert(header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json")); header::HeaderValue::from_static("application/json"));
let mut json = req.json::<MyObject>().content_type("text/json");
assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType);
let mut json = req.json::<MyObject>().limit(100); let mut req = HttpRequest::default();
req.headers_mut().insert(header::CONTENT_TYPE, req.headers_mut().insert(header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json")); header::HeaderValue::from_static("application/json"));
req.headers_mut().insert(header::CONTENT_LENGTH, req.headers_mut().insert(header::CONTENT_LENGTH,
header::HeaderValue::from_static("10000")); header::HeaderValue::from_static("10000"));
let mut json = req.json::<MyObject>().limit(100);
assert_eq!(json.poll().err().unwrap(), JsonPayloadError::Overflow); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::Overflow);
let mut req = HttpRequest::default();
req.headers_mut().insert(header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"));
req.headers_mut().insert(header::CONTENT_LENGTH, req.headers_mut().insert(header::CONTENT_LENGTH,
header::HeaderValue::from_static("16")); header::HeaderValue::from_static("16"));
req.payload_mut().unread_data(Bytes::from_static(b"{\"name\": \"test\"}")); req.payload_mut().unread_data(Bytes::from_static(b"{\"name\": \"test\"}"));
let mut json = req.json::<MyObject>(); let mut json = req.json::<MyObject>();
assert_eq!(json.poll().ok().unwrap(), Async::Ready(MyObject{name: "test".to_owned()})); assert_eq!(json.poll().ok().unwrap(), Async::Ready(MyObject{name: "test".to_owned()}));
} }
} }

View file

@ -1,4 +1,4 @@
//! Actix web is a small, fast, pragmatic, open source rust web framework. //! Actix web is a small, pragmatic, extremely fast, web framework for Rust.
//! //!
//! ```rust //! ```rust
//! use actix_web::*; //! use actix_web::*;
@ -32,19 +32,20 @@
//! * Supported *HTTP/1.x* and *HTTP/2.0* protocols //! * Supported *HTTP/1.x* and *HTTP/2.0* protocols
//! * Streaming and pipelining //! * Streaming and pipelining
//! * Keep-alive and slow requests handling //! * Keep-alive and slow requests handling
//! * `WebSockets` //! * `WebSockets` server/client
//! * Transparent content compression/decompression (br, gzip, deflate) //! * Transparent content compression/decompression (br, gzip, deflate)
//! * Configurable request routing //! * Configurable request routing
//! * Multipart streams
//! * Middlewares (`Logger`, `Session`, `DefaultHeaders`)
//! * Graceful server shutdown //! * Graceful server shutdown
//! * Built on top of [Actix](https://github.com/actix/actix). //! * Multipart streams
//! * SSL support with openssl or native-tls
//! * Middlewares (`Logger`, `Session`, `CORS`, `DefaultHeaders`)
//! * Built on top of [Actix actor framework](https://github.com/actix/actix).
#![cfg_attr(actix_nightly, feature( #![cfg_attr(actix_nightly, feature(
specialization, // for impl ErrorResponse for std::error::Error specialization, // for impl ErrorResponse for std::error::Error
))] ))]
#![cfg_attr(feature = "cargo-clippy", allow( #![cfg_attr(feature = "cargo-clippy", allow(
decimal_literal_representation,))] decimal_literal_representation,suspicious_arithmetic_impl,))]
#[macro_use] #[macro_use]
extern crate log; extern crate log;
@ -77,6 +78,7 @@ extern crate serde;
extern crate serde_json; extern crate serde_json;
extern crate flate2; extern crate flate2;
extern crate brotli2; extern crate brotli2;
extern crate encoding;
extern crate percent_encoding; extern crate percent_encoding;
extern crate smallvec; extern crate smallvec;
extern crate num_cpus; extern crate num_cpus;
@ -100,16 +102,18 @@ extern crate tokio_openssl;
mod application; mod application;
mod body; mod body;
mod context; mod context;
mod handler;
mod helpers; mod helpers;
mod httpmessage;
mod httprequest; mod httprequest;
mod httpresponse; mod httpresponse;
mod info; mod info;
mod json; mod json;
mod route; mod route;
mod router; mod router;
mod param;
mod resource; mod resource;
mod handler; mod param;
mod payload;
mod pipeline; mod pipeline;
pub mod client; pub mod client;
@ -121,12 +125,12 @@ pub mod multipart;
pub mod middleware; pub mod middleware;
pub mod pred; pub mod pred;
pub mod test; pub mod test;
pub mod payload;
pub mod server; 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 httpmessage::HttpMessage;
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};
@ -185,11 +189,12 @@ pub mod dev {
//! ``` //! ```
pub use body::BodyStream; pub use body::BodyStream;
pub use context::Drain;
pub use info::ConnectionInfo; pub use info::ConnectionInfo;
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 param::{FromParam, Params}; pub use param::{FromParam, Params};
pub use httprequest::{UrlEncoded, RequestBody}; pub use httpmessage::{UrlEncoded, MessageBody};
pub use httpresponse::HttpResponseBuilder; pub use httpresponse::HttpResponseBuilder;
} }

View file

@ -38,8 +38,8 @@
//! .max_age(3600) //! .max_age(3600)
//! .finish().expect("Can not create CORS middleware") //! .finish().expect("Can not create CORS middleware")
//! .register(r); // <- Register CORS middleware //! .register(r); // <- Register CORS middleware
//! r.method(Method::GET).f(|_| httpcodes::HTTPOk); //! r.method(Method::GET).f(|_| httpcodes::HttpOk);
//! r.method(Method::HEAD).f(|_| httpcodes::HTTPMethodNotAllowed); //! r.method(Method::HEAD).f(|_| httpcodes::HttpMethodNotAllowed);
//! }) //! })
//! .finish(); //! .finish();
//! } //! }
@ -55,9 +55,10 @@ use http::header::{self, HeaderName, HeaderValue};
use error::{Result, ResponseError}; use error::{Result, ResponseError};
use resource::Resource; use resource::Resource;
use httpmessage::HttpMessage;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
use httpcodes::{HTTPOk, HTTPBadRequest}; use httpcodes::{HttpOk, HttpBadRequest};
use middleware::{Middleware, Response, Started}; use middleware::{Middleware, Response, Started};
/// A set of errors that can occur during processing CORS /// A set of errors that can occur during processing CORS
@ -109,7 +110,7 @@ pub enum CorsBuilderError {
impl ResponseError for CorsError { impl ResponseError for CorsError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
HTTPBadRequest.build().body(format!("{}", self)).unwrap() HttpBadRequest.build().body(format!("{}", self)).unwrap()
} }
} }
@ -218,7 +219,7 @@ impl Cors {
/// method, but in that case *Cors* middleware wont be able to handle *OPTIONS* /// method, but in that case *Cors* middleware wont be able to handle *OPTIONS*
/// requests. /// requests.
pub fn register<S: 'static>(self, resource: &mut Resource<S>) { pub fn register<S: 'static>(self, resource: &mut Resource<S>) {
resource.method(Method::OPTIONS).h(HTTPOk); resource.method(Method::OPTIONS).h(HttpOk);
resource.middleware(self); resource.middleware(self);
} }
@ -306,7 +307,7 @@ impl<S> Middleware<S> for Cors {
}; };
Ok(Started::Response( Ok(Started::Response(
HTTPOk.build() HttpOk.build()
.if_some(self.max_age.as_ref(), |max_age, resp| { .if_some(self.max_age.as_ref(), |max_age, resp| {
let _ = resp.header( let _ = resp.header(
header::ACCESS_CONTROL_MAX_AGE, format!("{}", max_age).as_str());}) header::ACCESS_CONTROL_MAX_AGE, format!("{}", max_age).as_str());})
@ -822,7 +823,7 @@ mod tests {
.method(Method::OPTIONS) .method(Method::OPTIONS)
.finish(); .finish();
let resp: HttpResponse = HTTPOk.into(); let resp: HttpResponse = HttpOk.into();
let resp = cors.response(&mut req, resp).unwrap().response(); let resp = cors.response(&mut req, resp).unwrap().response();
assert_eq!( assert_eq!(
&b"*"[..], &b"*"[..],
@ -831,7 +832,7 @@ mod tests {
&b"Origin"[..], &b"Origin"[..],
resp.headers().get(header::VARY).unwrap().as_bytes()); resp.headers().get(header::VARY).unwrap().as_bytes());
let resp: HttpResponse = HTTPOk.build() let resp: HttpResponse = HttpOk.build()
.header(header::VARY, "Accept") .header(header::VARY, "Accept")
.finish().unwrap(); .finish().unwrap();
let resp = cors.response(&mut req, resp).unwrap().response(); let resp = cors.response(&mut req, resp).unwrap().response();
@ -843,7 +844,7 @@ mod tests {
.disable_vary_header() .disable_vary_header()
.allowed_origin("https://www.example.com") .allowed_origin("https://www.example.com")
.finish().unwrap(); .finish().unwrap();
let resp: HttpResponse = HTTPOk.into(); let resp: HttpResponse = HttpOk.into();
let resp = cors.response(&mut req, resp).unwrap().response(); let resp = cors.response(&mut req, resp).unwrap().response();
assert_eq!( assert_eq!(
&b"https://www.example.com"[..], &b"https://www.example.com"[..],

266
src/middleware/csrf.rs Normal file
View file

@ -0,0 +1,266 @@
//! A filter for cross-site request forgery (CSRF).
//!
//! This middleware is stateless and [based on request
//! headers](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Verifying_Same_Origin_with_Standard_Headers).
//!
//! By default requests are allowed only if one of these is true:
//!
//! * The request method is safe (`GET`, `HEAD`, `OPTIONS`). It is the
//! applications responsibility to ensure these methods cannot be used to
//! execute unwanted actions. Note that upgrade requests for websockets are
//! also considered safe.
//! * The `Origin` header (added automatically by the browser) matches one
//! of the allowed origins.
//! * There is no `Origin` header but the `Referer` header matches one of
//! the allowed origins.
//!
//! Use [`CsrfFilterBuilder::allow_xhr()`](struct.CsrfFilterBuilder.html#method.allow_xhr)
//! if you want to allow requests with unsafe methods via
//! [CORS](../cors/struct.Cors.html).
//!
//! # Example
//!
//! ```
//! # extern crate actix_web;
//! # use actix_web::*;
//!
//! use actix_web::middleware::csrf;
//!
//! fn handle_post(_req: HttpRequest) -> &'static str {
//! "This action should only be triggered with requests from the same site"
//! }
//!
//! fn main() {
//! let app = Application::new()
//! .middleware(
//! csrf::CsrfFilter::build()
//! .allowed_origin("https://www.example.com")
//! .finish())
//! .resource("/", |r| {
//! r.method(Method::GET).f(|_| httpcodes::HttpOk);
//! r.method(Method::POST).f(handle_post);
//! })
//! .finish();
//! }
//! ```
//!
//! In this example the entire application is protected from CSRF.
use std::borrow::Cow;
use std::collections::HashSet;
use bytes::Bytes;
use error::{Result, ResponseError};
use http::{HeaderMap, HttpTryFrom, Uri, header};
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use httpmessage::HttpMessage;
use httpcodes::HttpForbidden;
use middleware::{Middleware, Started};
/// Potential cross-site request forgery detected.
#[derive(Debug, Fail)]
pub enum CsrfError {
/// The HTTP request header `Origin` was required but not provided.
#[fail(display="Origin header required")]
MissingOrigin,
/// The HTTP request header `Origin` could not be parsed correctly.
#[fail(display="Could not parse Origin header")]
BadOrigin,
/// The cross-site request was denied.
#[fail(display="Cross-site request denied")]
CsrDenied,
}
impl ResponseError for CsrfError {
fn error_response(&self) -> HttpResponse {
HttpForbidden.build().body(self.to_string()).unwrap()
}
}
fn uri_origin(uri: &Uri) -> Option<String> {
match (uri.scheme_part(), uri.host(), uri.port()) {
(Some(scheme), Some(host), Some(port)) => {
Some(format!("{}://{}:{}", scheme, host, port))
}
(Some(scheme), Some(host), None) => {
Some(format!("{}://{}", scheme, host))
}
_ => None
}
}
fn origin(headers: &HeaderMap) -> Option<Result<Cow<str>, CsrfError>> {
headers.get(header::ORIGIN)
.map(|origin| {
origin
.to_str()
.map_err(|_| CsrfError::BadOrigin)
.map(|o| o.into())
})
.or_else(|| {
headers.get(header::REFERER)
.map(|referer| {
Uri::try_from(Bytes::from(referer.as_bytes()))
.ok()
.as_ref()
.and_then(uri_origin)
.ok_or(CsrfError::BadOrigin)
.map(|o| o.into())
})
})
}
/// A middleware that filters cross-site requests.
pub struct CsrfFilter {
origins: HashSet<String>,
allow_xhr: bool,
allow_missing_origin: bool,
}
impl CsrfFilter {
/// Start building a `CsrfFilter`.
pub fn build() -> CsrfFilterBuilder {
CsrfFilterBuilder {
cors: CsrfFilter {
origins: HashSet::new(),
allow_xhr: false,
allow_missing_origin: false,
}
}
}
fn validate<S>(&self, req: &mut HttpRequest<S>) -> Result<(), CsrfError> {
if req.method().is_safe() || (self.allow_xhr && req.headers().contains_key("x-requested-with")) {
Ok(())
} else if let Some(header) = origin(req.headers()) {
match header {
Ok(ref origin) if self.origins.contains(origin.as_ref()) => Ok(()),
Ok(_) => Err(CsrfError::CsrDenied),
Err(err) => Err(err),
}
} else if self.allow_missing_origin {
Ok(())
} else {
Err(CsrfError::MissingOrigin)
}
}
}
impl<S> Middleware<S> for CsrfFilter {
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
self.validate(req)?;
Ok(Started::Done)
}
}
/// Used to build a `CsrfFilter`.
///
/// To construct a CSRF filter:
///
/// 1. Call [`CsrfFilter::build`](struct.CsrfFilter.html#method.build) to
/// start building.
/// 2. [Add](struct.CsrfFilterBuilder.html#method.allowed_origin) allowed
/// origins.
/// 3. Call [finish](struct.CsrfFilterBuilder.html#method.finish) to retrieve
/// the constructed filter.
///
/// # Example
///
/// ```
/// use actix_web::middleware::csrf;
///
/// let csrf = csrf::CsrfFilter::build()
/// .allowed_origin("https://www.example.com")
/// .finish();
/// ```
pub struct CsrfFilterBuilder {
cors: CsrfFilter,
}
impl CsrfFilterBuilder {
/// Add an origin that is allowed to make requests. Will be verified
/// against the `Origin` request header.
pub fn allowed_origin(mut self, origin: &str) -> CsrfFilterBuilder {
self.cors.origins.insert(origin.to_owned());
self
}
/// Allow all requests with an `X-Requested-With` header.
///
/// A cross-site attacker should not be able to send requests with custom
/// headers unless a CORS policy whitelists them. Therefore it should be
/// safe to allow requests with an `X-Requested-With` header (added
/// automatically by many JavaScript libraries).
///
/// This is disabled by default, because in Safari it is possible to
/// circumvent this using redirects and Flash.
///
/// Use this method to enable more lax filtering.
pub fn allow_xhr(mut self) -> CsrfFilterBuilder {
self.cors.allow_xhr = true;
self
}
/// Allow requests if the expected `Origin` header is missing (and
/// there is no `Referer` to fall back on).
///
/// The filter is conservative by default, but it should be safe to allow
/// missing `Origin` headers because a cross-site attacker cannot prevent
/// the browser from sending `Origin` on unsafe requests.
pub fn allow_missing_origin(mut self) -> CsrfFilterBuilder {
self.cors.allow_missing_origin = true;
self
}
/// Finishes building the `CsrfFilter` instance.
pub fn finish(self) -> CsrfFilter {
self.cors
}
}
#[cfg(test)]
mod tests {
use super::*;
use http::Method;
use test::TestRequest;
#[test]
fn test_safe() {
let csrf = CsrfFilter::build()
.allowed_origin("https://www.example.com")
.finish();
let mut req = TestRequest::with_header("Origin", "https://www.w3.org")
.method(Method::HEAD)
.finish();
assert!(csrf.start(&mut req).is_ok());
}
#[test]
fn test_csrf() {
let csrf = CsrfFilter::build()
.allowed_origin("https://www.example.com")
.finish();
let mut req = TestRequest::with_header("Origin", "https://www.w3.org")
.method(Method::POST)
.finish();
assert!(csrf.start(&mut req).is_err());
}
#[test]
fn test_referer() {
let csrf = CsrfFilter::build()
.allowed_origin("https://www.example.com")
.finish();
let mut req = TestRequest::with_header("Referer", "https://www.example.com/some/path?query=param")
.method(Method::POST)
.finish();
assert!(csrf.start(&mut req).is_ok());
}
}

View file

@ -22,8 +22,8 @@ use middleware::{Response, Middleware};
/// .header("X-Version", "0.2") /// .header("X-Version", "0.2")
/// .finish()) /// .finish())
/// .resource("/test", |r| { /// .resource("/test", |r| {
/// r.method(Method::GET).f(|_| httpcodes::HTTPOk); /// r.method(Method::GET).f(|_| httpcodes::HttpOk);
/// r.method(Method::HEAD).f(|_| httpcodes::HTTPMethodNotAllowed); /// r.method(Method::HEAD).f(|_| httpcodes::HttpMethodNotAllowed);
/// }) /// })
/// .finish(); /// .finish();
/// } /// }
@ -95,7 +95,7 @@ impl DefaultHeadersBuilder {
/// Finishes building and returns the built `DefaultHeaders` middleware. /// Finishes building and returns the built `DefaultHeaders` middleware.
pub fn finish(&mut self) -> DefaultHeaders { pub fn finish(&mut self) -> DefaultHeaders {
let headers = self.headers.take().expect("cannot reuse middleware builder"); let headers = self.headers.take().expect("cannot reuse middleware builder");
DefaultHeaders{ ct: self.ct, headers: headers } DefaultHeaders{ ct: self.ct, headers }
} }
} }

View file

@ -8,6 +8,7 @@ use time;
use regex::Regex; use regex::Regex;
use error::Result; use error::Result;
use httpmessage::HttpMessage;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
use middleware::{Middleware, Started, Finished}; use middleware::{Middleware, Started, Finished};

View file

@ -9,6 +9,7 @@ mod logger;
mod session; mod session;
mod defaultheaders; mod defaultheaders;
pub mod cors; pub mod cors;
pub mod csrf;
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,

View file

@ -1,6 +1,3 @@
#![allow(dead_code, unused_imports, unused_variables)]
use std::any::Any;
use std::rc::Rc; use std::rc::Rc;
use std::sync::Arc; use std::sync::Arc;
use std::marker::PhantomData; use std::marker::PhantomData;
@ -49,8 +46,7 @@ impl<S> RequestSession for HttpRequest<S> {
return Session(s.0.as_mut()) return Session(s.0.as_mut())
} }
} }
//Session(&mut DUMMY) Session(unsafe{&mut DUMMY})
unreachable!()
} }
} }
@ -90,7 +86,7 @@ impl<'a> Session<'a> {
} }
/// Set a `value` from the session. /// Set a `value` from the session.
pub fn set<T: Serialize>(&'a mut self, key: &str, value: T) -> Result<()> { pub fn set<T: Serialize>(&mut self, key: &str, value: T) -> Result<()> {
self.0.set(key, serde_json::to_string(&value)?); self.0.set(key, serde_json::to_string(&value)?);
Ok(()) Ok(())
} }
@ -195,15 +191,13 @@ pub trait SessionBackend<S>: Sized + 'static {
/// Dummy session impl, does not do anything /// Dummy session impl, does not do anything
struct DummySessionImpl; struct DummySessionImpl;
static DUMMY: DummySessionImpl = DummySessionImpl; static mut DUMMY: DummySessionImpl = DummySessionImpl;
impl SessionImpl for DummySessionImpl { impl SessionImpl for DummySessionImpl {
fn get(&self, key: &str) -> Option<&str> { fn get(&self, _: &str) -> Option<&str> { None }
None fn set(&mut self, _: &str, _: String) {}
} fn remove(&mut self, _: &str) {}
fn set(&mut self, key: &str, value: String) {}
fn remove(&mut self, key: &str) {}
fn clear(&mut self) {} fn clear(&mut self) {}
fn write(&self, resp: HttpResponse) -> Result<Response> { fn write(&self, resp: HttpResponse) -> Result<Response> {
Ok(Response::Done(resp)) Ok(Response::Done(resp))
@ -377,8 +371,8 @@ impl<S> SessionBackend<S> for CookieSessionBackend {
FutOk( FutOk(
CookieSession { CookieSession {
changed: false, changed: false,
state: state,
inner: Rc::clone(&self.0), inner: Rc::clone(&self.0),
state,
}) })
} }
} }

View file

@ -9,12 +9,11 @@ use httparse;
use bytes::Bytes; use bytes::Bytes;
use http::HttpTryFrom; use http::HttpTryFrom;
use http::header::{self, HeaderMap, HeaderName, HeaderValue}; use http::header::{self, HeaderMap, HeaderName, HeaderValue};
use futures::{Async, Future, Stream, Poll}; use futures::{Async, Stream, Poll};
use futures::task::{Task, current as current_task}; use futures::task::{Task, current as current_task};
use error::{ParseError, PayloadError, MultipartError}; use error::{ParseError, PayloadError, MultipartError};
use payload::Payload; use payload::PayloadHelper;
use httprequest::HttpRequest;
const MAX_HEADERS: usize = 32; const MAX_HEADERS: usize = 32;
@ -24,27 +23,24 @@ const MAX_HEADERS: usize = 32;
/// Stream implementation. /// Stream implementation.
/// `MultipartItem::Field` contains multipart field. `MultipartItem::Multipart` /// `MultipartItem::Field` contains multipart field. `MultipartItem::Multipart`
/// is used for nested multipart streams. /// is used for nested multipart streams.
#[derive(Debug)] pub struct Multipart<S> {
pub struct Multipart {
safety: Safety, safety: Safety,
error: Option<MultipartError>, error: Option<MultipartError>,
inner: Option<Rc<RefCell<InnerMultipart>>>, inner: Option<Rc<RefCell<InnerMultipart<S>>>>,
} }
/// ///
#[derive(Debug)] pub enum MultipartItem<S> {
pub enum MultipartItem {
/// Multipart field /// Multipart field
Field(Field), Field(Field<S>),
/// Nested multipart stream /// Nested multipart stream
Nested(Multipart), Nested(Multipart<S>),
} }
#[derive(Debug)] enum InnerMultipartItem<S> {
enum InnerMultipartItem {
None, None,
Field(Rc<RefCell<InnerField>>), Field(Rc<RefCell<InnerField<S>>>),
Multipart(Rc<RefCell<InnerMultipart>>), Multipart(Rc<RefCell<InnerMultipart<S>>>),
} }
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
@ -59,57 +55,14 @@ enum InnerState {
Headers, Headers,
} }
#[derive(Debug)] struct InnerMultipart<S> {
struct InnerMultipart { payload: PayloadRef<S>,
payload: PayloadRef,
boundary: String, boundary: String,
state: InnerState, state: InnerState,
item: InnerMultipartItem, item: InnerMultipartItem<S>,
} }
impl Multipart { impl Multipart<()> {
/// Create multipart instance for boundary.
pub fn new(boundary: String, payload: Payload) -> Multipart {
Multipart {
error: None,
safety: Safety::new(),
inner: Some(Rc::new(RefCell::new(
InnerMultipart {
payload: PayloadRef::new(payload),
boundary: boundary,
state: InnerState::FirstBoundary,
item: InnerMultipartItem::None,
})))
}
}
/// Create multipart instance for request.
pub fn from_request<S>(req: &mut HttpRequest<S>) -> Multipart {
match Multipart::boundary(req.headers()) {
Ok(boundary) => Multipart::new(boundary, req.payload().clone()),
Err(err) =>
Multipart {
error: Some(err),
safety: Safety::new(),
inner: None,
}
}
}
// /// Create multipart instance for client response.
// pub fn from_response(resp: &mut ClientResponse) -> Multipart {
// match Multipart::boundary(resp.headers()) {
// Ok(boundary) => Multipart::new(boundary, resp.payload().clone()),
// Err(err) =>
// Multipart {
// error: Some(err),
// safety: Safety::new(),
// inner: None,
// }
// }
// }
/// Extract boundary info from headers. /// Extract boundary info from headers.
pub fn boundary(headers: &HeaderMap) -> Result<String, MultipartError> { pub fn boundary(headers: &HeaderMap) -> Result<String, MultipartError> {
if let Some(content_type) = headers.get(header::CONTENT_TYPE) { if let Some(content_type) = headers.get(header::CONTENT_TYPE) {
@ -132,8 +85,34 @@ impl Multipart {
} }
} }
impl Stream for Multipart { impl<S> Multipart<S> where S: Stream<Item=Bytes, Error=PayloadError> {
type Item = MultipartItem;
/// Create multipart instance for boundary.
pub fn new(boundary: Result<String, MultipartError>, stream: S) -> Multipart<S> {
match boundary {
Ok(boundary) => Multipart {
error: None,
safety: Safety::new(),
inner: Some(Rc::new(RefCell::new(
InnerMultipart {
boundary,
payload: PayloadRef::new(PayloadHelper::new(stream)),
state: InnerState::FirstBoundary,
item: InnerMultipartItem::None,
})))
},
Err(err) =>
Multipart {
error: Some(err),
safety: Safety::new(),
inner: None,
}
}
}
}
impl<S> Stream for Multipart<S> where S: Stream<Item=Bytes, Error=PayloadError> {
type Item = MultipartItem<S>;
type Error = MultipartError; type Error = MultipartError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> { fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
@ -147,13 +126,14 @@ impl Stream for Multipart {
} }
} }
impl InnerMultipart { impl<S> InnerMultipart<S> where S: Stream<Item=Bytes, Error=PayloadError> {
fn read_headers(payload: &mut Payload) -> Poll<HeaderMap, MultipartError> fn read_headers(payload: &mut PayloadHelper<S>) -> Poll<HeaderMap, MultipartError>
{ {
match payload.readuntil(b"\r\n\r\n").poll()? { match payload.readuntil(b"\r\n\r\n")? {
Async::NotReady => Ok(Async::NotReady), Async::NotReady => Ok(Async::NotReady),
Async::Ready(bytes) => { Async::Ready(None) => Err(MultipartError::Incomplete),
Async::Ready(Some(bytes)) => {
let mut hdrs = [httparse::EMPTY_HEADER; MAX_HEADERS]; let mut hdrs = [httparse::EMPTY_HEADER; MAX_HEADERS];
match httparse::parse_headers(&bytes, &mut hdrs) { match httparse::parse_headers(&bytes, &mut hdrs) {
Ok(httparse::Status::Complete((_, hdrs))) => { Ok(httparse::Status::Complete((_, hdrs))) => {
@ -179,12 +159,14 @@ impl InnerMultipart {
} }
} }
fn read_boundary(payload: &mut Payload, boundary: &str) -> Poll<bool, MultipartError> fn read_boundary(payload: &mut PayloadHelper<S>, boundary: &str)
-> Poll<bool, MultipartError>
{ {
// TODO: need to read epilogue // TODO: need to read epilogue
match payload.readline().poll()? { match payload.readline()? {
Async::NotReady => Ok(Async::NotReady), Async::NotReady => Ok(Async::NotReady),
Async::Ready(chunk) => { Async::Ready(None) => Err(MultipartError::Incomplete),
Async::Ready(Some(chunk)) => {
if chunk.len() == boundary.len() + 4 && if chunk.len() == boundary.len() + 4 &&
&chunk[..2] == b"--" && &chunk[..2] == b"--" &&
&chunk[2..boundary.len()+2] == boundary.as_bytes() &chunk[2..boundary.len()+2] == boundary.as_bytes()
@ -203,11 +185,13 @@ impl InnerMultipart {
} }
} }
fn skip_until_boundary(payload: &mut Payload, boundary: &str) -> Poll<bool, MultipartError> fn skip_until_boundary(payload: &mut PayloadHelper<S>, boundary: &str)
-> Poll<bool, MultipartError>
{ {
let mut eof = false; let mut eof = false;
loop { loop {
if let Async::Ready(chunk) = payload.readline().poll()? { match payload.readline()? {
Async::Ready(Some(chunk)) => {
if chunk.is_empty() { if chunk.is_empty() {
//ValueError("Could not find starting boundary %r" //ValueError("Could not find starting boundary %r"
//% (self._boundary)) //% (self._boundary))
@ -228,14 +212,15 @@ impl InnerMultipart {
break; break;
} }
} }
} else { },
return Ok(Async::NotReady) Async::NotReady => return Ok(Async::NotReady),
Async::Ready(None) => return Err(MultipartError::Incomplete),
} }
} }
Ok(Async::Ready(eof)) Ok(Async::Ready(eof))
} }
fn poll(&mut self, safety: &Safety) -> Poll<Option<MultipartItem>, MultipartError> { fn poll(&mut self, safety: &Safety) -> Poll<Option<MultipartItem<S>>, MultipartError> {
if self.state == InnerState::Eof { if self.state == InnerState::Eof {
Ok(Async::Ready(None)) Ok(Async::Ready(None))
} else { } else {
@ -247,25 +232,18 @@ impl InnerMultipart {
let stop = match self.item { let stop = match self.item {
InnerMultipartItem::Field(ref mut field) => { InnerMultipartItem::Field(ref mut field) => {
match field.borrow_mut().poll(safety)? { match field.borrow_mut().poll(safety)? {
Async::NotReady => { Async::NotReady => return Ok(Async::NotReady),
return Ok(Async::NotReady) Async::Ready(Some(_)) => continue,
} Async::Ready(None) => true,
Async::Ready(Some(_)) =>
continue,
Async::Ready(None) =>
true,
}
} }
},
InnerMultipartItem::Multipart(ref mut multipart) => { InnerMultipartItem::Multipart(ref mut multipart) => {
match multipart.borrow_mut().poll(safety)? { match multipart.borrow_mut().poll(safety)? {
Async::NotReady => Async::NotReady => return Ok(Async::NotReady),
return Ok(Async::NotReady), Async::Ready(Some(_)) => continue,
Async::Ready(Some(_)) => Async::Ready(None) => true,
continue,
Async::Ready(None) =>
true,
}
} }
},
_ => false, _ => false,
}; };
if stop { if stop {
@ -281,25 +259,22 @@ impl InnerMultipart {
match self.state { match self.state {
// read until first boundary // read until first boundary
InnerState::FirstBoundary => { InnerState::FirstBoundary => {
if let Async::Ready(eof) = match InnerMultipart::skip_until_boundary(payload, &self.boundary)? {
InnerMultipart::skip_until_boundary(payload, &self.boundary)? Async::Ready(eof) => {
{
if eof { if eof {
self.state = InnerState::Eof; self.state = InnerState::Eof;
return Ok(Async::Ready(None)); return Ok(Async::Ready(None));
} else { } else {
self.state = InnerState::Headers; self.state = InnerState::Headers;
} }
} else { },
return Ok(Async::NotReady) Async::NotReady => return Ok(Async::NotReady),
}
} }
},
// read boundary // read boundary
InnerState::Boundary => { InnerState::Boundary => {
match InnerMultipart::read_boundary(payload, &self.boundary)? { match InnerMultipart::read_boundary(payload, &self.boundary)? {
Async::NotReady => { Async::NotReady => return Ok(Async::NotReady),
return Ok(Async::NotReady)
}
Async::Ready(eof) => { Async::Ready(eof) => {
if eof { if eof {
self.state = InnerState::Eof; self.state = InnerState::Eof;
@ -375,7 +350,7 @@ impl InnerMultipart {
} }
} }
impl Drop for InnerMultipart { impl<S> Drop for InnerMultipart<S> {
fn drop(&mut self) { fn drop(&mut self) {
// InnerMultipartItem::Field has to be dropped first because of Safety. // InnerMultipartItem::Field has to be dropped first because of Safety.
self.item = InnerMultipartItem::None; self.item = InnerMultipartItem::None;
@ -383,23 +358,18 @@ impl Drop for InnerMultipart {
} }
/// A single field in a multipart stream /// A single field in a multipart stream
pub struct Field { pub struct Field<S> {
ct: mime::Mime, ct: mime::Mime,
headers: HeaderMap, headers: HeaderMap,
inner: Rc<RefCell<InnerField>>, inner: Rc<RefCell<InnerField<S>>>,
safety: Safety, safety: Safety,
} }
impl Field { impl<S> Field<S> where S: Stream<Item=Bytes, Error=PayloadError> {
fn new(safety: Safety, headers: HeaderMap, fn new(safety: Safety, headers: HeaderMap,
ct: mime::Mime, inner: Rc<RefCell<InnerField>>) -> Self { ct: mime::Mime, inner: Rc<RefCell<InnerField<S>>>) -> Self {
Field { Field {ct, headers, inner, safety}
ct: ct,
headers: headers,
inner: inner,
safety: safety,
}
} }
pub fn headers(&self) -> &HeaderMap { pub fn headers(&self) -> &HeaderMap {
@ -411,7 +381,7 @@ impl Field {
} }
} }
impl Stream for Field { impl<S> Stream for Field<S> where S: Stream<Item=Bytes, Error=PayloadError> {
type Item = Bytes; type Item = Bytes;
type Error = MultipartError; type Error = MultipartError;
@ -424,7 +394,7 @@ impl Stream for Field {
} }
} }
impl fmt::Debug for Field { impl<S> fmt::Debug for Field<S> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let res = write!(f, "\nMultipartField: {}\n", self.ct); let res = write!(f, "\nMultipartField: {}\n", self.ct);
let _ = write!(f, " boundary: {}\n", self.inner.borrow().boundary); let _ = write!(f, " boundary: {}\n", self.inner.borrow().boundary);
@ -441,18 +411,17 @@ impl fmt::Debug for Field {
} }
} }
#[derive(Debug)] struct InnerField<S> {
struct InnerField { payload: Option<PayloadRef<S>>,
payload: Option<PayloadRef>,
boundary: String, boundary: String,
eof: bool, eof: bool,
length: Option<u64>, length: Option<u64>,
} }
impl InnerField { impl<S> InnerField<S> where S: Stream<Item=Bytes, Error=PayloadError> {
fn new(payload: PayloadRef, boundary: String, headers: &HeaderMap) fn new(payload: PayloadRef<S>, boundary: String, headers: &HeaderMap)
-> Result<InnerField, PayloadError> -> Result<InnerField<S>, PayloadError>
{ {
let len = if let Some(len) = headers.get(header::CONTENT_LENGTH) { let len = if let Some(len) = headers.get(header::CONTENT_LENGTH) {
if let Ok(s) = len.to_str() { if let Ok(s) = len.to_str() {
@ -469,22 +438,23 @@ impl InnerField {
}; };
Ok(InnerField { Ok(InnerField {
boundary,
payload: Some(payload), payload: Some(payload),
boundary: boundary,
eof: false, eof: false,
length: len }) length: len })
} }
/// Reads body part content chunk of the specified size. /// Reads body part content chunk of the specified size.
/// The body part must has `Content-Length` header with proper value. /// The body part must has `Content-Length` header with proper value.
fn read_len(payload: &mut Payload, size: &mut u64) -> Poll<Option<Bytes>, MultipartError> fn read_len(payload: &mut PayloadHelper<S>, size: &mut u64)
-> Poll<Option<Bytes>, MultipartError>
{ {
if *size == 0 { if *size == 0 {
Ok(Async::Ready(None)) Ok(Async::Ready(None))
} else { } else {
match payload.readany().poll() { match payload.readany() {
Ok(Async::NotReady) => Ok(Async::NotReady), Ok(Async::NotReady) => Ok(Async::NotReady),
Ok(Async::Ready(None)) => Ok(Async::Ready(None)), Ok(Async::Ready(None)) => Err(MultipartError::Incomplete),
Ok(Async::Ready(Some(mut chunk))) => { Ok(Async::Ready(Some(mut chunk))) => {
let len = cmp::min(chunk.len() as u64, *size); let len = cmp::min(chunk.len() as u64, *size);
*size -= len; *size -= len;
@ -501,23 +471,26 @@ impl InnerField {
/// Reads content chunk of body part with unknown length. /// Reads content chunk of body part with unknown length.
/// The `Content-Length` header for body part is not necessary. /// The `Content-Length` header for body part is not necessary.
fn read_stream(payload: &mut Payload, boundary: &str) -> Poll<Option<Bytes>, MultipartError> fn read_stream(payload: &mut PayloadHelper<S>, boundary: &str)
-> Poll<Option<Bytes>, MultipartError>
{ {
match payload.readuntil(b"\r").poll()? { match payload.readuntil(b"\r")? {
Async::NotReady => Ok(Async::NotReady), Async::NotReady => Ok(Async::NotReady),
Async::Ready(mut chunk) => { Async::Ready(None) => Err(MultipartError::Incomplete),
Async::Ready(Some(mut chunk)) => {
if chunk.len() == 1 { if chunk.len() == 1 {
payload.unread_data(chunk); payload.unread_data(chunk);
match payload.readexactly(boundary.len() + 4).poll()? { match payload.readexactly(boundary.len() + 4)? {
Async::NotReady => Ok(Async::NotReady), Async::NotReady => Ok(Async::NotReady),
Async::Ready(chunk) => { Async::Ready(None) => Err(MultipartError::Incomplete),
Async::Ready(Some(chunk)) => {
if &chunk[..2] == b"\r\n" && &chunk[2..4] == b"--" && if &chunk[..2] == b"\r\n" && &chunk[2..4] == b"--" &&
&chunk[4..] == boundary.as_bytes() &chunk[4..] == boundary.as_bytes()
{ {
payload.unread_data(chunk); payload.unread_data(chunk.freeze());
Ok(Async::Ready(None)) Ok(Async::Ready(None))
} else { } else {
Ok(Async::Ready(Some(chunk))) Ok(Async::Ready(Some(chunk.freeze())))
} }
} }
} }
@ -535,24 +508,6 @@ impl InnerField {
if self.payload.is_none() { if self.payload.is_none() {
return Ok(Async::Ready(None)) return Ok(Async::Ready(None))
} }
if self.eof {
if let Some(payload) = self.payload.as_ref().unwrap().get_mut(s) {
match payload.readline().poll()? {
Async::NotReady =>
return Ok(Async::NotReady),
Async::Ready(chunk) => {
assert_eq!(
chunk.as_ref(), b"\r\n",
"reader did not read all the data or it is malformed");
}
}
} else {
return Ok(Async::NotReady);
}
self.payload.take();
return Ok(Async::Ready(None))
}
let result = if let Some(payload) = self.payload.as_ref().unwrap().get_mut(s) { let result = if let Some(payload) = self.payload.as_ref().unwrap().get_mut(s) {
let res = if let Some(ref mut len) = self.length { let res = if let Some(ref mut len) = self.length {
@ -566,12 +521,13 @@ impl InnerField {
Async::Ready(Some(bytes)) => Async::Ready(Some(bytes)), Async::Ready(Some(bytes)) => Async::Ready(Some(bytes)),
Async::Ready(None) => { Async::Ready(None) => {
self.eof = true; self.eof = true;
match payload.readline().poll()? { match payload.readline()? {
Async::NotReady => Async::NotReady, Async::NotReady => Async::NotReady,
Async::Ready(chunk) => { Async::Ready(None) => Async::Ready(None),
assert_eq!( Async::Ready(Some(line)) => {
chunk.as_ref(), b"\r\n", if line.as_ref() != b"\r\n" {
"reader did not read all the data or it is malformed"); warn!("multipart field did not read all the data or it is malformed");
}
Async::Ready(None) Async::Ready(None)
} }
} }
@ -588,25 +544,22 @@ impl InnerField {
} }
} }
#[derive(Debug)] struct PayloadRef<S> {
struct PayloadRef { payload: Rc<PayloadHelper<S>>,
task: Option<Task>,
payload: Rc<Payload>,
} }
impl PayloadRef { impl<S> PayloadRef<S> where S: Stream<Item=Bytes, Error=PayloadError> {
fn new(payload: Payload) -> PayloadRef { fn new(payload: PayloadHelper<S>) -> PayloadRef<S> {
PayloadRef { PayloadRef {
task: None,
payload: Rc::new(payload), payload: Rc::new(payload),
} }
} }
fn get_mut<'a, 'b>(&'a self, s: &'b Safety) -> Option<&'a mut Payload> fn get_mut<'a, 'b>(&'a self, s: &'b Safety) -> Option<&'a mut PayloadHelper<S>>
where 'a: 'b where 'a: 'b
{ {
if s.current() { if s.current() {
let payload: &mut Payload = unsafe { let payload: &mut PayloadHelper<S> = unsafe {
&mut *(self.payload.as_ref() as *const _ as *mut _)}; &mut *(self.payload.as_ref() as *const _ as *mut _)};
Some(payload) Some(payload)
} else { } else {
@ -615,10 +568,9 @@ impl PayloadRef {
} }
} }
impl Clone for PayloadRef { impl<S> Clone for PayloadRef<S> {
fn clone(&self) -> PayloadRef { fn clone(&self) -> PayloadRef<S> {
PayloadRef { PayloadRef {
task: Some(current_task()),
payload: Rc::clone(&self.payload), payload: Rc::clone(&self.payload),
} }
} }
@ -639,7 +591,7 @@ impl Safety {
Safety { Safety {
task: None, task: None,
level: Rc::strong_count(&payload), level: Rc::strong_count(&payload),
payload: payload, payload,
} }
} }
@ -655,7 +607,7 @@ impl Clone for Safety {
Safety { Safety {
task: Some(current_task()), task: Some(current_task()),
level: Rc::strong_count(&payload), level: Rc::strong_count(&payload),
payload: payload, payload,
} }
} }
} }
@ -733,7 +685,7 @@ mod tests {
sender.feed_data(bytes); sender.feed_data(bytes);
let mut multipart = Multipart::new( let mut multipart = Multipart::new(
"abbc761f78ff4d7cb7573b5a23f96ef0".to_owned(), payload); Ok("abbc761f78ff4d7cb7573b5a23f96ef0".to_owned()), payload);
match multipart.poll() { match multipart.poll() {
Ok(Async::Ready(Some(item))) => { Ok(Async::Ready(Some(item))) => {
match item { match item {

View file

@ -1,40 +1,18 @@
//! Payload stream //! Payload stream
use std::{fmt, cmp}; use std::cmp;
use std::rc::{Rc, Weak}; use std::rc::{Rc, Weak};
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::ops::{Deref, DerefMut};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures::{Future, Async, Poll, Stream}; use futures::{Async, Poll, Stream};
use futures::task::{Task, current as current_task};
use body::BodyStream;
use error::PayloadError; use error::PayloadError;
pub(crate) const DEFAULT_BUFFER_SIZE: usize = 65_536; // max buffer size 64k #[derive(Debug, PartialEq)]
pub(crate) enum PayloadStatus {
/// Just Bytes object Read,
#[derive(PartialEq, Message)] Pause,
pub struct PayloadItem(pub Bytes); Dropped,
impl Deref for PayloadItem {
type Target = Bytes;
fn deref(&self) -> &Bytes {
&self.0
}
}
impl DerefMut for PayloadItem {
fn deref_mut(&mut self) -> &mut Bytes {
&mut self.0
}
}
impl fmt::Debug for PayloadItem {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
} }
/// Buffered stream of bytes chunks /// Buffered stream of bytes chunks
@ -88,68 +66,25 @@ impl Payload {
self.inner.borrow().len() == 0 self.inner.borrow().len() == 0
} }
/// Get first available chunk of data.
#[inline]
pub fn readany(&self) -> ReadAny {
ReadAny(Rc::clone(&self.inner))
}
/// Get exact number of bytes
#[inline]
pub fn readexactly(&self, size: usize) -> ReadExactly {
ReadExactly(Rc::clone(&self.inner), size)
}
/// Read until `\n`
#[inline]
pub fn readline(&self) -> ReadLine {
ReadLine(Rc::clone(&self.inner))
}
/// Read until match line
#[inline]
pub fn readuntil(&self, line: &[u8]) -> ReadUntil {
ReadUntil(Rc::clone(&self.inner), line.to_vec())
}
#[doc(hidden)]
#[inline]
pub fn readall(&self) -> Option<Bytes> {
self.inner.borrow_mut().readall()
}
/// Put unused data back to payload /// Put unused data back to payload
#[inline] #[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 #[cfg(test)]
#[inline] pub(crate) fn readall(&self) -> Option<Bytes> {
pub fn buffer_size(&self) -> usize { self.inner.borrow_mut().readall()
self.inner.borrow().buffer_size()
}
/// Set size of payload buffer
#[inline]
pub fn set_buffer_size(&self, size: usize) {
self.inner.borrow_mut().set_buffer_size(size)
}
/// Convert payload into compatible `HttpResponse` body stream
#[inline]
pub fn stream(self) -> BodyStream {
Box::new(self.map(|i| i.0).map_err(|e| e.into()))
} }
} }
impl Stream for Payload { impl Stream for Payload {
type Item = PayloadItem; type Item = Bytes;
type Error = PayloadError; type Error = PayloadError;
#[inline] #[inline]
fn poll(&mut self) -> Poll<Option<PayloadItem>, PayloadError> { fn poll(&mut self) -> Poll<Option<Bytes>, PayloadError> {
self.inner.borrow_mut().readany(false) self.inner.borrow_mut().readany()
} }
} }
@ -159,69 +94,8 @@ impl Clone for Payload {
} }
} }
/// Get first available chunk of data
pub struct ReadAny(Rc<RefCell<Inner>>);
impl Stream for ReadAny {
type Item = Bytes;
type Error = PayloadError;
fn poll(&mut self) -> Poll<Option<Bytes>, Self::Error> {
match self.0.borrow_mut().readany(false)? {
Async::Ready(Some(item)) => Ok(Async::Ready(Some(item.0))),
Async::Ready(None) => Ok(Async::Ready(None)),
Async::NotReady => Ok(Async::NotReady),
}
}
}
/// Get exact number of bytes
pub struct ReadExactly(Rc<RefCell<Inner>>, usize);
impl Future for ReadExactly {
type Item = Bytes;
type Error = PayloadError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self.0.borrow_mut().readexactly(self.1, false)? {
Async::Ready(chunk) => Ok(Async::Ready(chunk)),
Async::NotReady => Ok(Async::NotReady),
}
}
}
/// Read until `\n`
pub struct ReadLine(Rc<RefCell<Inner>>);
impl Future for ReadLine {
type Item = Bytes;
type Error = PayloadError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self.0.borrow_mut().readline(false)? {
Async::Ready(chunk) => Ok(Async::Ready(chunk)),
Async::NotReady => Ok(Async::NotReady),
}
}
}
/// Read until match line
pub struct ReadUntil(Rc<RefCell<Inner>>, Vec<u8>);
impl Future for ReadUntil {
type Item = Bytes;
type Error = PayloadError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self.0.borrow_mut().readuntil(&self.1, false)? {
Async::Ready(chunk) => Ok(Async::Ready(chunk)),
Async::NotReady => Ok(Async::NotReady),
}
}
}
/// Payload writer interface. /// Payload writer interface.
pub trait PayloadWriter { pub(crate) trait PayloadWriter {
/// Set stream error. /// Set stream error.
fn set_error(&mut self, err: PayloadError); fn set_error(&mut self, err: PayloadError);
@ -232,8 +106,8 @@ pub trait PayloadWriter {
/// Feed bytes into a payload stream /// Feed bytes into a payload stream
fn feed_data(&mut self, data: Bytes); fn feed_data(&mut self, data: Bytes);
/// Get estimated available capacity /// Need read data
fn capacity(&self) -> usize; fn need_read(&self) -> PayloadStatus;
} }
/// Sender part of the payload stream /// Sender part of the payload stream
@ -262,59 +136,54 @@ impl PayloadWriter for PayloadSender {
} }
#[inline] #[inline]
fn capacity(&self) -> usize { fn need_read(&self) -> PayloadStatus {
// we check need_read only if Payload (other side) is alive,
// otherwise always return true (consume payload)
if let Some(shared) = self.inner.upgrade() { if let Some(shared) = self.inner.upgrade() {
shared.borrow().capacity() if shared.borrow().need_read {
PayloadStatus::Read
} else { } else {
0 PayloadStatus::Pause
}
} else {
PayloadStatus::Dropped
} }
} }
} }
#[derive(Debug)] #[derive(Debug)]
struct Inner { struct Inner {
len: usize, len: usize,
eof: bool, eof: bool,
err: Option<PayloadError>, err: Option<PayloadError>,
task: Option<Task>, need_read: bool,
items: VecDeque<Bytes>, items: VecDeque<Bytes>,
buf_size: usize,
} }
impl Inner { impl Inner {
fn new(eof: bool) -> Self { fn new(eof: bool) -> Self {
Inner { Inner {
eof,
len: 0, len: 0,
eof: eof,
err: None, err: None,
task: None,
items: VecDeque::new(), items: VecDeque::new(),
buf_size: DEFAULT_BUFFER_SIZE, need_read: true,
} }
} }
fn set_error(&mut self, err: PayloadError) { fn set_error(&mut self, err: PayloadError) {
self.err = Some(err); self.err = Some(err);
if let Some(task) = self.task.take() {
task.notify()
}
} }
fn feed_eof(&mut self) { fn feed_eof(&mut self) {
self.eof = true; self.eof = true;
if let Some(task) = self.task.take() {
task.notify()
}
} }
fn feed_data(&mut self, data: Bytes) { fn feed_data(&mut self, data: Bytes) {
self.len += data.len(); self.len += data.len();
self.need_read = false;
self.items.push_back(data); self.items.push_back(data);
if let Some(task) = self.task.take() {
task.notify()
}
} }
fn eof(&self) -> bool { fn eof(&self) -> bool {
@ -325,23 +194,87 @@ impl Inner {
self.len self.len
} }
fn readany(&mut self, notify: bool) -> Poll<Option<PayloadItem>, PayloadError> { #[cfg(test)]
pub(crate) fn readall(&mut self) -> Option<Bytes> {
let len = self.items.iter().map(|b| b.len()).sum();
if len > 0 {
let mut buf = BytesMut::with_capacity(len);
for item in &self.items {
buf.extend_from_slice(item);
}
self.items = VecDeque::new();
self.len = 0;
Some(buf.take().freeze())
} else {
self.need_read = true;
None
}
}
fn readany(&mut self) -> Poll<Option<Bytes>, PayloadError> {
if let Some(data) = self.items.pop_front() { if let Some(data) = self.items.pop_front() {
self.len -= data.len(); self.len -= data.len();
Ok(Async::Ready(Some(PayloadItem(data)))) Ok(Async::Ready(Some(data)))
} else if let Some(err) = self.err.take() { } else if let Some(err) = self.err.take() {
Err(err) Err(err)
} else if self.eof { } else if self.eof {
Ok(Async::Ready(None)) Ok(Async::Ready(None))
} else { } else {
if notify { self.need_read = true;
self.task = Some(current_task());
}
Ok(Async::NotReady) Ok(Async::NotReady)
} }
} }
fn readexactly(&mut self, size: usize, notify: bool) -> Result<Async<Bytes>, PayloadError> { fn unread_data(&mut self, data: Bytes) {
self.len += data.len();
self.items.push_front(data);
}
}
pub struct PayloadHelper<S> {
len: usize,
items: VecDeque<Bytes>,
stream: S,
}
impl<S> PayloadHelper<S> where S: Stream<Item=Bytes, Error=PayloadError> {
pub fn new(stream: S) -> Self {
PayloadHelper {
len: 0,
items: VecDeque::new(),
stream,
}
}
fn poll_stream(&mut self) -> Poll<bool, PayloadError> {
self.stream.poll().map(|res| {
match res {
Async::Ready(Some(data)) => {
self.len += data.len();
self.items.push_back(data);
Async::Ready(true)
},
Async::Ready(None) => Async::Ready(false),
Async::NotReady => Async::NotReady,
}
})
}
pub fn readany(&mut self) -> Poll<Option<Bytes>, PayloadError> {
if let Some(data) = self.items.pop_front() {
self.len -= data.len();
Ok(Async::Ready(Some(data)))
} else {
match self.poll_stream()? {
Async::Ready(true) => self.readany(),
Async::Ready(false) => Ok(Async::Ready(None)),
Async::NotReady => Ok(Async::NotReady),
}
}
}
pub fn readexactly(&mut self, size: usize) -> Poll<Option<BytesMut>, PayloadError> {
if size <= self.len { if size <= self.len {
let mut buf = BytesMut::with_capacity(size); let mut buf = BytesMut::with_capacity(size);
while buf.len() < size { while buf.len() < size {
@ -351,22 +284,40 @@ impl Inner {
buf.extend_from_slice(&chunk.split_to(rem)); buf.extend_from_slice(&chunk.split_to(rem));
if !chunk.is_empty() { if !chunk.is_empty() {
self.items.push_front(chunk); self.items.push_front(chunk);
return Ok(Async::Ready(buf.freeze())) }
}
return Ok(Async::Ready(Some(buf)))
}
match self.poll_stream()? {
Async::Ready(true) => self.readexactly(size),
Async::Ready(false) => Ok(Async::Ready(None)),
Async::NotReady => Ok(Async::NotReady),
}
}
pub fn copy(&mut self, size: usize) -> Poll<Option<BytesMut>, PayloadError> {
if size <= self.len {
let mut buf = BytesMut::with_capacity(size);
for chunk in &self.items {
if buf.len() < size {
let rem = cmp::min(size - buf.len(), chunk.len());
buf.extend_from_slice(&chunk[..rem]);
}
if buf.len() == size {
return Ok(Async::Ready(Some(buf)))
} }
} }
} }
if let Some(err) = self.err.take() { match self.poll_stream()? {
Err(err) Async::Ready(true) => self.copy(size),
} else { Async::Ready(false) => Ok(Async::Ready(None)),
if notify { Async::NotReady => Ok(Async::NotReady),
self.task = Some(current_task());
}
Ok(Async::NotReady)
} }
} }
fn readuntil(&mut self, line: &[u8], notify: bool) -> Result<Async<Bytes>, PayloadError> { pub fn readuntil(&mut self, line: &[u8]) -> Poll<Option<Bytes>, PayloadError> {
let mut idx = 0; let mut idx = 0;
let mut num = 0; let mut num = 0;
let mut offset = 0; let mut offset = 0;
@ -410,58 +361,33 @@ impl Inner {
} }
} }
self.len -= length; self.len -= length;
return Ok(Async::Ready(buf.freeze())) return Ok(Async::Ready(Some(buf.freeze())))
}
}
if let Some(err) = self.err.take() {
Err(err)
} else {
if notify {
self.task = Some(current_task());
}
Ok(Async::NotReady)
} }
} }
fn readline(&mut self, notify: bool) -> Result<Async<Bytes>, PayloadError> { match self.poll_stream()? {
self.readuntil(b"\n", notify) Async::Ready(true) => self.readuntil(line),
} Async::Ready(false) => Ok(Async::Ready(None)),
Async::NotReady => Ok(Async::NotReady),
pub fn readall(&mut self) -> Option<Bytes> {
let len = self.items.iter().map(|b| b.len()).sum();
if len > 0 {
let mut buf = BytesMut::with_capacity(len);
for item in &self.items {
buf.extend_from_slice(item);
}
self.items = VecDeque::new();
self.len = 0;
Some(buf.take().freeze())
} else {
None
} }
} }
fn unread_data(&mut self, data: Bytes) { pub fn readline(&mut self) -> Poll<Option<Bytes>, PayloadError> {
self.readuntil(b"\n")
}
pub fn unread_data(&mut self, data: Bytes) {
self.len += data.len(); self.len += data.len();
self.items.push_front(data); self.items.push_front(data);
} }
#[inline] #[allow(dead_code)]
fn capacity(&self) -> usize { pub fn remaining(&mut self) -> Bytes {
if self.len > self.buf_size { self.items.iter_mut()
0 .fold(BytesMut::new(), |mut b, c| {
} else { b.extend_from_slice(c);
self.buf_size - self.len b
} }).freeze()
}
fn buffer_size(&self) -> usize {
self.buf_size
}
fn set_buffer_size(&mut self, size: usize) {
self.buf_size = size
} }
} }
@ -487,11 +413,10 @@ mod tests {
fn test_basic() { fn test_basic() {
Core::new().unwrap().run(lazy(|| { Core::new().unwrap().run(lazy(|| {
let (_, payload) = Payload::new(false); let (_, payload) = Payload::new(false);
let mut payload = PayloadHelper::new(payload);
assert!(!payload.eof()); assert_eq!(payload.len, 0);
assert!(payload.is_empty()); assert_eq!(Async::NotReady, payload.readany().ok().unwrap());
assert_eq!(payload.len(), 0);
assert_eq!(Async::NotReady, payload.readany().poll().ok().unwrap());
let res: Result<(), ()> = Ok(()); let res: Result<(), ()> = Ok(());
result(res) result(res)
@ -502,22 +427,17 @@ mod tests {
fn test_eof() { fn test_eof() {
Core::new().unwrap().run(lazy(|| { Core::new().unwrap().run(lazy(|| {
let (mut sender, payload) = Payload::new(false); let (mut sender, payload) = Payload::new(false);
let mut payload = PayloadHelper::new(payload);
assert_eq!(Async::NotReady, payload.readany().poll().ok().unwrap()); assert_eq!(Async::NotReady, payload.readany().ok().unwrap());
assert!(!payload.eof());
sender.feed_data(Bytes::from("data")); sender.feed_data(Bytes::from("data"));
sender.feed_eof(); sender.feed_eof();
assert!(!payload.eof());
assert_eq!(Async::Ready(Some(Bytes::from("data"))), assert_eq!(Async::Ready(Some(Bytes::from("data"))),
payload.readany().poll().ok().unwrap()); payload.readany().ok().unwrap());
assert!(payload.is_empty()); assert_eq!(payload.len, 0);
assert!(payload.eof()); assert_eq!(Async::Ready(None), payload.readany().ok().unwrap());
assert_eq!(payload.len(), 0);
assert_eq!(Async::Ready(None), payload.readany().poll().ok().unwrap());
let res: Result<(), ()> = Ok(()); let res: Result<(), ()> = Ok(());
result(res) result(res)
})).unwrap(); })).unwrap();
@ -527,11 +447,12 @@ mod tests {
fn test_err() { fn test_err() {
Core::new().unwrap().run(lazy(|| { Core::new().unwrap().run(lazy(|| {
let (mut sender, payload) = Payload::new(false); let (mut sender, payload) = Payload::new(false);
let mut payload = PayloadHelper::new(payload);
assert_eq!(Async::NotReady, payload.readany().poll().ok().unwrap()); assert_eq!(Async::NotReady, payload.readany().ok().unwrap());
sender.set_error(PayloadError::Incomplete); sender.set_error(PayloadError::Incomplete);
payload.readany().poll().err().unwrap(); payload.readany().err().unwrap();
let res: Result<(), ()> = Ok(()); let res: Result<(), ()> = Ok(());
result(res) result(res)
})).unwrap(); })).unwrap();
@ -541,20 +462,18 @@ mod tests {
fn test_readany() { fn test_readany() {
Core::new().unwrap().run(lazy(|| { Core::new().unwrap().run(lazy(|| {
let (mut sender, payload) = Payload::new(false); let (mut sender, payload) = Payload::new(false);
let mut payload = PayloadHelper::new(payload);
sender.feed_data(Bytes::from("line1")); sender.feed_data(Bytes::from("line1"));
assert!(!payload.is_empty());
assert_eq!(payload.len(), 5);
sender.feed_data(Bytes::from("line2")); sender.feed_data(Bytes::from("line2"));
assert!(!payload.is_empty());
assert_eq!(payload.len(), 10);
assert_eq!(Async::Ready(Some(Bytes::from("line1"))), assert_eq!(Async::Ready(Some(Bytes::from("line1"))),
payload.readany().poll().ok().unwrap()); payload.readany().ok().unwrap());
assert!(!payload.is_empty()); assert_eq!(payload.len, 0);
assert_eq!(payload.len(), 5);
assert_eq!(Async::Ready(Some(Bytes::from("line2"))),
payload.readany().ok().unwrap());
assert_eq!(payload.len, 0);
let res: Result<(), ()> = Ok(()); let res: Result<(), ()> = Ok(());
result(res) result(res)
@ -565,23 +484,23 @@ mod tests {
fn test_readexactly() { fn test_readexactly() {
Core::new().unwrap().run(lazy(|| { Core::new().unwrap().run(lazy(|| {
let (mut sender, payload) = Payload::new(false); let (mut sender, payload) = Payload::new(false);
let mut payload = PayloadHelper::new(payload);
assert_eq!(Async::NotReady, payload.readexactly(2).poll().ok().unwrap()); assert_eq!(Async::NotReady, payload.readexactly(2).ok().unwrap());
sender.feed_data(Bytes::from("line1")); sender.feed_data(Bytes::from("line1"));
sender.feed_data(Bytes::from("line2")); sender.feed_data(Bytes::from("line2"));
assert_eq!(payload.len(), 10);
assert_eq!(Async::Ready(Bytes::from("li")), assert_eq!(Async::Ready(Some(BytesMut::from("li"))),
payload.readexactly(2).poll().ok().unwrap()); payload.readexactly(2).ok().unwrap());
assert_eq!(payload.len(), 8); assert_eq!(payload.len, 3);
assert_eq!(Async::Ready(Bytes::from("ne1l")), assert_eq!(Async::Ready(Some(BytesMut::from("ne1l"))),
payload.readexactly(4).poll().ok().unwrap()); payload.readexactly(4).ok().unwrap());
assert_eq!(payload.len(), 4); assert_eq!(payload.len, 4);
sender.set_error(PayloadError::Incomplete); sender.set_error(PayloadError::Incomplete);
payload.readexactly(10).poll().err().unwrap(); payload.readexactly(10).err().unwrap();
let res: Result<(), ()> = Ok(()); let res: Result<(), ()> = Ok(());
result(res) result(res)
@ -592,23 +511,23 @@ mod tests {
fn test_readuntil() { fn test_readuntil() {
Core::new().unwrap().run(lazy(|| { Core::new().unwrap().run(lazy(|| {
let (mut sender, payload) = Payload::new(false); let (mut sender, payload) = Payload::new(false);
let mut payload = PayloadHelper::new(payload);
assert_eq!(Async::NotReady, payload.readuntil(b"ne").poll().ok().unwrap()); assert_eq!(Async::NotReady, payload.readuntil(b"ne").ok().unwrap());
sender.feed_data(Bytes::from("line1")); sender.feed_data(Bytes::from("line1"));
sender.feed_data(Bytes::from("line2")); sender.feed_data(Bytes::from("line2"));
assert_eq!(payload.len(), 10);
assert_eq!(Async::Ready(Bytes::from("line")), assert_eq!(Async::Ready(Some(Bytes::from("line"))),
payload.readuntil(b"ne").poll().ok().unwrap()); payload.readuntil(b"ne").ok().unwrap());
assert_eq!(payload.len(), 6); assert_eq!(payload.len, 1);
assert_eq!(Async::Ready(Bytes::from("1line2")), assert_eq!(Async::Ready(Some(Bytes::from("1line2"))),
payload.readuntil(b"2").poll().ok().unwrap()); payload.readuntil(b"2").ok().unwrap());
assert_eq!(payload.len(), 0); assert_eq!(payload.len, 0);
sender.set_error(PayloadError::Incomplete); sender.set_error(PayloadError::Incomplete);
payload.readuntil(b"b").poll().err().unwrap(); payload.readuntil(b"b").err().unwrap();
let res: Result<(), ()> = Ok(()); let res: Result<(), ()> = Ok(());
result(res) result(res)
@ -625,7 +544,7 @@ mod tests {
assert_eq!(payload.len(), 4); assert_eq!(payload.len(), 4);
assert_eq!(Async::Ready(Some(Bytes::from("data"))), assert_eq!(Async::Ready(Some(Bytes::from("data"))),
payload.readany().poll().ok().unwrap()); payload.poll().ok().unwrap());
let res: Result<(), ()> = Ok(()); let res: Result<(), ()> = Ok(());
result(res) result(res)

View file

@ -72,7 +72,7 @@ struct PipelineInfo<S> {
impl<S> PipelineInfo<S> { impl<S> PipelineInfo<S> {
fn new(req: HttpRequest<S>) -> PipelineInfo<S> { fn new(req: HttpRequest<S>) -> PipelineInfo<S> {
PipelineInfo { PipelineInfo {
req: req, req,
count: 0, count: 0,
mws: Rc::new(Vec::new()), mws: Rc::new(Vec::new()),
error: None, error: None,
@ -108,9 +108,8 @@ impl<S: 'static, H: PipelineHandler<S>> Pipeline<S, H> {
handler: Rc<RefCell<H>>) -> Pipeline<S, H> handler: Rc<RefCell<H>>) -> Pipeline<S, H>
{ {
let mut info = PipelineInfo { let mut info = PipelineInfo {
req: req, req, mws,
count: 0, count: 0,
mws: mws,
error: None, error: None,
context: None, context: None,
disconnected: None, disconnected: None,
@ -174,7 +173,8 @@ impl<S: 'static, H: PipelineHandler<S>> HttpHandlerTask for Pipeline<S, H> {
PipelineState::None => PipelineState::None =>
return Ok(Async::Ready(true)), return Ok(Async::Ready(true)),
PipelineState::Error => PipelineState::Error =>
return Err(io::Error::new(io::ErrorKind::Other, "Internal error").into()), return Err(io::Error::new(
io::ErrorKind::Other, "Internal error").into()),
_ => (), _ => (),
} }
@ -307,7 +307,7 @@ impl<S: 'static, H> WaitingResponse<S, H> {
RunMiddlewares::init(info, resp), RunMiddlewares::init(info, resp),
ReplyItem::Future(fut) => ReplyItem::Future(fut) =>
PipelineState::Handler( PipelineState::Handler(
WaitingResponse { fut: fut, _s: PhantomData, _h: PhantomData }), WaitingResponse { fut, _s: PhantomData, _h: PhantomData }),
} }
} }
@ -355,7 +355,7 @@ impl<S: 'static, H> RunMiddlewares<S, H> {
}, },
Ok(Response::Future(fut)) => { Ok(Response::Future(fut)) => {
return PipelineState::RunMiddlewares( return PipelineState::RunMiddlewares(
RunMiddlewares { curr: curr, fut: Some(fut), RunMiddlewares { curr, fut: Some(fut),
_s: PhantomData, _h: PhantomData }) _s: PhantomData, _h: PhantomData })
}, },
}; };
@ -444,7 +444,7 @@ 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,
iostate: IOState::Response, iostate: IOState::Response,
running: RunningState::Running, running: RunningState::Running,
drain: None, _s: PhantomData, _h: PhantomData}) drain: None, _s: PhantomData, _h: PhantomData})
@ -644,7 +644,7 @@ impl<S: 'static, H> FinishingMiddlewares<S, H> {
if info.count == 0 { if info.count == 0 {
Completed::init(info) Completed::init(info)
} else { } else {
let mut state = FinishingMiddlewares{resp: resp, fut: None, let mut state = FinishingMiddlewares{resp, fut: None,
_s: PhantomData, _h: PhantomData}; _s: PhantomData, _h: PhantomData};
if let Some(st) = state.poll(info) { if let Some(st) = state.poll(info) {
st st

View file

@ -3,6 +3,7 @@
use std::marker::PhantomData; use std::marker::PhantomData;
use http; use http;
use http::{header, HttpTryFrom}; use http::{header, HttpTryFrom};
use httpmessage::HttpMessage;
use httprequest::HttpRequest; use httprequest::HttpRequest;
/// Trait defines resource route predicate. /// Trait defines resource route predicate.
@ -27,8 +28,8 @@ pub trait Predicate<S> {
/// fn main() { /// fn main() {
/// Application::new() /// Application::new()
/// .resource("/index.html", |r| r.route() /// .resource("/index.html", |r| r.route()
/// .p(pred::Any(pred::Get()).or(pred::Post())) /// .filter(pred::Any(pred::Get()).or(pred::Post()))
/// .h(HTTPMethodNotAllowed)); /// .h(HttpMethodNotAllowed));
/// } /// }
/// ``` /// ```
pub fn Any<S: 'static, P: Predicate<S> + 'static>(pred: P) -> AnyPredicate<S> pub fn Any<S: 'static, P: Predicate<S> + 'static>(pred: P) -> AnyPredicate<S>
@ -70,9 +71,9 @@ impl<S: 'static> Predicate<S> for AnyPredicate<S> {
/// fn main() { /// fn main() {
/// Application::new() /// Application::new()
/// .resource("/index.html", |r| r.route() /// .resource("/index.html", |r| r.route()
/// .p(pred::All(pred::Get()) /// .filter(pred::All(pred::Get())
/// .and(pred::Header("content-type", "plain/text"))) /// .and(pred::Header("content-type", "plain/text")))
/// .h(HTTPMethodNotAllowed)); /// .h(HttpMethodNotAllowed));
/// } /// }
/// ``` /// ```
pub fn All<S: 'static, P: Predicate<S> + 'static>(pred: P) -> AllPredicate<S> { pub fn All<S: 'static, P: Predicate<S> + 'static>(pred: P) -> AllPredicate<S> {

View file

@ -81,8 +81,8 @@ impl<S: 'static> Resource<S> {
/// let app = Application::new() /// let app = Application::new()
/// .resource( /// .resource(
/// "/", |r| r.route() /// "/", |r| r.route()
/// .p(pred::Any(pred::Get()).or(pred::Put())) /// .filter(pred::Any(pred::Get()).or(pred::Put()))
/// .p(pred::Header("Content-Type", "text/plain")) /// .filter(pred::Header("Content-Type", "text/plain"))
/// .f(|r| HttpResponse::Ok())) /// .f(|r| HttpResponse::Ok()))
/// .finish(); /// .finish();
/// } /// }
@ -97,11 +97,11 @@ impl<S: 'static> Resource<S> {
/// This is shortcut for: /// This is shortcut for:
/// ///
/// ```rust,ignore /// ```rust,ignore
/// Resource::resource("/", |r| r.route().p(pred::Get()).f(index) /// Resource::resource("/", |r| r.route().filter(pred::Get()).f(index)
/// ``` /// ```
pub fn method(&mut self, method: Method) -> &mut Route<S> { pub fn method(&mut self, method: Method) -> &mut Route<S> {
self.routes.push(Route::default()); self.routes.push(Route::default());
self.routes.last_mut().unwrap().p(pred::Method(method)) self.routes.last_mut().unwrap().filter(pred::Method(method))
} }
/// Register a new route and add handler object. /// Register a new route and add handler object.

View file

@ -8,7 +8,7 @@ use pred::Predicate;
use handler::{Reply, ReplyItem, Handler, use handler::{Reply, ReplyItem, Handler,
Responder, RouteHandler, AsyncHandler, WrapHandler}; Responder, RouteHandler, AsyncHandler, WrapHandler};
use middleware::{Middleware, Response as MiddlewareResponse, Started as MiddlewareStarted}; use middleware::{Middleware, Response as MiddlewareResponse, Started as MiddlewareStarted};
use httpcodes::HTTPNotFound; use httpcodes::HttpNotFound;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
@ -26,7 +26,7 @@ impl<S: 'static> Default for Route<S> {
fn default() -> Route<S> { fn default() -> Route<S> {
Route { Route {
preds: Vec::new(), preds: Vec::new(),
handler: InnerHandler::new(|_| HTTPNotFound), handler: InnerHandler::new(|_| HttpNotFound),
} }
} }
} }
@ -65,18 +65,24 @@ impl<S: 'static> Route<S> {
/// Application::new() /// Application::new()
/// .resource("/path", |r| /// .resource("/path", |r|
/// r.route() /// r.route()
/// .p(pred::Get()) /// .filter(pred::Get())
/// .p(pred::Header("content-type", "text/plain")) /// .filter(pred::Header("content-type", "text/plain"))
/// .f(|req| HTTPOk) /// .f(|req| HttpOk)
/// ) /// )
/// # .finish(); /// # .finish();
/// # } /// # }
/// ``` /// ```
pub fn p<T: Predicate<S> + 'static>(&mut self, p: T) -> &mut Self { pub fn filter<T: Predicate<S> + 'static>(&mut self, p: T) -> &mut Self {
self.preds.push(Box::new(p)); self.preds.push(Box::new(p));
self self
} }
#[doc(hidden)]
#[deprecated(since="0.4.1", note="please use `.filter()` instead")]
pub fn p<T: Predicate<S> + 'static>(&mut self, p: T) -> &mut Self {
self.filter(p)
}
/// 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) {
@ -179,14 +185,10 @@ impl<S: 'static> Compose<S> {
mws: Rc<Vec<Box<Middleware<S>>>>, mws: Rc<Vec<Box<Middleware<S>>>>,
handler: InnerHandler<S>) -> Self handler: InnerHandler<S>) -> Self
{ {
let mut info = ComposeInfo { let mut info = ComposeInfo { count: 0, req, mws, handler };
count: 0,
req: req,
mws: mws,
handler: handler };
let state = StartMiddlewares::init(&mut info); let state = StartMiddlewares::init(&mut info);
Compose {state: state, info: info} Compose {state, info}
} }
} }
@ -308,7 +310,7 @@ impl<S: 'static> WaitingResponse<S> {
RunMiddlewares::init(info, resp), RunMiddlewares::init(info, resp),
ReplyItem::Future(fut) => ReplyItem::Future(fut) =>
ComposeState::Handler( ComposeState::Handler(
WaitingResponse { fut: fut, _s: PhantomData }), WaitingResponse { fut, _s: PhantomData }),
} }
} }
@ -353,7 +355,7 @@ impl<S: 'static> RunMiddlewares<S> {
}, },
Ok(MiddlewareResponse::Future(fut)) => { Ok(MiddlewareResponse::Future(fut)) => {
return ComposeState::RunMiddlewares( return ComposeState::RunMiddlewares(
RunMiddlewares { curr: curr, fut: Some(fut), _s: PhantomData }) RunMiddlewares { curr, fut: Some(fut), _s: PhantomData })
}, },
}; };
} }

View file

@ -46,13 +46,9 @@ impl Router {
} }
} }
let len = prefix.len(); let prefix_len = prefix.len();
(Router(Rc::new( (Router(Rc::new(
Inner{ prefix: prefix, Inner{ prefix, prefix_len, named, patterns, srv: settings })), resources)
prefix_len: len,
named: named,
patterns: patterns,
srv: settings })), resources)
} }
/// Router prefix /// Router prefix
@ -152,7 +148,12 @@ impl Pattern {
/// ///
/// Panics if path pattern is wrong. /// Panics if path pattern is wrong.
pub fn new(name: &str, path: &str) -> Self { pub fn new(name: &str, path: &str) -> Self {
let (pattern, elements, is_dynamic) = Pattern::parse(path); Pattern::with_prefix(name, path, "/")
}
/// Parse path pattern and create new `Pattern` instance with custom prefix
pub fn with_prefix(name: &str, path: &str, prefix: &str) -> Self {
let (pattern, elements, is_dynamic) = Pattern::parse(path, prefix);
let tp = if is_dynamic { let tp = if is_dynamic {
let re = match Regex::new(&pattern) { let re = match Regex::new(&pattern) {
@ -168,10 +169,10 @@ impl Pattern {
}; };
Pattern { Pattern {
tp: tp, tp,
pattern,
elements,
name: name.into(), name: name.into(),
pattern: pattern,
elements: elements,
} }
} }
@ -192,7 +193,9 @@ impl Pattern {
} }
} }
pub fn match_with_params<'a>(&'a self, path: &'a str, params: &'a mut Params<'a>) -> bool { pub fn match_with_params<'a>(&'a self, path: &'a str, params: &'a mut Params<'a>)
-> bool
{
match self.tp { match self.tp {
PatternType::Static(ref s) => s == path, PatternType::Static(ref s) => s == path,
PatternType::Dynamic(ref re, ref names) => { PatternType::Dynamic(ref re, ref names) => {
@ -240,11 +243,11 @@ impl Pattern {
Ok(path) Ok(path)
} }
fn parse(pattern: &str) -> (String, Vec<PatternElement>, bool) { fn parse(pattern: &str, prefix: &str) -> (String, Vec<PatternElement>, bool) {
const DEFAULT_PATTERN: &str = "[^/]+"; const DEFAULT_PATTERN: &str = "[^/]+";
let mut re1 = String::from("^/"); let mut re1 = String::from("^") + prefix;
let mut re2 = String::from("/"); let mut re2 = String::from(prefix);
let mut el = String::new(); let mut el = String::new();
let mut in_param = false; let mut in_param = false;
let mut in_param_pattern = false; let mut in_param_pattern = false;

View file

@ -18,15 +18,6 @@ enum HttpProtocol<T: IoStream, H: 'static> {
Unknown(Rc<WorkerSettings<H>>, Option<SocketAddr>, T, BytesMut), 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 { enum ProtocolKind {
Http1, Http1,
Http2, Http2,
@ -41,18 +32,18 @@ pub struct HttpChannel<T, H> where T: IoStream, H: HttpHandler + 'static {
impl<T, H> HttpChannel<T, H> where T: IoStream, H: HttpHandler + 'static impl<T, H> HttpChannel<T, H> where T: IoStream, H: HttpHandler + 'static
{ {
pub(crate) fn new(settings: Rc<WorkerSettings<H>>, pub(crate) fn new(settings: Rc<WorkerSettings<H>>,
io: T, peer: Option<SocketAddr>, http2: bool) -> HttpChannel<T, H> mut io: T, peer: Option<SocketAddr>, http2: bool) -> HttpChannel<T, H>
{ {
settings.add_channel(); settings.add_channel();
let _ = io.set_nodelay(true);
if http2 { if http2 {
HttpChannel { HttpChannel {
node: None, node: None, proto: Some(HttpProtocol::H2(
proto: Some(HttpProtocol::H2(
h2::Http2::new(settings, io, peer, Bytes::new()))) } h2::Http2::new(settings, io, peer, Bytes::new()))) }
} else { } else {
HttpChannel { HttpChannel {
node: None, node: None, proto: Some(HttpProtocol::Unknown(
proto: Some(HttpProtocol::Unknown(
settings, peer, io, BytesMut::with_capacity(4096))) } settings, peer, io, BytesMut::with_capacity(4096))) }
} }
} }
@ -78,15 +69,18 @@ impl<T, H> Future for HttpChannel<T, H> where T: IoStream, H: HttpHandler + 'sta
type Error = (); type Error = ();
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if !self.proto.as_ref().map(|p| p.is_unknown()).unwrap_or(false) && self.node.is_none() { if !self.node.is_none() {
self.node = Some(Node::new(self)); let el = self as *mut _;
match self.proto { self.node = Some(Node::new(el));
let _ = match self.proto {
Some(HttpProtocol::H1(ref mut h1)) => Some(HttpProtocol::H1(ref mut h1)) =>
h1.settings().head().insert(self.node.as_ref().unwrap()), self.node.as_ref().map(|n| h1.settings().head().insert(n)),
Some(HttpProtocol::H2(ref mut h2)) => Some(HttpProtocol::H2(ref mut h2)) =>
h2.settings().head().insert(self.node.as_ref().unwrap()), self.node.as_ref().map(|n| h2.settings().head().insert(n)),
_ => (), Some(HttpProtocol::Unknown(ref mut settings, _, _, _)) =>
} self.node.as_ref().map(|n| settings.head().insert(n)),
None => unreachable!(),
};
} }
let kind = match self.proto { let kind = match self.proto {
@ -95,7 +89,7 @@ impl<T, H> Future for HttpChannel<T, H> where T: IoStream, H: HttpHandler + 'sta
match result { match result {
Ok(Async::Ready(())) | Err(_) => { Ok(Async::Ready(())) | Err(_) => {
h1.settings().remove_channel(); h1.settings().remove_channel();
self.node.as_ref().unwrap().remove(); self.node.as_mut().map(|n| n.remove());
}, },
_ => (), _ => (),
} }
@ -106,7 +100,7 @@ impl<T, H> Future for HttpChannel<T, H> where T: IoStream, H: HttpHandler + 'sta
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_mut().map(|n| n.remove());
}, },
_ => (), _ => (),
} }
@ -117,6 +111,7 @@ impl<T, H> Future for HttpChannel<T, H> where T: IoStream, H: HttpHandler + 'sta
Ok(Async::Ready(0)) | Err(_) => { Ok(Async::Ready(0)) | Err(_) => {
debug!("Ignored premature client disconnection"); debug!("Ignored premature client disconnection");
settings.remove_channel(); settings.remove_channel();
self.node.as_mut().map(|n| n.remove());
return Err(()) return Err(())
}, },
_ => (), _ => (),
@ -163,11 +158,11 @@ pub(crate) struct Node<T>
impl<T> Node<T> impl<T> Node<T>
{ {
fn new(el: &mut T) -> Self { fn new(el: *mut T) -> Self {
Node { Node {
next: None, next: None,
prev: None, prev: None,
element: el as *mut _, element: el,
} }
} }
@ -186,13 +181,14 @@ impl<T> Node<T>
} }
} }
fn remove(&self) { fn remove(&mut self) {
#[allow(mutable_transmutes)]
unsafe { unsafe {
if let Some(ref prev) = self.prev { self.element = ptr::null_mut();
let p: &mut Node<()> = mem::transmute(prev.as_ref().unwrap()); let next = self.next.take();
let slf: &mut Node<T> = mem::transmute(self); let mut prev = self.prev.take();
p.next = slf.next.take();
if let Some(ref mut prev) = prev {
prev.as_mut().unwrap().next = next;
} }
} }
} }
@ -237,7 +233,7 @@ pub(crate) struct WrapperStream<T> where T: AsyncRead + AsyncWrite + 'static {
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 }
} }
} }

View file

@ -11,14 +11,14 @@ 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};
use headers::ContentEncoding; use headers::ContentEncoding;
use body::{Body, Binary}; use body::{Body, Binary};
use error::PayloadError; use error::PayloadError;
use httprequest::HttpMessage; use httprequest::HttpInnerMessage;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
use payload::{PayloadSender, PayloadWriter}; use payload::{PayloadSender, PayloadWriter, PayloadStatus};
use super::shared::SharedBytes; use super::shared::SharedBytes;
@ -120,26 +120,84 @@ impl PayloadWriter for PayloadType {
} }
#[inline] #[inline]
fn capacity(&self) -> usize { fn need_read(&self) -> PayloadStatus {
match *self { match *self {
PayloadType::Sender(ref sender) => sender.capacity(), PayloadType::Sender(ref sender) => sender.need_read(),
PayloadType::Encoding(ref enc) => enc.capacity(), PayloadType::Encoding(ref enc) => enc.need_read(),
} }
} }
} }
enum Decoder {
Deflate(Box<DeflateDecoder<Writer<BytesMut>>>), /// Payload wrapper with content decompression support
pub(crate) struct EncodedPayload {
inner: PayloadSender,
error: bool,
payload: PayloadStream,
}
impl EncodedPayload {
pub fn new(inner: PayloadSender, enc: ContentEncoding) -> EncodedPayload {
EncodedPayload{ inner, error: false, payload: PayloadStream::new(enc) }
}
}
impl PayloadWriter for EncodedPayload {
fn set_error(&mut self, err: PayloadError) {
self.inner.set_error(err)
}
fn feed_eof(&mut self) {
if !self.error {
match self.payload.feed_eof() {
Err(err) => {
self.error = true;
self.set_error(PayloadError::Io(err));
},
Ok(value) => {
if let Some(b) = value {
self.inner.feed_data(b);
}
self.inner.feed_eof();
}
}
}
}
fn feed_data(&mut self, data: Bytes) {
if self.error {
return
}
match self.payload.feed_data(data) {
Ok(Some(b)) => self.inner.feed_data(b),
Ok(None) => (),
Err(e) => {
self.error = true;
self.set_error(e.into());
}
}
}
#[inline]
fn need_read(&self) -> PayloadStatus {
self.inner.need_read()
}
}
pub(crate) enum Decoder {
Deflate(Box<DeflateDecoder<Writer>>),
Gzip(Option<Box<GzDecoder<Wrapper>>>), Gzip(Option<Box<GzDecoder<Wrapper>>>),
Br(Box<BrotliDecoder<Writer<BytesMut>>>), Br(Box<BrotliDecoder<Writer>>),
Identity, Identity,
} }
// should go after write::GzDecoder get implemented // should go after write::GzDecoder get implemented
#[derive(Debug)] #[derive(Debug)]
struct Wrapper { pub(crate) struct Wrapper {
buf: BytesMut, pub buf: BytesMut,
eof: bool, pub eof: bool,
} }
impl io::Read for Wrapper { impl io::Read for Wrapper {
@ -169,50 +227,64 @@ impl io::Write for Wrapper {
} }
} }
/// Payload wrapper with content decompression support pub(crate) struct Writer {
pub(crate) struct EncodedPayload { buf: BytesMut,
inner: PayloadSender,
decoder: Decoder,
dst: BytesMut,
error: bool,
} }
impl EncodedPayload { impl Writer {
pub fn new(inner: PayloadSender, enc: ContentEncoding) -> EncodedPayload { fn new() -> Writer {
Writer{buf: BytesMut::with_capacity(8192)}
}
fn take(&mut self) -> Bytes {
self.buf.take().freeze()
}
}
impl io::Write for Writer {
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 stream with decompression support
pub(crate) struct PayloadStream {
decoder: Decoder,
dst: BytesMut,
}
impl PayloadStream {
pub fn new(enc: ContentEncoding) -> PayloadStream {
let dec = match enc { let dec = match enc {
ContentEncoding::Br => Decoder::Br( ContentEncoding::Br => Decoder::Br(
Box::new(BrotliDecoder::new(BytesMut::with_capacity(8192).writer()))), Box::new(BrotliDecoder::new(Writer::new()))),
ContentEncoding::Deflate => Decoder::Deflate( ContentEncoding::Deflate => Decoder::Deflate(
Box::new(DeflateDecoder::new(BytesMut::with_capacity(8192).writer()))), Box::new(DeflateDecoder::new(Writer::new()))),
ContentEncoding::Gzip => Decoder::Gzip(None), ContentEncoding::Gzip => Decoder::Gzip(None),
_ => Decoder::Identity, _ => Decoder::Identity,
}; };
EncodedPayload{ inner: inner, decoder: dec, error: false, dst: BytesMut::new() } PayloadStream{ decoder: dec, dst: BytesMut::new() }
} }
} }
impl PayloadWriter for EncodedPayload { impl PayloadStream {
fn set_error(&mut self, err: PayloadError) { pub fn feed_eof(&mut self) -> io::Result<Option<Bytes>> {
self.inner.set_error(err) match self.decoder {
}
fn feed_eof(&mut self) {
if self.error {
return
}
let err = match self.decoder {
Decoder::Br(ref mut decoder) => { Decoder::Br(ref mut decoder) => {
match decoder.finish() { match decoder.finish() {
Ok(mut writer) => { Ok(mut writer) => {
let b = writer.get_mut().take().freeze(); let b = writer.take();
if !b.is_empty() { if !b.is_empty() {
self.inner.feed_data(b); Ok(Some(b))
} else {
Ok(None)
} }
self.inner.feed_eof();
return
}, },
Err(err) => Some(err), Err(e) => Err(e),
} }
}, },
Decoder::Gzip(ref mut decoder) => { Decoder::Gzip(ref mut decoder) => {
@ -224,66 +296,50 @@ impl PayloadWriter for EncodedPayload {
match decoder.read(unsafe{self.dst.bytes_mut()}) { match decoder.read(unsafe{self.dst.bytes_mut()}) {
Ok(n) => { Ok(n) => {
if n == 0 { if n == 0 {
self.inner.feed_eof(); return Ok(Some(self.dst.take().freeze()))
return
} else { } else {
unsafe{self.dst.set_len(n)}; unsafe{self.dst.advance_mut(n)};
self.inner.feed_data(self.dst.split_to(n).freeze());
} }
} }
Err(err) => { Err(e) => return Err(e),
break Some(err);
}
} }
} }
} else { } else {
return Ok(None)
} }
}, },
Decoder::Deflate(ref mut decoder) => { Decoder::Deflate(ref mut decoder) => {
match decoder.try_finish() { match decoder.try_finish() {
Ok(_) => { Ok(_) => {
let b = decoder.get_mut().get_mut().take().freeze(); let b = decoder.get_mut().take();
if !b.is_empty() { if !b.is_empty() {
self.inner.feed_data(b); Ok(Some(b))
}
self.inner.feed_eof();
return
},
Err(err) => Some(err),
}
},
Decoder::Identity => {
self.inner.feed_eof();
return
}
};
self.error = true;
self.decoder = Decoder::Identity;
if let Some(err) = err {
self.set_error(PayloadError::Io(err));
} else { } else {
self.set_error(PayloadError::Incomplete); Ok(None)
}
},
Err(e) => Err(e),
}
},
Decoder::Identity => Ok(None),
} }
} }
fn feed_data(&mut self, data: Bytes) { pub fn feed_data(&mut self, data: Bytes) -> io::Result<Option<Bytes>> {
if self.error {
return
}
match self.decoder { match self.decoder {
Decoder::Br(ref mut decoder) => { Decoder::Br(ref mut decoder) => {
if decoder.write(&data).is_ok() && decoder.flush().is_ok() { match decoder.write(&data).and_then(|_| decoder.flush()) {
let b = decoder.get_mut().get_mut().take().freeze(); Ok(_) => {
let b = decoder.get_mut().take();
if !b.is_empty() { if !b.is_empty() {
self.inner.feed_data(b); Ok(Some(b))
} else {
Ok(None)
} }
return },
Err(e) => Err(e)
} }
trace!("Error decoding br encoding"); },
}
Decoder::Gzip(ref mut decoder) => { Decoder::Gzip(ref mut decoder) => {
if decoder.is_none() { if decoder.is_none() {
*decoder = Some( *decoder = Some(
@ -298,60 +354,52 @@ impl PayloadWriter for EncodedPayload {
match decoder.as_mut().as_mut().unwrap().read(unsafe{self.dst.bytes_mut()}) { match decoder.as_mut().as_mut().unwrap().read(unsafe{self.dst.bytes_mut()}) {
Ok(n) => { Ok(n) => {
if n == 0 { if n == 0 {
return return Ok(Some(self.dst.take().freeze()));
} else { } else {
unsafe{self.dst.set_len(n)}; unsafe{self.dst.advance_mut(n)};
self.inner.feed_data(self.dst.split_to(n).freeze());
} }
} }
Err(e) => { Err(e) => {
if e.kind() == io::ErrorKind::WouldBlock { return Err(e)
return
}
break
} }
} }
} }
} },
Decoder::Deflate(ref mut decoder) => { Decoder::Deflate(ref mut decoder) => {
if decoder.write(&data).is_ok() && decoder.flush().is_ok() { match decoder.write(&data).and_then(|_| decoder.flush()) {
let b = decoder.get_mut().get_mut().take().freeze(); Ok(_) => {
let b = decoder.get_mut().take();
if !b.is_empty() { if !b.is_empty() {
self.inner.feed_data(b); Ok(Some(b))
} else {
Ok(None)
} }
return },
Err(e) => Err(e),
} }
trace!("Error decoding deflate encoding"); },
Decoder::Identity => Ok(Some(data)),
} }
Decoder::Identity => {
self.inner.feed_data(data);
return
}
};
self.error = true;
self.decoder = Decoder::Identity;
self.set_error(PayloadError::EncodingCorrupted);
}
fn capacity(&self) -> usize {
self.inner.capacity()
} }
} }
pub(crate) struct PayloadEncoder(ContentEncoder); pub(crate) enum ContentEncoder {
Deflate(DeflateEncoder<TransferEncoding>),
Gzip(GzEncoder<TransferEncoding>),
Br(BrotliEncoder<TransferEncoding>),
Identity(TransferEncoding),
}
impl PayloadEncoder { impl ContentEncoder {
pub fn empty(bytes: SharedBytes) -> PayloadEncoder { pub fn empty(bytes: SharedBytes) -> ContentEncoder {
PayloadEncoder(ContentEncoder::Identity(TransferEncoding::eof(bytes))) ContentEncoder::Identity(TransferEncoding::eof(bytes))
} }
pub fn new(buf: SharedBytes, pub fn for_server(buf: SharedBytes,
req: &HttpMessage, req: &HttpInnerMessage,
resp: &mut HttpResponse, resp: &mut HttpResponse,
response_encoding: ContentEncoding) -> PayloadEncoder response_encoding: ContentEncoding) -> ContentEncoder
{ {
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);
@ -440,7 +488,7 @@ impl PayloadEncoder {
} }
TransferEncoding::eof(buf) TransferEncoding::eof(buf)
} else { } else {
PayloadEncoder::streaming_encoding(buf, version, resp) ContentEncoder::streaming_encoding(buf, version, resp)
} }
} }
}; };
@ -451,7 +499,6 @@ impl PayloadEncoder {
resp.replace_body(body); resp.replace_body(body);
} }
PayloadEncoder(
match encoding { match encoding {
ContentEncoding::Deflate => ContentEncoder::Deflate( ContentEncoding::Deflate => ContentEncoder::Deflate(
DeflateEncoder::new(transfer, Compression::default())), DeflateEncoder::new(transfer, Compression::default())),
@ -462,7 +509,6 @@ impl PayloadEncoder {
ContentEncoding::Identity => ContentEncoder::Identity(transfer), ContentEncoding::Identity => ContentEncoder::Identity(transfer),
ContentEncoding::Auto => unreachable!() ContentEncoding::Auto => unreachable!()
} }
)
} }
fn streaming_encoding(buf: SharedBytes, version: Version, fn streaming_encoding(buf: SharedBytes, version: Version,
@ -527,33 +573,6 @@ impl PayloadEncoder {
} }
} }
impl PayloadEncoder {
#[inline]
pub fn is_eof(&self) -> bool {
self.0.is_eof()
}
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
#[inline(always)]
pub fn write(&mut self, payload: Binary) -> Result<(), io::Error> {
self.0.write(payload)
}
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
#[inline(always)]
pub fn write_eof(&mut self) -> Result<(), io::Error> {
self.0.write_eof()
}
}
pub(crate) enum ContentEncoder {
Deflate(DeflateEncoder<TransferEncoding>),
Gzip(GzEncoder<TransferEncoding>),
Br(BrotliEncoder<TransferEncoding>),
Identity(TransferEncoding),
}
impl ContentEncoder { impl ContentEncoder {
#[inline] #[inline]
@ -829,10 +848,7 @@ impl AcceptEncoding {
Err(_) => 0.0, Err(_) => 0.0,
} }
}; };
Some(AcceptEncoding { Some(AcceptEncoding{ encoding, quality })
encoding: encoding,
quality: quality,
})
} }
/// Parse a raw Accept-Encoding header value into an ordered list. /// Parse a raw Accept-Encoding header value into an ordered list.

View file

@ -1,3 +1,5 @@
#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))]
use std::{self, io}; use std::{self, io};
use std::rc::Rc; use std::rc::Rc;
use std::net::SocketAddr; use std::net::SocketAddr;
@ -13,10 +15,10 @@ use futures::{Future, Poll, Async};
use tokio_core::reactor::Timeout; use tokio_core::reactor::Timeout;
use pipeline::Pipeline; use pipeline::Pipeline;
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}; use payload::{Payload, PayloadWriter, PayloadStatus};
use super::{utils, Writer}; use super::{utils, Writer};
use super::h1writer::H1Writer; use super::h1writer::H1Writer;
@ -62,18 +64,20 @@ 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>, buf: BytesMut) pub fn new(settings: Rc<WorkerSettings<H>>,
-> Self stream: T,
addr: Option<SocketAddr>, read_buf: BytesMut) -> Self
{ {
let bytes = h.get_shared_bytes(); let bytes = settings.get_shared_bytes();
Http1{ flags: Flags::KEEPALIVE, Http1{ flags: Flags::KEEPALIVE,
settings: h,
addr: addr,
stream: H1Writer::new(stream, bytes), stream: H1Writer::new(stream, bytes),
reader: Reader::new(), reader: Reader::new(),
read_buf: buf,
tasks: VecDeque::new(), tasks: VecDeque::new(),
keepalive_timer: None } keepalive_timer: None,
addr,
read_buf,
settings,
}
} }
pub fn settings(&self) -> &WorkerSettings<H> { pub fn settings(&self) -> &WorkerSettings<H> {
@ -84,18 +88,6 @@ impl<T, H> Http1<T, H>
self.stream.get_mut() self.stream.get_mut()
} }
fn poll_completed(&mut self, shutdown: bool) -> Result<bool, ()> {
// check stream state
match self.stream.poll_completed(shutdown) {
Ok(Async::Ready(_)) => Ok(true),
Ok(Async::NotReady) => Ok(false),
Err(err) => {
debug!("Error sending data: {}", err);
Err(())
}
}
}
pub fn poll(&mut self) -> Poll<(), ()> { pub fn poll(&mut self) -> Poll<(), ()> {
// keep-alive timer // keep-alive timer
if let Some(ref mut timer) = self.keepalive_timer { if let Some(ref mut timer) = self.keepalive_timer {
@ -109,14 +101,32 @@ impl<T, H> Http1<T, H>
} }
} }
self.poll_io() loop {
match self.poll_io()? {
Async::Ready(true) => (),
Async::Ready(false) => return Ok(Async::Ready(())),
Async::NotReady => return Ok(Async::NotReady),
}
}
}
fn poll_completed(&mut self, shutdown: bool) -> Result<bool, ()> {
// check stream state
match self.stream.poll_completed(shutdown) {
Ok(Async::Ready(_)) => Ok(true),
Ok(Async::NotReady) => Ok(false),
Err(err) => {
debug!("Error sending data: {}", err);
Err(())
}
}
} }
// TODO: refactor // TODO: refactor
pub fn poll_io(&mut self) -> Poll<(), ()> { pub fn poll_io(&mut self) -> Poll<bool, ()> {
// read incoming data // read incoming data
let need_read = let need_read = if !self.flags.intersects(Flags::ERROR) &&
if !self.flags.contains(Flags::ERROR) && self.tasks.len() < MAX_PIPELINED_MESSAGES self.tasks.len() < MAX_PIPELINED_MESSAGES
{ {
'outer: loop { 'outer: loop {
match self.reader.parse(self.stream.get_mut(), match self.reader.parse(self.stream.get_mut(),
@ -131,9 +141,9 @@ impl<T, H> Http1<T, H>
// start request processing // start request processing
for h in self.settings.handlers().iter_mut() { for h in self.settings.handlers().iter_mut() {
req = match h.handle(req) { req = match h.handle(req) {
Ok(t) => { Ok(pipe) => {
self.tasks.push_back( self.tasks.push_back(
Entry {pipe: t, flags: EntryFlags::empty()}); Entry {pipe, flags: EntryFlags::empty()});
continue 'outer continue 'outer
}, },
Err(req) => req, Err(req) => req,
@ -141,18 +151,11 @@ impl<T, H> Http1<T, H>
} }
self.tasks.push_back( self.tasks.push_back(
Entry {pipe: Pipeline::error(HTTPNotFound), Entry {pipe: Pipeline::error(HttpNotFound),
flags: EntryFlags::empty()}); flags: EntryFlags::empty()});
continue continue
}, },
Ok(Async::NotReady) => (), Ok(Async::NotReady) => (),
Err(ReaderError::Disconnect) => {
self.flags.insert(Flags::ERROR);
self.stream.disconnected();
for entry in &mut self.tasks {
entry.pipe.disconnected()
}
},
Err(err) => { Err(err) => {
// notify all tasks // notify all tasks
self.stream.disconnected(); self.stream.disconnected();
@ -167,6 +170,9 @@ impl<T, H> Http1<T, H>
// on parse error, stop reading stream but tasks need to be completed // on parse error, stop reading stream but tasks need to be completed
self.flags.insert(Flags::ERROR); self.flags.insert(Flags::ERROR);
match err {
ReaderError::Disconnect => (),
_ =>
if self.tasks.is_empty() { if self.tasks.is_empty() {
if let ReaderError::Error(err) = err { if let ReaderError::Error(err) = err {
self.tasks.push_back( self.tasks.push_back(
@ -174,6 +180,7 @@ impl<T, H> Http1<T, H>
flags: EntryFlags::empty()}); flags: EntryFlags::empty()});
} }
} }
}
}, },
} }
break break
@ -183,6 +190,8 @@ impl<T, H> Http1<T, H>
true true
}; };
let retry = self.reader.need_read() == PayloadStatus::Read;
loop { loop {
// check in-flight messages // check in-flight messages
let mut io = false; let mut io = false;
@ -217,7 +226,12 @@ impl<T, H> Http1<T, H>
} }
}, },
// no more IO for this iteration // no more IO for this iteration
Ok(Async::NotReady) => io = true, Ok(Async::NotReady) => {
if self.reader.need_read() == PayloadStatus::Read && !retry {
return Ok(Async::Ready(true));
}
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
@ -264,14 +278,14 @@ impl<T, H> Http1<T, H>
if !self.poll_completed(true)? { if !self.poll_completed(true)? {
return Ok(Async::NotReady) return Ok(Async::NotReady)
} }
return Ok(Async::Ready(())) return Ok(Async::Ready(false))
} }
// start keep-alive timer, this also is slow request timeout // start keep-alive timer, this also is slow request timeout
if self.tasks.is_empty() { if self.tasks.is_empty() {
// check stream state // check stream state
if self.flags.contains(Flags::ERROR) { if self.flags.contains(Flags::ERROR) {
return Ok(Async::Ready(())) return Ok(Async::Ready(false))
} }
if self.settings.keep_alive_enabled() { if self.settings.keep_alive_enabled() {
@ -291,7 +305,7 @@ impl<T, H> Http1<T, H>
return Ok(Async::NotReady) return Ok(Async::NotReady)
} }
// keep-alive is disabled, drop connection // keep-alive is disabled, drop connection
return Ok(Async::Ready(())) return Ok(Async::Ready(false))
} }
} else if !self.poll_completed(false)? || } else if !self.poll_completed(false)? ||
self.flags.contains(Flags::KEEPALIVE) { self.flags.contains(Flags::KEEPALIVE) {
@ -299,7 +313,7 @@ impl<T, H> Http1<T, H>
// if keep-alive unset, rely on operating system // if keep-alive unset, rely on operating system
return Ok(Async::NotReady) return Ok(Async::NotReady)
} else { } else {
return Ok(Async::Ready(())) return Ok(Async::Ready(false))
} }
} else { } else {
self.poll_completed(false)?; self.poll_completed(false)?;
@ -327,6 +341,7 @@ struct PayloadInfo {
enum ReaderError { enum ReaderError {
Disconnect, Disconnect,
Payload, Payload,
PayloadDropped,
Error(ParseError), Error(ParseError),
} }
@ -337,14 +352,27 @@ impl Reader {
} }
} }
#[inline]
fn need_read(&self) -> PayloadStatus {
if let Some(ref info) = self.payload {
info.tx.need_read()
} else {
PayloadStatus::Read
}
}
#[inline] #[inline]
fn decode(&mut self, buf: &mut BytesMut, payload: &mut PayloadInfo) fn decode(&mut self, buf: &mut BytesMut, payload: &mut PayloadInfo)
-> Result<Decoding, ReaderError> -> Result<Decoding, ReaderError>
{ {
loop { while !buf.is_empty() {
match payload.decoder.decode(buf) { match payload.decoder.decode(buf) {
Ok(Async::Ready(Some(bytes))) => { Ok(Async::Ready(Some(bytes))) => {
payload.tx.feed_data(bytes) payload.tx.feed_data(bytes);
if payload.decoder.is_eof() {
payload.tx.feed_eof();
return Ok(Decoding::Ready)
}
}, },
Ok(Async::Ready(None)) => { Ok(Async::Ready(None)) => {
payload.tx.feed_eof(); payload.tx.feed_eof();
@ -357,6 +385,7 @@ impl Reader {
} }
} }
} }
Ok(Decoding::NotReady)
} }
pub fn parse<T, H>(&mut self, io: &mut T, pub fn parse<T, H>(&mut self, io: &mut T,
@ -364,44 +393,60 @@ impl Reader {
settings: &WorkerSettings<H>) -> Poll<HttpRequest, ReaderError> settings: &WorkerSettings<H>) -> Poll<HttpRequest, ReaderError>
where T: IoStream where T: IoStream
{ {
match self.need_read() {
PayloadStatus::Read => (),
PayloadStatus::Pause => return Ok(Async::NotReady),
PayloadStatus::Dropped => return Err(ReaderError::PayloadDropped),
}
// read payload // read payload
let done = { let done = {
if let Some(ref mut payload) = self.payload { if let Some(ref mut payload) = self.payload {
if payload.tx.capacity() == 0 { 'buf: loop {
return Ok(Async::NotReady) let not_ready = match utils::read_from_io(io, buf) {
}
match utils::read_from_io(io, buf) {
Ok(Async::Ready(0)) => { Ok(Async::Ready(0)) => {
payload.tx.set_error(PayloadError::Incomplete); payload.tx.set_error(PayloadError::Incomplete);
// http channel should not deal with payload errors // http channel should not deal with payload errors
return Err(ReaderError::Payload) return Err(ReaderError::Payload)
}, },
Ok(Async::NotReady) => true,
Err(err) => { Err(err) => {
payload.tx.set_error(err.into()); payload.tx.set_error(err.into());
// http channel should not deal with payload errors // http channel should not deal with payload errors
return Err(ReaderError::Payload) return Err(ReaderError::Payload)
} }
_ => (), _ => false,
} };
loop { loop {
match payload.decoder.decode(buf) { match payload.decoder.decode(buf) {
Ok(Async::Ready(Some(bytes))) => { Ok(Async::Ready(Some(bytes))) => {
payload.tx.feed_data(bytes) payload.tx.feed_data(bytes);
if payload.decoder.is_eof() {
payload.tx.feed_eof();
break 'buf true
}
}, },
Ok(Async::Ready(None)) => { Ok(Async::Ready(None)) => {
payload.tx.feed_eof(); payload.tx.feed_eof();
break true break 'buf true
},
Ok(Async::NotReady) => {
// if buffer is full then
// socket still can contain more data
if not_ready {
return Ok(Async::NotReady)
}
continue 'buf
}, },
Ok(Async::NotReady) =>
break false,
Err(err) => { Err(err) => {
payload.tx.set_error(err.into()); payload.tx.set_error(err.into());
return Err(ReaderError::Payload) return Err(ReaderError::Payload)
} }
} }
} }
}
} else { } else {
false false
} }
@ -409,16 +454,13 @@ impl Reader {
if done { self.payload = None } if done { self.payload = None }
// 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() { if buf.is_empty() {
match utils::read_from_io(io, buf) { match utils::read_from_io(io, buf) {
Ok(Async::Ready(0)) => return Err(ReaderError::Disconnect), Ok(Async::Ready(0)) => return Err(ReaderError::Disconnect),
Ok(Async::Ready(_)) => (), Ok(Async::Ready(_)) => (),
Ok(Async::NotReady) => return Ok(Async::NotReady), Ok(Async::NotReady) => return Ok(Async::NotReady),
Err(err) => return Err(ReaderError::Error(err.into())) Err(err) => return Err(ReaderError::Error(err.into()))
} }
false
} else {
true
}; };
loop { loop {
@ -434,11 +476,10 @@ impl Reader {
return Ok(Async::Ready(msg)); return Ok(Async::Ready(msg));
}, },
Async::NotReady => { Async::NotReady => {
if buf.capacity() >= MAX_BUFFER_SIZE { if buf.len() >= 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 {
match utils::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");
@ -448,9 +489,6 @@ impl Reader {
Ok(Async::NotReady) => return Ok(Async::NotReady), Ok(Async::NotReady) => return Ok(Async::NotReady),
Err(err) => return Err(ReaderError::Error(err.into())), Err(err) => return Err(ReaderError::Error(err.into())),
} }
} else {
return Ok(Async::NotReady)
}
}, },
} }
} }
@ -540,7 +578,7 @@ impl Reader {
let (psender, payload) = Payload::new(false); let (psender, payload) = Payload::new(false);
let info = PayloadInfo { let info = PayloadInfo {
tx: PayloadType::new(&msg.get_mut().headers, psender), tx: PayloadType::new(&msg.get_mut().headers, psender),
decoder: decoder, decoder,
}; };
msg.get_mut().payload = Some(payload); msg.get_mut().payload = Some(payload);
Ok(Async::Ready((HttpRequest::from_message(msg), Some(info)))) Ok(Async::Ready((HttpRequest::from_message(msg), Some(info))))
@ -624,6 +662,13 @@ enum ChunkedState {
} }
impl Decoder { impl Decoder {
pub fn is_eof(&self) -> bool {
match self.kind {
Kind::Length(0) | Kind::Chunked(ChunkedState::End, _) | Kind::Eof(true) => true,
_ => false,
}
}
pub fn decode(&mut self, body: &mut BytesMut) -> Poll<Option<Bytes>, io::Error> { pub fn decode(&mut self, body: &mut BytesMut) -> Poll<Option<Bytes>, io::Error> {
match self.kind { match self.kind {
Kind::Length(ref mut remaining) => { Kind::Length(ref mut remaining) => {
@ -815,11 +860,12 @@ mod tests {
use std::{io, cmp, time}; use std::{io, cmp, time};
use std::net::Shutdown; use std::net::Shutdown;
use bytes::{Bytes, BytesMut, Buf}; use bytes::{Bytes, BytesMut, Buf};
use futures::Async; use futures::{Async, Stream};
use tokio_io::{AsyncRead, AsyncWrite}; use tokio_io::{AsyncRead, AsyncWrite};
use http::{Version, Method}; use http::{Version, Method};
use super::*; use super::*;
use httpmessage::HttpMessage;
use application::HttpApplication; use application::HttpApplication;
use server::settings::WorkerSettings; use server::settings::WorkerSettings;
use server::IoStream; use server::IoStream;
@ -1320,6 +1366,7 @@ mod tests {
assert!(!req.payload().eof()); assert!(!req.payload().eof());
buf.feed_data("4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); buf.feed_data("4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n");
let _ = req.payload_mut().poll();
not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); not_ready!(reader.parse(&mut buf, &mut readbuf, &settings));
assert!(!req.payload().eof()); assert!(!req.payload().eof());
assert_eq!(req.payload_mut().readall().unwrap().as_ref(), b"dataline"); assert_eq!(req.payload_mut().readall().unwrap().as_ref(), b"dataline");
@ -1344,6 +1391,7 @@ mod tests {
"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n\ "4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n\
POST /test2 HTTP/1.1\r\n\ POST /test2 HTTP/1.1\r\n\
transfer-encoding: chunked\r\n\r\n"); transfer-encoding: chunked\r\n\r\n");
let _ = req.payload_mut().poll();
let req2 = reader_parse_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); let req2 = reader_parse_ready!(reader.parse(&mut buf, &mut readbuf, &settings));
assert_eq!(*req2.method(), Method::POST); assert_eq!(*req2.method(), Method::POST);
@ -1367,6 +1415,10 @@ mod tests {
assert!(req.chunked().unwrap()); assert!(req.chunked().unwrap());
assert!(!req.payload().eof()); assert!(!req.payload().eof());
buf.feed_data("4\r\n1111\r\n");
not_ready!(reader.parse(&mut buf, &mut readbuf, &settings));
assert_eq!(req.payload_mut().readall().unwrap().as_ref(), b"1111");
buf.feed_data("4\r\ndata\r"); buf.feed_data("4\r\ndata\r");
not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); not_ready!(reader.parse(&mut buf, &mut readbuf, &settings));
@ -1384,13 +1436,18 @@ mod tests {
buf.feed_data("ne\r\n0\r\n"); buf.feed_data("ne\r\n0\r\n");
not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); not_ready!(reader.parse(&mut buf, &mut readbuf, &settings));
//trailers
//buf.feed_data("test: test\r\n"); //buf.feed_data("test: test\r\n");
//not_ready!(reader.parse(&mut buf, &mut readbuf)); //not_ready!(reader.parse(&mut buf, &mut readbuf));
let _ = req.payload_mut().poll();
not_ready!(reader.parse(&mut buf, &mut readbuf, &settings));
assert_eq!(req.payload_mut().readall().unwrap().as_ref(), b"dataline"); assert_eq!(req.payload_mut().readall().unwrap().as_ref(), b"dataline");
assert!(!req.payload().eof()); assert!(!req.payload().eof());
buf.feed_data("\r\n"); buf.feed_data("\r\n");
let _ = req.payload_mut().poll();
not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); not_ready!(reader.parse(&mut buf, &mut readbuf, &settings));
assert!(req.payload().eof()); assert!(req.payload().eof());
} }
@ -1409,6 +1466,7 @@ mod tests {
assert!(!req.payload().eof()); assert!(!req.payload().eof());
buf.feed_data("4;test\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); // test: test\r\n\r\n") buf.feed_data("4;test\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); // test: test\r\n\r\n")
let _ = req.payload_mut().poll();
not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); not_ready!(reader.parse(&mut buf, &mut readbuf, &settings));
assert!(!req.payload().eof()); assert!(!req.payload().eof());
assert_eq!(req.payload_mut().readall().unwrap().as_ref(), b"dataline"); assert_eq!(req.payload_mut().readall().unwrap().as_ref(), b"dataline");

View file

@ -1,3 +1,5 @@
#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))]
use std::{io, mem}; use std::{io, mem};
use bytes::BufMut; use bytes::BufMut;
use futures::{Async, Poll}; use futures::{Async, Poll};
@ -8,11 +10,11 @@ use http::header::{HeaderValue, CONNECTION, DATE};
use helpers; use helpers;
use body::{Body, Binary}; use body::{Body, Binary};
use headers::ContentEncoding; use headers::ContentEncoding;
use httprequest::HttpMessage; use httprequest::HttpInnerMessage;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE}; use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE};
use super::shared::SharedBytes; use super::shared::SharedBytes;
use super::encoding::PayloadEncoder; use super::encoding::ContentEncoder;
const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific
@ -28,7 +30,7 @@ bitflags! {
pub(crate) struct H1Writer<T: AsyncWrite> { pub(crate) struct H1Writer<T: AsyncWrite> {
flags: Flags, flags: Flags,
stream: T, stream: T,
encoder: PayloadEncoder, encoder: ContentEncoder,
written: u64, written: u64,
headers_size: u32, headers_size: u32,
buffer: SharedBytes, buffer: SharedBytes,
@ -39,11 +41,11 @@ impl<T: AsyncWrite> H1Writer<T> {
pub fn new(stream: T, buf: SharedBytes) -> H1Writer<T> { pub fn new(stream: T, buf: SharedBytes) -> H1Writer<T> {
H1Writer { H1Writer {
flags: Flags::empty(), flags: Flags::empty(),
stream: stream, encoder: ContentEncoder::empty(buf.clone()),
encoder: PayloadEncoder::empty(buf.clone()),
written: 0, written: 0,
headers_size: 0, headers_size: 0,
buffer: buf, buffer: buf,
stream,
} }
} }
@ -96,12 +98,12 @@ impl<T: AsyncWrite> Writer for H1Writer<T> {
} }
fn start(&mut self, fn start(&mut self,
req: &mut HttpMessage, req: &mut HttpInnerMessage,
msg: &mut HttpResponse, msg: &mut HttpResponse,
encoding: ContentEncoding) -> io::Result<WriterState> encoding: ContentEncoding) -> io::Result<WriterState>
{ {
// prepare task // prepare task
self.encoder = PayloadEncoder::new(self.buffer.clone(), req, msg, encoding); self.encoder = ContentEncoder::for_server(self.buffer.clone(), req, msg, encoding);
if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) { if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) {
self.flags.insert(Flags::STARTED | Flags::KEEPALIVE); self.flags.insert(Flags::STARTED | Flags::KEEPALIVE);
} else { } else {

View file

@ -1,3 +1,5 @@
#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))]
use std::{io, cmp, mem}; use std::{io, cmp, mem};
use std::rc::Rc; use std::rc::Rc;
use std::io::{Read, Write}; use std::io::{Read, Write};
@ -16,9 +18,10 @@ use tokio_core::reactor::Timeout;
use pipeline::Pipeline; use pipeline::Pipeline;
use error::PayloadError; use error::PayloadError;
use httpcodes::HTTPNotFound; use httpcodes::HttpNotFound;
use httpmessage::HttpMessage;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use payload::{Payload, PayloadWriter}; use payload::{Payload, PayloadWriter, PayloadStatus};
use super::h2writer::H2Writer; use super::h2writer::H2Writer;
use super::encoding::PayloadType; use super::encoding::PayloadType;
@ -32,7 +35,8 @@ bitflags! {
} }
/// HTTP/2 Transport /// HTTP/2 Transport
pub(crate) struct Http2<T, H> pub(crate)
struct Http2<T, H>
where T: AsyncRead + AsyncWrite + 'static, H: 'static where T: AsyncRead + AsyncWrite + 'static, H: 'static
{ {
flags: Flags, flags: Flags,
@ -53,15 +57,17 @@ impl<T, H> Http2<T, H>
where T: AsyncRead + AsyncWrite + 'static, where T: AsyncRead + AsyncWrite + 'static,
H: HttpHandler + 'static H: HttpHandler + 'static
{ {
pub fn new(h: Rc<WorkerSettings<H>>, io: T, addr: Option<SocketAddr>, buf: Bytes) -> Self pub fn new(settings: Rc<WorkerSettings<H>>,
io: T,
addr: Option<SocketAddr>, buf: Bytes) -> Self
{ {
Http2{ flags: Flags::empty(), Http2{ flags: Flags::empty(),
settings: h,
addr: addr,
tasks: VecDeque::new(), tasks: VecDeque::new(),
state: State::Handshake( state: State::Handshake(
server::handshake(IoWrapper{unread: Some(buf), inner: io})), server::handshake(IoWrapper{unread: Some(buf), inner: io})),
keepalive_timer: None, keepalive_timer: None,
addr,
settings,
} }
} }
@ -99,6 +105,8 @@ impl<T, H> Http2<T, H>
item.poll_payload(); item.poll_payload();
if !item.flags.contains(EntryFlags::EOF) { if !item.flags.contains(EntryFlags::EOF) {
let retry = item.payload.need_read() == PayloadStatus::Read;
loop {
match item.task.poll_io(&mut item.stream) { match item.task.poll_io(&mut item.stream) {
Ok(Async::Ready(ready)) => { Ok(Async::Ready(ready)) => {
item.flags.insert(EntryFlags::EOF); item.flags.insert(EntryFlags::EOF);
@ -107,7 +115,12 @@ impl<T, H> Http2<T, H>
} }
not_ready = false; not_ready = false;
}, },
Ok(Async::NotReady) => (), Ok(Async::NotReady) => {
if item.payload.need_read() == PayloadStatus::Read && !retry
{
continue
}
},
Err(err) => { Err(err) => {
error!("Unhandled error: {}", err); error!("Unhandled error: {}", err);
item.flags.insert(EntryFlags::EOF); item.flags.insert(EntryFlags::EOF);
@ -115,6 +128,8 @@ impl<T, H> Http2<T, H>
item.stream.reset(Reason::INTERNAL_ERROR); item.stream.reset(Reason::INTERNAL_ERROR);
} }
} }
break
}
} else if !item.flags.contains(EntryFlags::FINISHED) { } else if !item.flags.contains(EntryFlags::FINISHED) {
match item.task.poll() { match item.task.poll() {
Ok(Async::NotReady) => (), Ok(Async::NotReady) => (),
@ -244,7 +259,6 @@ struct Entry {
payload: PayloadType, payload: PayloadType,
recv: RecvStream, recv: RecvStream,
stream: H2Writer, stream: H2Writer,
capacity: usize,
flags: EntryFlags, flags: EntryFlags,
} }
@ -284,17 +298,24 @@ impl Entry {
} }
} }
Entry {task: task.unwrap_or_else(|| Pipeline::error(HTTPNotFound)), Entry {task: task.unwrap_or_else(|| Pipeline::error(HttpNotFound)),
payload: psender, payload: psender,
recv: recv,
stream: H2Writer::new(resp, settings.get_shared_bytes()), stream: H2Writer::new(resp, settings.get_shared_bytes()),
flags: EntryFlags::empty(), flags: EntryFlags::empty(),
capacity: 0, recv,
} }
} }
fn poll_payload(&mut self) { fn poll_payload(&mut self) {
if !self.flags.contains(EntryFlags::REOF) { if !self.flags.contains(EntryFlags::REOF) {
if self.payload.need_read() == PayloadStatus::Read {
if let Err(err) = self.recv.release_capacity().release_capacity(32_768) {
self.payload.set_error(PayloadError::Http2(err))
}
} else if let Err(err) = self.recv.release_capacity().release_capacity(0) {
self.payload.set_error(PayloadError::Http2(err))
}
match self.recv.poll() { match self.recv.poll() {
Ok(Async::Ready(Some(chunk))) => { Ok(Async::Ready(Some(chunk))) => {
self.payload.feed_data(chunk); self.payload.feed_data(chunk);
@ -307,14 +328,6 @@ impl Entry {
self.payload.set_error(PayloadError::Http2(err)) self.payload.set_error(PayloadError::Http2(err))
} }
} }
let capacity = self.payload.capacity();
if self.capacity != capacity {
self.capacity = capacity;
if let Err(err) = self.recv.release_capacity().release_capacity(capacity) {
self.payload.set_error(PayloadError::Http2(err))
}
}
} }
} }
} }

View file

@ -1,3 +1,5 @@
#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))]
use std::{io, cmp}; use std::{io, cmp};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures::{Async, Poll}; use futures::{Async, Poll};
@ -9,9 +11,9 @@ use http::header::{HeaderValue, CONNECTION, TRANSFER_ENCODING, DATE, CONTENT_LEN
use helpers; use helpers;
use body::{Body, Binary}; use body::{Body, Binary};
use headers::ContentEncoding; use headers::ContentEncoding;
use httprequest::HttpMessage; use httprequest::HttpInnerMessage;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
use super::encoding::PayloadEncoder; use super::encoding::ContentEncoder;
use super::shared::SharedBytes; use super::shared::SharedBytes;
use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE}; use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE};
@ -28,7 +30,7 @@ bitflags! {
pub(crate) struct H2Writer { pub(crate) struct H2Writer {
respond: SendResponse<Bytes>, respond: SendResponse<Bytes>,
stream: Option<SendStream<Bytes>>, stream: Option<SendStream<Bytes>>,
encoder: PayloadEncoder, encoder: ContentEncoder,
flags: Flags, flags: Flags,
written: u64, written: u64,
buffer: SharedBytes, buffer: SharedBytes,
@ -38,9 +40,9 @@ impl H2Writer {
pub fn new(respond: SendResponse<Bytes>, buf: SharedBytes) -> H2Writer { pub fn new(respond: SendResponse<Bytes>, buf: SharedBytes) -> H2Writer {
H2Writer { H2Writer {
respond: respond, respond,
stream: None, stream: None,
encoder: PayloadEncoder::empty(buf.clone()), encoder: ContentEncoder::empty(buf.clone()),
flags: Flags::empty(), flags: Flags::empty(),
written: 0, written: 0,
buffer: buf, buffer: buf,
@ -109,11 +111,11 @@ impl Writer for H2Writer {
self.written self.written
} }
fn start(&mut self, req: &mut HttpMessage, msg: &mut HttpResponse, encoding: ContentEncoding) fn start(&mut self, req: &mut HttpInnerMessage, msg: &mut HttpResponse, encoding: ContentEncoding)
-> io::Result<WriterState> { -> io::Result<WriterState> {
// prepare response // prepare response
self.flags.insert(Flags::STARTED); self.flags.insert(Flags::STARTED);
self.encoder = PayloadEncoder::new(self.buffer.clone(), req, msg, encoding); self.encoder = ContentEncoder::for_server(self.buffer.clone(), req, msg, encoding);
if let Body::Empty = *msg.body() { if let Body::Empty = *msg.body() {
self.flags.insert(Flags::EOF); self.flags.insert(Flags::EOF);
} }

View file

@ -25,7 +25,7 @@ pub use self::settings::ServerSettings;
use body::Binary; use body::Binary;
use error::Error; use error::Error;
use headers::ContentEncoding; use headers::ContentEncoding;
use httprequest::{HttpMessage, HttpRequest}; use httprequest::{HttpInnerMessage, HttpRequest};
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
/// max buffer size 64k /// max buffer size 64k
@ -103,7 +103,7 @@ pub enum WriterState {
pub trait Writer { pub trait Writer {
fn written(&self) -> u64; fn written(&self) -> u64;
fn start(&mut self, req: &mut HttpMessage, resp: &mut HttpResponse, encoding: ContentEncoding) fn start(&mut self, req: &mut HttpInnerMessage, resp: &mut HttpResponse, encoding: ContentEncoding)
-> io::Result<WriterState>; -> io::Result<WriterState>;
fn write(&mut self, payload: Binary) -> io::Result<WriterState>; fn write(&mut self, payload: Binary) -> io::Result<WriterState>;

View file

@ -36,11 +36,7 @@ impl ServerSettings {
} else { } else {
"localhost".to_owned() "localhost".to_owned()
}; };
ServerSettings { ServerSettings { addr, secure, host }
addr: addr,
secure: secure,
host: host,
}
} }
/// Returns the socket address of the local half of this TCP connection /// Returns the socket address of the local half of this TCP connection
@ -67,7 +63,7 @@ pub(crate) struct WorkerSettings<H> {
bytes: Rc<SharedBytesPool>, bytes: Rc<SharedBytesPool>,
messages: Rc<helpers::SharedMessagePool>, messages: Rc<helpers::SharedMessagePool>,
channels: Cell<usize>, channels: Cell<usize>,
node: Node<()>, node: Box<Node<()>>,
} }
impl<H> WorkerSettings<H> { impl<H> WorkerSettings<H> {
@ -79,7 +75,7 @@ impl<H> WorkerSettings<H> {
bytes: Rc::new(SharedBytesPool::new()), bytes: Rc::new(SharedBytesPool::new()),
messages: Rc::new(helpers::SharedMessagePool::new()), messages: Rc::new(helpers::SharedMessagePool::new()),
channels: Cell::new(0), channels: Cell::new(0),
node: Node::head(), node: Box::new(Node::head()),
} }
} }
@ -107,8 +103,8 @@ impl<H> WorkerSettings<H> {
SharedBytes::new(self.bytes.get_bytes(), Rc::clone(&self.bytes)) SharedBytes::new(self.bytes.get_bytes(), Rc::clone(&self.bytes))
} }
pub fn get_http_message(&self) -> helpers::SharedHttpMessage { pub fn get_http_message(&self) -> helpers::SharedHttpInnerMessage {
helpers::SharedHttpMessage::new(self.messages.get(), Rc::clone(&self.messages)) helpers::SharedHttpInnerMessage::new(self.messages.get(), Rc::clone(&self.messages))
} }
pub fn add_channel(&self) { pub fn add_channel(&self) {

View file

@ -261,7 +261,7 @@ impl<H: IntoHttpHandler> HttpServer<H>
/// ///
/// HttpServer::new( /// HttpServer::new(
/// || Application::new() /// || Application::new()
/// .resource("/", |r| r.h(httpcodes::HTTPOk))) /// .resource("/", |r| r.h(httpcodes::HttpOk)))
/// .bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0") /// .bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0")
/// .start(); /// .start();
/// # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0)); /// # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
@ -312,7 +312,7 @@ impl<H: IntoHttpHandler> HttpServer<H>
/// fn main() { /// fn main() {
/// HttpServer::new( /// HttpServer::new(
/// || Application::new() /// || Application::new()
/// .resource("/", |r| r.h(httpcodes::HTTPOk))) /// .resource("/", |r| r.h(httpcodes::HttpOk)))
/// .bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0") /// .bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0")
/// .run(); /// .run();
/// } /// }
@ -697,7 +697,7 @@ fn create_tcp_listener(addr: net::SocketAddr, backlog: i32) -> io::Result<net::T
net::SocketAddr::V4(_) => TcpBuilder::new_v4()?, net::SocketAddr::V4(_) => TcpBuilder::new_v4()?,
net::SocketAddr::V6(_) => TcpBuilder::new_v6()?, net::SocketAddr::V6(_) => TcpBuilder::new_v6()?,
}; };
builder.bind(addr)?;
builder.reuse_address(true)?; builder.reuse_address(true)?;
builder.bind(addr)?;
Ok(builder.listen(backlog)?) Ok(builder.listen(backlog)?)
} }

View file

@ -5,7 +5,7 @@ use futures::{Async, Poll};
use super::IoStream; use super::IoStream;
const LW_BUFFER_SIZE: usize = 4096; const LW_BUFFER_SIZE: usize = 4096;
const HW_BUFFER_SIZE: usize = 16_384; const HW_BUFFER_SIZE: usize = 32_768;
pub fn read_from_io<T: IoStream>(io: &mut T, buf: &mut BytesMut) -> Poll<usize, io::Error> { pub fn read_from_io<T: IoStream>(io: &mut T, buf: &mut BytesMut) -> Poll<usize, io::Error> {

View file

@ -62,7 +62,7 @@ impl<H: HttpHandler + 'static> Worker<H> {
Worker { Worker {
settings: Rc::new(WorkerSettings::new(h, keep_alive)), settings: Rc::new(WorkerSettings::new(h, keep_alive)),
hnd: Arbiter::handle().clone(), hnd: Arbiter::handle().clone(),
handler: handler, handler,
} }
} }

View file

@ -14,6 +14,7 @@ use tokio_core::net::TcpListener;
use tokio_core::reactor::Core; use tokio_core::reactor::Core;
use net2::TcpBuilder; use net2::TcpBuilder;
use ws;
use body::Binary; use body::Binary;
use error::Error; use error::Error;
use handler::{Handler, Responder, ReplyItem}; use handler::{Handler, Responder, ReplyItem};
@ -25,7 +26,6 @@ use payload::Payload;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
use server::{HttpServer, IntoHttpHandler, ServerSettings}; use server::{HttpServer, IntoHttpHandler, ServerSettings};
use ws::{WsClient, WsClientError, WsClientReader, WsClientWriter};
use client::{ClientRequest, ClientRequestBuilder}; use client::{ClientRequest, ClientRequestBuilder};
/// The `TestServer` type. /// The `TestServer` type.
@ -41,7 +41,7 @@ use client::{ClientRequest, ClientRequestBuilder};
/// # use actix_web::*; /// # use actix_web::*;
/// # /// #
/// # fn my_handler(req: HttpRequest) -> HttpResponse { /// # fn my_handler(req: HttpRequest) -> HttpResponse {
/// # httpcodes::HTTPOk.into() /// # httpcodes::HttpOk.into()
/// # } /// # }
/// # /// #
/// # fn main() { /// # fn main() {
@ -94,12 +94,12 @@ impl TestServer {
let _ = sys.run(); let _ = sys.run();
}); });
let (sys, addr) = rx.recv().unwrap(); let (server_sys, addr) = rx.recv().unwrap();
TestServer { TestServer {
addr: addr, addr,
thread: Some(join), thread: Some(join),
system: System::new("actix-test"), system: System::new("actix-test"),
server_sys: sys, server_sys,
} }
} }
@ -131,12 +131,12 @@ impl TestServer {
let _ = sys.run(); let _ = sys.run();
}); });
let (sys, addr) = rx.recv().unwrap(); let (server_sys, addr) = rx.recv().unwrap();
TestServer { TestServer {
addr: addr, addr,
server_sys,
thread: Some(join), thread: Some(join),
system: System::new("actix-test"), system: System::new("actix-test"),
server_sys: sys,
} }
} }
@ -180,9 +180,9 @@ impl TestServer {
} }
/// Connect to websocket server /// Connect to websocket server
pub fn ws(&mut self) -> Result<(WsClientReader, WsClientWriter), WsClientError> { pub fn ws(&mut self) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> {
let url = self.url("/"); let url = self.url("/");
self.system.run_until_complete(WsClient::new(url).connect().unwrap()) self.system.run_until_complete(ws::Client::new(url).connect())
} }
/// Create `GET` request /// Create `GET` request
@ -282,9 +282,9 @@ impl<S: 'static> Iterator for TestApp<S> {
/// ///
/// fn index(req: HttpRequest) -> HttpResponse { /// fn index(req: HttpRequest) -> HttpResponse {
/// if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) { /// if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) {
/// httpcodes::HTTPOk.into() /// httpcodes::HttpOk.into()
/// } else { /// } else {
/// httpcodes::HTTPBadRequest.into() /// httpcodes::HttpBadRequest.into()
/// } /// }
/// } /// }
/// ///
@ -346,7 +346,7 @@ impl<S> TestRequest<S> {
/// Start HttpRequest build process with application state /// Start HttpRequest build process with application state
pub fn with_state(state: S) -> TestRequest<S> { pub fn with_state(state: S) -> TestRequest<S> {
TestRequest { TestRequest {
state: state, state,
method: Method::GET, method: Method::GET,
uri: Uri::from_str("/").unwrap(), uri: Uri::from_str("/").unwrap(),
version: Version::HTTP_11, version: Version::HTTP_11,
@ -434,7 +434,7 @@ impl<S> TestRequest<S> {
let req = self.finish(); let req = self.finish();
let resp = h.handle(req.clone()); let resp = h.handle(req.clone());
match resp.respond_to(req.clone_without_state()) { match resp.respond_to(req.without_state()) {
Ok(resp) => { Ok(resp) => {
match resp.into().into() { match resp.into().into() {
ReplyItem::Message(resp) => Ok(resp), ReplyItem::Message(resp) => Ok(resp),
@ -461,7 +461,7 @@ impl<S> TestRequest<S> {
let mut core = Core::new().unwrap(); let mut core = Core::new().unwrap();
match core.run(fut) { match core.run(fut) {
Ok(r) => { Ok(r) => {
match r.respond_to(req.clone_without_state()) { match r.respond_to(req.without_state()) {
Ok(reply) => match reply.into().into() { Ok(reply) => match reply.into().into() {
ReplyItem::Message(resp) => Ok(resp), ReplyItem::Message(resp) => Ok(resp),
_ => panic!("Nested async replies are not supported"), _ => panic!("Nested async replies are not supported"),

View file

@ -1,53 +1,70 @@
//! Http client request //! Http client request
#![allow(unused_imports, dead_code)]
use std::{fmt, io, str}; use std::{fmt, io, str};
use std::rc::Rc; use std::rc::Rc;
use std::time::Duration;
use std::cell::UnsafeCell; use std::cell::UnsafeCell;
use base64; use base64;
use rand; use rand;
use bytes::Bytes;
use cookie::Cookie; use cookie::Cookie;
use bytes::BytesMut;
use http::{HttpTryFrom, StatusCode, Error as HttpError}; use http::{HttpTryFrom, StatusCode, Error as HttpError};
use http::header::{self, HeaderName, HeaderValue}; use http::header::{self, HeaderName, HeaderValue};
use sha1::Sha1; use sha1::Sha1;
use futures::{Async, Future, Poll, Stream}; use futures::{Async, Future, Poll, Stream};
use futures::future::{Either, err as FutErr}; use futures::unsync::mpsc::{unbounded, UnboundedSender};
use tokio_core::net::TcpStream; use byteorder::{ByteOrder, NetworkEndian};
use actix::prelude::*; use actix::prelude::*;
use body::Binary; use body::{Body, Binary};
use error::UrlParseError; use error::UrlParseError;
use server::shared::SharedBytes; use payload::PayloadHelper;
use httpmessage::HttpMessage;
use server::{utils, IoStream}; use client::{ClientRequest, ClientRequestBuilder, ClientResponse,
use client::{ClientRequest, ClientRequestBuilder, ClientConnector, SendRequest, SendRequestError,
HttpResponseParser, HttpResponseParserError, HttpClientWriter}; HttpResponseParserError};
use client::{Connect, Connection, ClientConnector, ClientConnectorError};
use super::Message; use super::{Message, ProtocolError};
use super::frame::Frame; use super::frame::Frame;
use super::proto::{CloseCode, OpCode}; use super::proto::{CloseCode, OpCode};
pub type WsClientFuture =
Future<Item=(WsClientReader, WsClientWriter), Error=WsClientError>; /// Backward compatibility
#[doc(hidden)]
#[deprecated(since="0.4.2", note="please use `ws::Client` instead")]
pub type WsClient = Client;
#[doc(hidden)]
#[deprecated(since="0.4.2", note="please use `ws::ClientError` instead")]
pub type WsClientError = ClientError;
#[doc(hidden)]
#[deprecated(since="0.4.2", note="please use `ws::ClientReader` instead")]
pub type WsClientReader = ClientReader;
#[doc(hidden)]
#[deprecated(since="0.4.2", note="please use `ws::ClientWriter` instead")]
pub type WsClientWriter = ClientWriter;
#[doc(hidden)]
#[deprecated(since="0.4.2", note="please use `ws::ClientHandshake` instead")]
pub type WsClientHandshake = ClientHandshake;
/// Websocket client error /// Websocket client error
#[derive(Fail, Debug)] #[derive(Fail, Debug)]
pub enum WsClientError { pub enum ClientError {
#[fail(display="Invalid url")] #[fail(display="Invalid url")]
InvalidUrl, InvalidUrl,
#[fail(display="Invalid response status")] #[fail(display="Invalid response status")]
InvalidResponseStatus, InvalidResponseStatus(StatusCode),
#[fail(display="Invalid upgrade header")] #[fail(display="Invalid upgrade header")]
InvalidUpgradeHeader, InvalidUpgradeHeader,
#[fail(display="Invalid connection header")] #[fail(display="Invalid connection header")]
InvalidConnectionHeader, InvalidConnectionHeader(HeaderValue),
#[fail(display="Missing CONNECTION header")]
MissingConnectionHeader,
#[fail(display="Missing SEC-WEBSOCKET-ACCEPT header")]
MissingWebSocketAcceptHeader,
#[fail(display="Invalid challenge response")] #[fail(display="Invalid challenge response")]
InvalidChallengeResponse, InvalidChallengeResponse(String, HeaderValue),
#[fail(display="Http parsing error")] #[fail(display="Http parsing error")]
Http(HttpError), Http(HttpError),
#[fail(display="Url parsing error")] #[fail(display="Url parsing error")]
@ -55,40 +72,48 @@ pub enum WsClientError {
#[fail(display="Response parsing error")] #[fail(display="Response parsing error")]
ResponseParseError(HttpResponseParserError), ResponseParseError(HttpResponseParserError),
#[fail(display="{}", _0)] #[fail(display="{}", _0)]
Connector(ClientConnectorError), SendRequest(SendRequestError),
#[fail(display="{}", _0)]
Protocol(#[cause] ProtocolError),
#[fail(display="{}", _0)] #[fail(display="{}", _0)]
Io(io::Error), Io(io::Error),
#[fail(display="Disconnected")] #[fail(display="Disconnected")]
Disconnected, Disconnected,
} }
impl From<HttpError> for WsClientError { impl From<HttpError> for ClientError {
fn from(err: HttpError) -> WsClientError { fn from(err: HttpError) -> ClientError {
WsClientError::Http(err) ClientError::Http(err)
} }
} }
impl From<UrlParseError> for WsClientError { impl From<UrlParseError> for ClientError {
fn from(err: UrlParseError) -> WsClientError { fn from(err: UrlParseError) -> ClientError {
WsClientError::Url(err) ClientError::Url(err)
} }
} }
impl From<ClientConnectorError> for WsClientError { impl From<SendRequestError> for ClientError {
fn from(err: ClientConnectorError) -> WsClientError { fn from(err: SendRequestError) -> ClientError {
WsClientError::Connector(err) ClientError::SendRequest(err)
} }
} }
impl From<io::Error> for WsClientError { impl From<ProtocolError> for ClientError {
fn from(err: io::Error) -> WsClientError { fn from(err: ProtocolError) -> ClientError {
WsClientError::Io(err) ClientError::Protocol(err)
} }
} }
impl From<HttpResponseParserError> for WsClientError { impl From<io::Error> for ClientError {
fn from(err: HttpResponseParserError) -> WsClientError { fn from(err: io::Error) -> ClientError {
WsClientError::ResponseParseError(err) ClientError::Io(err)
}
}
impl From<HttpResponseParserError> for ClientError {
fn from(err: HttpResponseParserError) -> ClientError {
ClientError::ResponseParseError(err)
} }
} }
@ -97,38 +122,40 @@ impl From<HttpResponseParserError> for WsClientError {
/// Example of `WebSocket` client usage is available in /// Example of `WebSocket` client usage is available in
/// [websocket example]( /// [websocket example](
/// https://github.com/actix/actix-web/blob/master/examples/websocket/src/client.rs#L24) /// https://github.com/actix/actix-web/blob/master/examples/websocket/src/client.rs#L24)
pub struct WsClient { pub struct Client {
request: ClientRequestBuilder, request: ClientRequestBuilder,
err: Option<WsClientError>, err: Option<ClientError>,
http_err: Option<HttpError>, http_err: Option<HttpError>,
origin: Option<HeaderValue>, origin: Option<HeaderValue>,
protocols: Option<String>, protocols: Option<String>,
conn: Addr<Unsync, ClientConnector>, conn: Addr<Unsync, ClientConnector>,
max_size: usize,
} }
impl WsClient { impl Client {
/// Create new websocket connection /// Create new websocket connection
pub fn new<S: AsRef<str>>(uri: S) -> WsClient { pub fn new<S: AsRef<str>>(uri: S) -> Client {
WsClient::with_connector(uri, ClientConnector::from_registry()) Client::with_connector(uri, ClientConnector::from_registry())
} }
/// Create new websocket connection with custom `ClientConnector` /// Create new websocket connection with custom `ClientConnector`
pub fn with_connector<S: AsRef<str>>(uri: S, conn: Addr<Unsync, ClientConnector>) -> WsClient { pub fn with_connector<S: AsRef<str>>(uri: S, conn: Addr<Unsync, ClientConnector>) -> Client {
let mut cl = WsClient { let mut cl = Client {
request: ClientRequest::build(), request: ClientRequest::build(),
err: None, err: None,
http_err: None, http_err: None,
origin: None, origin: None,
protocols: None, protocols: None,
conn: conn, max_size: 65_536,
conn,
}; };
cl.request.uri(uri.as_ref()); cl.request.uri(uri.as_ref());
cl cl
} }
/// Set supported websocket protocols /// Set supported websocket protocols
pub fn protocols<U, V>(&mut self, protos: U) -> &mut Self pub fn protocols<U, V>(mut self, protos: U) -> Self
where U: IntoIterator<Item=V> + 'static, where U: IntoIterator<Item=V> + 'static,
V: AsRef<str> V: AsRef<str>
{ {
@ -140,13 +167,13 @@ impl WsClient {
} }
/// Set cookie for handshake request /// Set cookie for handshake request
pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self { pub fn cookie(mut self, cookie: Cookie) -> Self {
self.request.cookie(cookie); self.request.cookie(cookie);
self self
} }
/// Set request Origin /// Set request Origin
pub fn origin<V>(&mut self, origin: V) -> &mut Self pub fn origin<V>(mut self, origin: V) -> Self
where HeaderValue: HttpTryFrom<V> where HeaderValue: HttpTryFrom<V>
{ {
match HeaderValue::try_from(origin) { match HeaderValue::try_from(origin) {
@ -156,8 +183,16 @@ impl WsClient {
self self
} }
/// Set max frame size
///
/// By default max size is set to 64kb
pub fn max_frame_size(mut self, size: usize) -> Self {
self.max_size = size;
self
}
/// Set request header /// Set request header
pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self pub fn header<K, V>(mut self, key: K, value: V) -> Self
where HeaderName: HttpTryFrom<K>, HeaderValue: HttpTryFrom<V> where HeaderName: HttpTryFrom<K>, HeaderValue: HttpTryFrom<V>
{ {
self.request.header(key, value); self.request.header(key, value);
@ -165,14 +200,13 @@ impl WsClient {
} }
/// Connect to websocket server and do ws handshake /// Connect to websocket server and do ws handshake
pub fn connect(&mut self) -> Result<Box<WsClientFuture>, WsClientError> { pub fn connect(&mut self) -> ClientHandshake {
if let Some(e) = self.err.take() { if let Some(e) = self.err.take() {
return Err(e) ClientHandshake::error(e)
} }
if let Some(e) = self.http_err.take() { else if let Some(e) = self.http_err.take() {
return Err(e.into()) ClientHandshake::error(e.into())
} } else {
// origin // origin
if let Some(origin) = self.origin.take() { if let Some(origin) = self.origin.take() {
self.request.set_header(header::ORIGIN, origin); self.request.set_header(header::ORIGIN, origin);
@ -181,54 +215,51 @@ impl WsClient {
self.request.upgrade(); self.request.upgrade();
self.request.set_header(header::UPGRADE, "websocket"); self.request.set_header(header::UPGRADE, "websocket");
self.request.set_header(header::CONNECTION, "upgrade"); self.request.set_header(header::CONNECTION, "upgrade");
self.request.set_header("SEC-WEBSOCKET-VERSION", "13"); self.request.set_header(header::SEC_WEBSOCKET_VERSION, "13");
self.request.with_connector(self.conn.clone());
if let Some(protocols) = self.protocols.take() { if let Some(protocols) = self.protocols.take() {
self.request.set_header("SEC-WEBSOCKET-PROTOCOL", protocols.as_str()); self.request.set_header(header::SEC_WEBSOCKET_PROTOCOL, protocols.as_str());
} }
let request = self.request.finish()?; let request = match self.request.finish() {
Ok(req) => req,
Err(err) => return ClientHandshake::error(err.into()),
};
if request.uri().host().is_none() { if request.uri().host().is_none() {
return Err(WsClientError::InvalidUrl) return ClientHandshake::error(ClientError::InvalidUrl)
} }
if let Some(scheme) = request.uri().scheme_part() { if let Some(scheme) = request.uri().scheme_part() {
if scheme != "http" && scheme != "https" && scheme != "ws" && scheme != "wss" { if scheme != "http" && scheme != "https" && scheme != "ws" && scheme != "wss" {
return Err(WsClientError::InvalidUrl); return ClientHandshake::error(ClientError::InvalidUrl)
} }
} else { } else {
return Err(WsClientError::InvalidUrl); return ClientHandshake::error(ClientError::InvalidUrl)
} }
// get connection and start handshake // start handshake
Ok(Box::new( ClientHandshake::new(request, self.max_size)
self.conn.send(Connect(request.uri().clone())) }
.map_err(|_| WsClientError::Disconnected)
.and_then(|res| match res {
Ok(stream) => Either::A(WsHandshake::new(stream, request)),
Err(err) => Either::B(FutErr(err.into())),
})
))
} }
} }
struct WsInner { struct Inner {
conn: Connection, tx: UnboundedSender<Bytes>,
writer: HttpClientWriter, rx: PayloadHelper<ClientResponse>,
parser: HttpResponseParser,
parser_buf: BytesMut,
closed: bool, closed: bool,
error_sent: bool,
} }
struct WsHandshake { pub struct ClientHandshake {
inner: Option<WsInner>, request: Option<SendRequest>,
request: ClientRequest, tx: Option<UnboundedSender<Bytes>>,
sent: bool,
key: String, key: String,
error: Option<ClientError>,
max_size: usize,
} }
impl WsHandshake { impl ClientHandshake {
fn new(conn: Connection, mut request: ClientRequest) -> WsHandshake { fn new(mut request: ClientRequest, max_size: usize) -> ClientHandshake
{
// Generate a random key for the `Sec-WebSocket-Key` header. // Generate a random key for the `Sec-WebSocket-Key` header.
// a base64-encoded (see Section 4 of [RFC4648]) value that, // a base64-encoded (see Section 4 of [RFC4648]) value that,
// when decoded, is 16 bytes in length (RFC 6455) // when decoded, is 16 bytes in length (RFC 6455)
@ -236,47 +267,54 @@ impl WsHandshake {
let key = base64::encode(&sec_key); let key = base64::encode(&sec_key);
request.headers_mut().insert( request.headers_mut().insert(
HeaderName::try_from("SEC-WEBSOCKET-KEY").unwrap(), header::SEC_WEBSOCKET_KEY,
HeaderValue::try_from(key.as_str()).unwrap()); HeaderValue::try_from(key.as_str()).unwrap());
let inner = WsInner { let (tx, rx) = unbounded();
conn: conn, request.set_body(Body::Streaming(
writer: HttpClientWriter::new(SharedBytes::default()), Box::new(rx.map_err(|_| io::Error::new(
parser: HttpResponseParser::default(), io::ErrorKind::Other, "disconnected").into()))));
parser_buf: BytesMut::new(),
closed: false,
error_sent: false,
};
WsHandshake { ClientHandshake {
key: key, key,
inner: Some(inner), max_size,
request: request, request: Some(request.send()),
sent: false, tx: Some(tx),
error: None,
}
}
fn error(err: ClientError) -> ClientHandshake {
ClientHandshake {
key: String::new(),
request: None,
tx: None,
error: Some(err),
max_size: 0
} }
} }
} }
impl Future for WsHandshake { impl Future for ClientHandshake {
type Item = (WsClientReader, WsClientWriter); type Item = (ClientReader, ClientWriter);
type Error = WsClientError; type Error = ClientError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let mut inner = self.inner.take().unwrap(); if let Some(err) = self.error.take() {
return Err(err)
if !self.sent {
self.sent = true;
inner.writer.start(&mut self.request)?;
}
if let Err(err) = inner.writer.poll_completed(&mut inner.conn, false) {
return Err(err.into())
} }
match inner.parser.parse(&mut inner.conn, &mut inner.parser_buf) { let resp = match self.request.as_mut().unwrap().poll()? {
Ok(Async::Ready(resp)) => { Async::Ready(response) => {
self.request.take();
response
},
Async::NotReady => return Ok(Async::NotReady)
};
// verify response // verify response
if resp.status() != StatusCode::SWITCHING_PROTOCOLS { if resp.status() != StatusCode::SWITCHING_PROTOCOLS {
return Err(WsClientError::InvalidResponseStatus) return Err(ClientError::InvalidResponseStatus(resp.status()))
} }
// Check for "UPGRADE" to websocket header // Check for "UPGRADE" to websocket header
let has_hdr = if let Some(hdr) = resp.headers().get(header::UPGRADE) { let has_hdr = if let Some(hdr) = resp.headers().get(header::UPGRADE) {
@ -289,20 +327,26 @@ impl Future for WsHandshake {
false false
}; };
if !has_hdr { if !has_hdr {
return Err(WsClientError::InvalidUpgradeHeader) trace!("Invalid upgrade header");
return Err(ClientError::InvalidUpgradeHeader)
} }
// Check for "CONNECTION" header // Check for "CONNECTION" header
let has_hdr = if let Some(conn) = resp.headers().get(header::CONNECTION) { if let Some(conn) = resp.headers().get(header::CONNECTION) {
if let Ok(s) = conn.to_str() { if let Ok(s) = conn.to_str() {
s.to_lowercase().contains("upgrade") if !s.to_lowercase().contains("upgrade") {
} else { false } trace!("Invalid connection header: {}", s);
} else { false }; return Err(ClientError::InvalidConnectionHeader(conn.clone()))
if !has_hdr { }
return Err(WsClientError::InvalidConnectionHeader) } else {
trace!("Invalid connection header: {:?}", conn);
return Err(ClientError::InvalidConnectionHeader(conn.clone()))
}
} else {
trace!("Missing connection header");
return Err(ClientError::MissingConnectionHeader)
} }
let match_key = if let Some(key) = resp.headers().get( if let Some(key) = resp.headers().get(header::SEC_WEBSOCKET_ACCEPT)
HeaderName::try_from("SEC-WEBSOCKET-ACCEPT").unwrap())
{ {
// field is constructed by concatenating /key/ // field is constructed by concatenating /key/
// with the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" (RFC 6455) // with the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" (RFC 6455)
@ -310,81 +354,82 @@ impl Future for WsHandshake {
let mut sha1 = Sha1::new(); let mut sha1 = Sha1::new();
sha1.update(self.key.as_ref()); sha1.update(self.key.as_ref());
sha1.update(WS_GUID); sha1.update(WS_GUID);
key.as_bytes() == base64::encode(&sha1.digest().bytes()).as_bytes() let encoded = base64::encode(&sha1.digest().bytes());
} else { if key.as_bytes() != encoded.as_bytes() {
false trace!(
}; "Invalid challenge response: expected: {} received: {:?}",
if !match_key { encoded, key);
return Err(WsClientError::InvalidChallengeResponse) return Err(ClientError::InvalidChallengeResponse(encoded, key.clone()));
} }
} else {
trace!("Missing SEC-WEBSOCKET-ACCEPT header");
return Err(ClientError::MissingWebSocketAcceptHeader)
};
let inner = Inner {
tx: self.tx.take().unwrap(),
rx: PayloadHelper::new(resp),
closed: false,
};
let inner = Rc::new(UnsafeCell::new(inner)); let inner = Rc::new(UnsafeCell::new(inner));
Ok(Async::Ready( Ok(Async::Ready(
(WsClientReader{inner: Rc::clone(&inner)}, (ClientReader{inner: Rc::clone(&inner), max_size: self.max_size},
WsClientWriter{inner: inner}))) ClientWriter{inner})))
},
Ok(Async::NotReady) => {
self.inner = Some(inner);
Ok(Async::NotReady)
},
Err(err) => Err(err.into())
}
} }
} }
pub struct WsClientReader { pub struct ClientReader {
inner: Rc<UnsafeCell<WsInner>> inner: Rc<UnsafeCell<Inner>>,
max_size: usize,
} }
impl fmt::Debug for WsClientReader { impl fmt::Debug for ClientReader {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "WsClientReader()") write!(f, "ws::ClientReader()")
} }
} }
impl WsClientReader { impl ClientReader {
#[inline] #[inline]
fn as_mut(&mut self) -> &mut WsInner { fn as_mut(&mut self) -> &mut Inner {
unsafe{ &mut *self.inner.get() } unsafe{ &mut *self.inner.get() }
} }
} }
impl Stream for WsClientReader { impl Stream for ClientReader {
type Item = Message; type Item = Message;
type Error = WsClientError; type Error = ProtocolError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> { fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
let max_size = self.max_size;
let inner = self.as_mut(); let inner = self.as_mut();
let mut done = false; if inner.closed {
return Ok(Async::Ready(None))
match utils::read_from_io(&mut inner.conn, &mut inner.parser_buf) {
Ok(Async::Ready(0)) => {
done = true;
inner.closed = true;
},
Ok(Async::Ready(_)) | Ok(Async::NotReady) => (),
Err(err) =>
return Err(err.into())
} }
// write
let _ = inner.writer.poll_completed(&mut inner.conn, false);
// read // read
match Frame::parse(&mut inner.parser_buf, false) { match Frame::parse(&mut inner.rx, false, max_size) {
Ok(Some(frame)) => { Ok(Async::Ready(Some(frame))) => {
// trace!("WsFrame {}", frame); let (finished, opcode, payload) = frame.unpack();
let (_finished, opcode, payload) = frame.unpack();
// continuation is not supported
if !finished {
inner.closed = true;
return Err(ProtocolError::NoContinuation)
}
match opcode { match opcode {
OpCode::Continue => unimplemented!(), OpCode::Continue => unimplemented!(),
OpCode::Bad => OpCode::Bad => {
Ok(Async::Ready(Some(Message::Error))), inner.closed = true;
Err(ProtocolError::BadOpCode)
},
OpCode::Close => { OpCode::Close => {
inner.closed = true; inner.closed = true;
inner.error_sent = true; let code = NetworkEndian::read_uint(payload.as_ref(), 2) as u16;
Ok(Async::Ready(Some(Message::Closed))) Ok(Async::Ready(Some(Message::Close(CloseCode::from(code)))))
}, },
OpCode::Ping => OpCode::Ping =>
Ok(Async::Ready(Some( Ok(Async::Ready(Some(
@ -401,53 +446,42 @@ impl Stream for WsClientReader {
match String::from_utf8(tmp) { match String::from_utf8(tmp) {
Ok(s) => Ok(s) =>
Ok(Async::Ready(Some(Message::Text(s)))), Ok(Async::Ready(Some(Message::Text(s)))),
Err(_) => Err(_) => {
Ok(Async::Ready(Some(Message::Error))),
}
}
}
}
Ok(None) => {
if done {
Ok(Async::Ready(None))
} else if inner.closed {
if !inner.error_sent {
inner.error_sent = true;
Ok(Async::Ready(Some(Message::Closed)))
} else {
Ok(Async::Ready(None))
}
} else {
Ok(Async::NotReady)
}
},
Err(err) => {
inner.closed = true; inner.closed = true;
inner.error_sent = true; Err(ProtocolError::BadEncoding)
Err(err.into()) }
}
}
}
}
Ok(Async::Ready(None)) => Ok(Async::Ready(None)),
Ok(Async::NotReady) => Ok(Async::NotReady),
Err(e) => {
inner.closed = true;
Err(e)
} }
} }
} }
} }
pub struct WsClientWriter { pub struct ClientWriter {
inner: Rc<UnsafeCell<WsInner>> inner: Rc<UnsafeCell<Inner>>
} }
impl WsClientWriter { impl ClientWriter {
#[inline] #[inline]
fn as_mut(&mut self) -> &mut WsInner { fn as_mut(&mut self) -> &mut Inner {
unsafe{ &mut *self.inner.get() } unsafe{ &mut *self.inner.get() }
} }
} }
impl WsClientWriter { impl ClientWriter {
/// Write payload /// Write payload
#[inline] #[inline]
fn write(&mut self, data: Binary) { fn write(&mut self, mut data: Binary) {
if !self.as_mut().closed { if !self.as_mut().closed {
let _ = self.as_mut().writer.write(data); let _ = self.as_mut().tx.unbounded_send(data.take());
} else { } else {
warn!("Trying to write to disconnected response"); warn!("Trying to write to disconnected response");
} }
@ -455,7 +489,7 @@ impl WsClientWriter {
/// Send text frame /// Send text frame
#[inline] #[inline]
pub fn text<T: Into<String>>(&mut self, text: T) { pub fn text<T: Into<Binary>>(&mut self, text: T) {
self.write(Frame::message(text.into(), OpCode::Text, true, true)); self.write(Frame::message(text.into(), OpCode::Text, true, true));
} }

View file

@ -18,7 +18,7 @@ use ws::frame::Frame;
use ws::proto::{OpCode, CloseCode}; use ws::proto::{OpCode, CloseCode};
/// Http actor execution context /// `WebSockets` actor execution context
pub struct WebsocketContext<A, S=()> where A: Actor<Context=WebsocketContext<A, S>>, pub struct WebsocketContext<A, S=()> where A: Actor<Context=WebsocketContext<A, S>>,
{ {
inner: ContextImpl<A>, inner: ContextImpl<A>,
@ -112,6 +112,7 @@ impl<A, S> WebsocketContext<A, S> where A: Actor<Context=Self> {
} }
let stream = self.stream.as_mut().unwrap(); let stream = self.stream.as_mut().unwrap();
stream.push(ContextFrame::Chunk(Some(data))); stream.push(ContextFrame::Chunk(Some(data)));
self.inner.modify();
} else { } else {
warn!("Trying to write to disconnected response"); warn!("Trying to write to disconnected response");
} }
@ -131,7 +132,7 @@ impl<A, S> WebsocketContext<A, S> where A: Actor<Context=Self> {
/// Send text frame /// Send text frame
#[inline] #[inline]
pub fn text<T: Into<String>>(&mut self, text: T) { pub fn text<T: Into<Binary>>(&mut self, text: T) {
self.write(Frame::message(text.into(), OpCode::Text, true, false)); self.write(Frame::message(text.into(), OpCode::Text, true, false));
} }
@ -179,6 +180,7 @@ impl<A, S> WebsocketContext<A, S> where A: Actor<Context=Self> {
self.stream = Some(SmallVec::new()); self.stream = Some(SmallVec::new());
} }
self.stream.as_mut().map(|s| s.push(frame)); self.stream.as_mut().map(|s| s.push(frame));
self.inner.modify();
} }
/// Handle of the running future /// Handle of the running future

View file

@ -1,11 +1,15 @@
use std::{fmt, mem}; use std::{fmt, mem};
use std::io::{Error, ErrorKind};
use std::iter::FromIterator; use std::iter::FromIterator;
use bytes::{BytesMut, BufMut}; use bytes::{Bytes, BytesMut, BufMut};
use byteorder::{ByteOrder, BigEndian, NetworkEndian}; use byteorder::{ByteOrder, BigEndian, NetworkEndian};
use futures::{Async, Poll, Stream};
use rand; use rand;
use body::Binary; use body::Binary;
use error::{PayloadError};
use payload::PayloadHelper;
use ws::ProtocolError;
use ws::proto::{OpCode, CloseCode}; use ws::proto::{OpCode, CloseCode};
use ws::mask::apply_mask; use ws::mask::apply_mask;
@ -48,14 +52,16 @@ impl Frame {
} }
/// Parse the input stream into a frame. /// Parse the input stream into a frame.
pub fn parse(buf: &mut BytesMut, server: bool) -> Result<Option<Frame>, Error> { pub fn parse<S>(pl: &mut PayloadHelper<S>, server: bool, max_size: usize)
-> Poll<Option<Frame>, ProtocolError>
where S: Stream<Item=Bytes, Error=PayloadError>
{
let mut idx = 2; let mut idx = 2;
let mut size = buf.len(); let buf = match pl.copy(2)? {
Async::Ready(Some(buf)) => buf,
if size < 2 { Async::Ready(None) => return Ok(Async::Ready(None)),
return Ok(None) Async::NotReady => return Ok(Async::NotReady),
} };
size -= 2;
let first = buf[0]; let first = buf[0];
let second = buf[1]; let second = buf[1];
let finished = first & 0x80 != 0; let finished = first & 0x80 != 0;
@ -63,11 +69,9 @@ impl Frame {
// check masking // check masking
let masked = second & 0x80 != 0; let masked = second & 0x80 != 0;
if !masked && server { if !masked && server {
return Err(Error::new( return Err(ProtocolError::UnmaskedFrame)
ErrorKind::Other, "Received an unmasked frame from client"))
} else if masked && !server { } else if masked && !server {
return Err(Error::new( return Err(ProtocolError::MaskedFrame)
ErrorKind::Other, "Received a masked frame from server"))
} }
let rsv1 = first & 0x40 != 0; let rsv1 = first & 0x40 != 0;
@ -77,70 +81,69 @@ impl Frame {
let len = second & 0x7F; let len = second & 0x7F;
let length = if len == 126 { let length = if len == 126 {
if size < 2 { let buf = match pl.copy(4)? {
return Ok(None) Async::Ready(Some(buf)) => buf,
} Async::Ready(None) => return Ok(Async::Ready(None)),
Async::NotReady => return Ok(Async::NotReady),
};
let len = NetworkEndian::read_uint(&buf[idx..], 2) as usize; let len = NetworkEndian::read_uint(&buf[idx..], 2) as usize;
size -= 2;
idx += 2; idx += 2;
len len
} else if len == 127 { } else if len == 127 {
if size < 8 { let buf = match pl.copy(10)? {
return Ok(None) Async::Ready(Some(buf)) => buf,
} Async::Ready(None) => return Ok(Async::Ready(None)),
Async::NotReady => return Ok(Async::NotReady),
};
let len = NetworkEndian::read_uint(&buf[idx..], 8) as usize; let len = NetworkEndian::read_uint(&buf[idx..], 8) as usize;
size -= 8;
idx += 8; idx += 8;
len len
} else { } else {
len as usize len as usize
}; };
// check for max allowed size
if length > max_size {
return Err(ProtocolError::Overflow)
}
let mask = if server { let mask = if server {
if size < 4 { let buf = match pl.copy(idx + 4)? {
return Ok(None) Async::Ready(Some(buf)) => buf,
} else { Async::Ready(None) => return Ok(Async::Ready(None)),
Async::NotReady => return Ok(Async::NotReady),
};
let mut mask_bytes = [0u8; 4]; let mut mask_bytes = [0u8; 4];
size -= 4;
mask_bytes.copy_from_slice(&buf[idx..idx+4]); mask_bytes.copy_from_slice(&buf[idx..idx+4]);
idx += 4; idx += 4;
Some(mask_bytes) Some(mask_bytes)
}
} else { } else {
None None
}; };
if size < length { let mut data = match pl.readexactly(idx + length)? {
return Ok(None) Async::Ready(Some(buf)) => buf,
} Async::Ready(None) => return Ok(Async::Ready(None)),
Async::NotReady => return Ok(Async::NotReady),
};
// get body // get body
buf.split_to(idx); data.split_to(idx);
let mut data = if length > 0 {
buf.split_to(length)
} else {
BytesMut::new()
};
// Disallow bad opcode // Disallow bad opcode
if let OpCode::Bad = opcode { if let OpCode::Bad = opcode {
return Err( return Err(ProtocolError::InvalidOpcode(first & 0x0F))
Error::new(
ErrorKind::Other,
format!("Encountered invalid opcode: {}", first & 0x0F)))
} }
// control frames must have length <= 125 // control frames must have length <= 125
match opcode { match opcode {
OpCode::Ping | OpCode::Pong if length > 125 => { OpCode::Ping | OpCode::Pong if length > 125 => {
return Err( return Err(ProtocolError::InvalidLength(length))
Error::new(
ErrorKind::Other,
format!("Rejected WebSocket handshake.Received control frame with length: {}.", length)))
} }
OpCode::Close if length > 125 => { OpCode::Close if length > 125 => {
debug!("Received close frame with payload length exceeding 125. Morphing to protocol close frame."); debug!("Received close frame with payload length exceeding 125. Morphing to protocol close frame.");
return Ok(Some(Frame::default())) return Ok(Async::Ready(Some(Frame::default())))
} }
_ => () _ => ()
} }
@ -150,14 +153,8 @@ impl Frame {
apply_mask(&mut data, mask); apply_mask(&mut data, mask);
} }
Ok(Some(Frame { Ok(Async::Ready(Some(Frame {
finished: finished, finished, rsv1, rsv2, rsv3, opcode, payload: data.into() })))
rsv1: rsv1,
rsv2: rsv2,
rsv3: rsv3,
opcode: opcode,
payload: data.into(),
}))
} }
/// Generate binary representation /// Generate binary representation
@ -191,7 +188,7 @@ impl Frame {
unsafe{buf.advance_mut(2)}; unsafe{buf.advance_mut(2)};
buf buf
} else { } else {
let mut buf = BytesMut::with_capacity(p_len + 8); let mut buf = BytesMut::with_capacity(p_len + 10);
buf.put_slice(&[one, two | 127]); buf.put_slice(&[one, two | 127]);
{ {
let buf_mut = unsafe{buf.bytes_mut()}; let buf_mut = unsafe{buf.bytes_mut()};
@ -258,13 +255,33 @@ impl fmt::Display for Frame {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use futures::stream::once;
fn is_none(frm: Poll<Option<Frame>, ProtocolError>) -> bool {
match frm {
Ok(Async::Ready(None)) => true,
_ => false,
}
}
fn extract(frm: Poll<Option<Frame>, ProtocolError>) -> Frame {
match frm {
Ok(Async::Ready(Some(frame))) => frame,
_ => panic!("error"),
}
}
#[test] #[test]
fn test_parse() { fn test_parse() {
let mut buf = PayloadHelper::new(
once(Ok(BytesMut::from(&[0b00000001u8, 0b00000001u8][..]).freeze())));
assert!(is_none(Frame::parse(&mut buf, false, 1024)));
let mut buf = BytesMut::from(&[0b00000001u8, 0b00000001u8][..]); let mut buf = BytesMut::from(&[0b00000001u8, 0b00000001u8][..]);
assert!(Frame::parse(&mut buf, false).unwrap().is_none());
buf.extend(b"1"); buf.extend(b"1");
let frame = Frame::parse(&mut buf, false).unwrap().unwrap(); let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
let frame = extract(Frame::parse(&mut buf, false, 1024));
println!("FRAME: {}", frame); println!("FRAME: {}", frame);
assert!(!frame.finished); assert!(!frame.finished);
assert_eq!(frame.opcode, OpCode::Text); assert_eq!(frame.opcode, OpCode::Text);
@ -273,8 +290,10 @@ mod tests {
#[test] #[test]
fn test_parse_length0() { fn test_parse_length0() {
let mut buf = BytesMut::from(&[0b00000001u8, 0b00000000u8][..]); let buf = BytesMut::from(&[0b00000001u8, 0b00000000u8][..]);
let frame = Frame::parse(&mut buf, false).unwrap().unwrap(); let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
let frame = extract(Frame::parse(&mut buf, false, 1024));
assert!(!frame.finished); assert!(!frame.finished);
assert_eq!(frame.opcode, OpCode::Text); assert_eq!(frame.opcode, OpCode::Text);
assert!(frame.payload.is_empty()); assert!(frame.payload.is_empty());
@ -282,12 +301,16 @@ mod tests {
#[test] #[test]
fn test_parse_length2() { fn test_parse_length2() {
let buf = BytesMut::from(&[0b00000001u8, 126u8][..]);
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
assert!(is_none(Frame::parse(&mut buf, false, 1024)));
let mut buf = BytesMut::from(&[0b00000001u8, 126u8][..]); let mut buf = BytesMut::from(&[0b00000001u8, 126u8][..]);
assert!(Frame::parse(&mut buf, false).unwrap().is_none());
buf.extend(&[0u8, 4u8][..]); buf.extend(&[0u8, 4u8][..]);
buf.extend(b"1234"); buf.extend(b"1234");
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
let frame = Frame::parse(&mut buf, false).unwrap().unwrap(); let frame = extract(Frame::parse(&mut buf, false, 1024));
assert!(!frame.finished); assert!(!frame.finished);
assert_eq!(frame.opcode, OpCode::Text); assert_eq!(frame.opcode, OpCode::Text);
assert_eq!(frame.payload.as_ref(), &b"1234"[..]); assert_eq!(frame.payload.as_ref(), &b"1234"[..]);
@ -295,12 +318,16 @@ mod tests {
#[test] #[test]
fn test_parse_length4() { fn test_parse_length4() {
let buf = BytesMut::from(&[0b00000001u8, 127u8][..]);
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
assert!(is_none(Frame::parse(&mut buf, false, 1024)));
let mut buf = BytesMut::from(&[0b00000001u8, 127u8][..]); let mut buf = BytesMut::from(&[0b00000001u8, 127u8][..]);
assert!(Frame::parse(&mut buf, false).unwrap().is_none());
buf.extend(&[0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 4u8][..]); buf.extend(&[0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 4u8][..]);
buf.extend(b"1234"); buf.extend(b"1234");
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
let frame = Frame::parse(&mut buf, false).unwrap().unwrap(); let frame = extract(Frame::parse(&mut buf, false, 1024));
assert!(!frame.finished); assert!(!frame.finished);
assert_eq!(frame.opcode, OpCode::Text); assert_eq!(frame.opcode, OpCode::Text);
assert_eq!(frame.payload.as_ref(), &b"1234"[..]); assert_eq!(frame.payload.as_ref(), &b"1234"[..]);
@ -311,10 +338,11 @@ mod tests {
let mut buf = BytesMut::from(&[0b00000001u8, 0b10000001u8][..]); let mut buf = BytesMut::from(&[0b00000001u8, 0b10000001u8][..]);
buf.extend(b"0001"); buf.extend(b"0001");
buf.extend(b"1"); buf.extend(b"1");
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
assert!(Frame::parse(&mut buf, false).is_err()); assert!(Frame::parse(&mut buf, false, 1024).is_err());
let frame = Frame::parse(&mut buf, true).unwrap().unwrap(); let frame = extract(Frame::parse(&mut buf, true, 1024));
assert!(!frame.finished); assert!(!frame.finished);
assert_eq!(frame.opcode, OpCode::Text); assert_eq!(frame.opcode, OpCode::Text);
assert_eq!(frame.payload, vec![1u8].into()); assert_eq!(frame.payload, vec![1u8].into());
@ -324,15 +352,30 @@ mod tests {
fn test_parse_frame_no_mask() { fn test_parse_frame_no_mask() {
let mut buf = BytesMut::from(&[0b00000001u8, 0b00000001u8][..]); let mut buf = BytesMut::from(&[0b00000001u8, 0b00000001u8][..]);
buf.extend(&[1u8]); buf.extend(&[1u8]);
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
assert!(Frame::parse(&mut buf, true).is_err()); assert!(Frame::parse(&mut buf, true, 1024).is_err());
let frame = Frame::parse(&mut buf, false).unwrap().unwrap(); let frame = extract(Frame::parse(&mut buf, false, 1024));
assert!(!frame.finished); assert!(!frame.finished);
assert_eq!(frame.opcode, OpCode::Text); assert_eq!(frame.opcode, OpCode::Text);
assert_eq!(frame.payload, vec![1u8].into()); assert_eq!(frame.payload, vec![1u8].into());
} }
#[test]
fn test_parse_frame_max_size() {
let mut buf = BytesMut::from(&[0b00000001u8, 0b00000010u8][..]);
buf.extend(&[1u8, 1u8]);
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
assert!(Frame::parse(&mut buf, true, 1).is_err());
if let Err(ProtocolError::Overflow) = Frame::parse(&mut buf, false, 0) {
} else {
panic!("error");
}
}
#[test] #[test]
fn test_ping_frame() { fn test_ping_frame() {
let frame = Frame::message(Vec::from("data"), OpCode::Ping, true, false); let frame = Frame::message(Vec::from("data"), OpCode::Ping, true, false);

View file

@ -23,9 +23,8 @@
//! type Context = ws::WebsocketContext<Self>; //! type Context = ws::WebsocketContext<Self>;
//! } //! }
//! //!
//! // Define Handler for ws::Message message //! // Handler for ws::Message messages
//! impl Handler<ws::Message> for Ws { //! impl StreamHandler<ws::Message, ws::WsError> for Ws {
//! type Result = ();
//! //!
//! fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { //! fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
//! match msg { //! match msg {
@ -43,17 +42,20 @@
//! # .finish(); //! # .finish();
//! # } //! # }
//! ``` //! ```
use bytes::BytesMut; use bytes::Bytes;
use http::{Method, StatusCode, header}; use http::{Method, StatusCode, header};
use futures::{Async, Poll, Stream}; use futures::{Async, Poll, Stream};
use byteorder::{ByteOrder, NetworkEndian};
use actix::{Actor, AsyncContext, Handler}; use actix::{Actor, AsyncContext, StreamHandler};
use body::Binary; use body::Binary;
use payload::ReadAny; use payload::PayloadHelper;
use error::{Error, WsHandshakeError}; use error::{Error, PayloadError, ResponseError};
use httpmessage::HttpMessage;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::{ConnectionType, HttpResponse, HttpResponseBuilder}; use httpresponse::{ConnectionType, HttpResponse, HttpResponseBuilder};
use httpcodes::{HttpBadRequest, HttpMethodNotAllowed};
mod frame; mod frame;
mod proto; mod proto;
@ -65,13 +67,108 @@ use self::frame::Frame;
use self::proto::{hash_key, OpCode}; use self::proto::{hash_key, OpCode};
pub use self::proto::CloseCode; pub use self::proto::CloseCode;
pub use self::context::WebsocketContext; pub use self::context::WebsocketContext;
pub use self::client::{WsClient, WsClientError, WsClientReader, WsClientWriter, WsClientFuture}; pub use self::client::{Client, ClientError,
ClientReader, ClientWriter, ClientHandshake};
const SEC_WEBSOCKET_ACCEPT: &str = "SEC-WEBSOCKET-ACCEPT"; #[allow(deprecated)]
const SEC_WEBSOCKET_KEY: &str = "SEC-WEBSOCKET-KEY"; pub use self::client::{WsClient, WsClientError,
const SEC_WEBSOCKET_VERSION: &str = "SEC-WEBSOCKET-VERSION"; WsClientReader, WsClientWriter, WsClientHandshake};
// const SEC_WEBSOCKET_PROTOCOL: &'static str = "SEC-WEBSOCKET-PROTOCOL";
/// Backward compatibility
#[doc(hidden)]
#[deprecated(since="0.4.2", note="please use `ws::ProtocolError` instead")]
pub type WsError = ProtocolError;
#[doc(hidden)]
#[deprecated(since="0.4.2", note="please use `ws::HandshakeError` instead")]
pub type WsHandshakeError = HandshakeError;
/// Websocket errors
#[derive(Fail, Debug)]
pub enum ProtocolError {
/// Received an unmasked frame from client
#[fail(display="Received an unmasked frame from client")]
UnmaskedFrame,
/// Received a masked frame from server
#[fail(display="Received a masked frame from server")]
MaskedFrame,
/// Encountered invalid opcode
#[fail(display="Invalid opcode: {}", _0)]
InvalidOpcode(u8),
/// Invalid control frame length
#[fail(display="Invalid control frame length: {}", _0)]
InvalidLength(usize),
/// Bad web socket op code
#[fail(display="Bad web socket op code")]
BadOpCode,
/// A payload reached size limit.
#[fail(display="A payload reached size limit.")]
Overflow,
/// Continuation is not supproted
#[fail(display="Continuation is not supproted.")]
NoContinuation,
/// Bad utf-8 encoding
#[fail(display="Bad utf-8 encoding.")]
BadEncoding,
/// Payload error
#[fail(display="Payload error: {}", _0)]
Payload(#[cause] PayloadError),
}
impl ResponseError for ProtocolError {}
impl From<PayloadError> for ProtocolError {
fn from(err: PayloadError) -> ProtocolError {
ProtocolError::Payload(err)
}
}
/// Websocket handshake errors
#[derive(Fail, PartialEq, Debug)]
pub enum HandshakeError {
/// Only get method is allowed
#[fail(display="Method not allowed")]
GetMethodRequired,
/// Upgrade header if not set to websocket
#[fail(display="Websocket upgrade is expected")]
NoWebsocketUpgrade,
/// Connection header is not set to upgrade
#[fail(display="Connection upgrade is expected")]
NoConnectionUpgrade,
/// Websocket version header is not set
#[fail(display="Websocket version header is required")]
NoVersionHeader,
/// Unsupported websocket version
#[fail(display="Unsupported version")]
UnsupportedVersion,
/// Websocket key is not set or wrong
#[fail(display="Unknown websocket key")]
BadWebsocketKey,
}
impl ResponseError for HandshakeError {
fn error_response(&self) -> HttpResponse {
match *self {
HandshakeError::GetMethodRequired => {
HttpMethodNotAllowed
.build()
.header(header::ALLOW, "GET")
.finish()
.unwrap()
}
HandshakeError::NoWebsocketUpgrade =>
HttpBadRequest.with_reason("No WebSocket UPGRADE header found"),
HandshakeError::NoConnectionUpgrade =>
HttpBadRequest.with_reason("No CONNECTION upgrade"),
HandshakeError::NoVersionHeader =>
HttpBadRequest.with_reason("Websocket version header is required"),
HandshakeError::UnsupportedVersion =>
HttpBadRequest.with_reason("Unsupported version"),
HandshakeError::BadWebsocketKey =>
HttpBadRequest.with_reason("Handshake error"),
}
}
}
/// `WebSocket` Message /// `WebSocket` Message
#[derive(Debug, PartialEq, Message)] #[derive(Debug, PartialEq, Message)]
@ -80,21 +177,19 @@ pub enum Message {
Binary(Binary), Binary(Binary),
Ping(String), Ping(String),
Pong(String), Pong(String),
Close, Close(CloseCode),
Closed,
Error
} }
/// 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>(req: HttpRequest<S>, actor: A) -> Result<HttpResponse, Error>
where A: Actor<Context=WebsocketContext<A, S>> + Handler<Message>, where A: Actor<Context=WebsocketContext<A, S>> + StreamHandler<Message, ProtocolError>,
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.clone());
let mut ctx = WebsocketContext::new(req, actor); let mut ctx = WebsocketContext::new(req, actor);
ctx.add_message_stream(stream); ctx.add_stream(stream);
Ok(resp.body(ctx)?) Ok(resp.body(ctx)?)
} }
@ -107,10 +202,10 @@ pub fn start<A, S>(mut req: HttpRequest<S>, actor: A) -> Result<HttpResponse, Er
// /// `protocols` is a sequence of known protocols. On successful handshake, // /// `protocols` is a sequence of known protocols. On successful handshake,
// /// the returned response headers contain the first protocol in this list // /// the returned response headers contain the first protocol in this list
// /// which the server also knows. // /// which the server also knows.
pub fn handshake<S>(req: &HttpRequest<S>) -> Result<HttpResponseBuilder, WsHandshakeError> { pub fn handshake<S>(req: &HttpRequest<S>) -> Result<HttpResponseBuilder, HandshakeError> {
// WebSocket accepts only GET // WebSocket accepts only GET
if *req.method() != Method::GET { if *req.method() != Method::GET {
return Err(WsHandshakeError::GetMethodRequired) return Err(HandshakeError::GetMethodRequired)
} }
// Check for "UPGRADE" to websocket header // Check for "UPGRADE" to websocket header
@ -124,35 +219,35 @@ pub fn handshake<S>(req: &HttpRequest<S>) -> Result<HttpResponseBuilder, WsHands
false false
}; };
if !has_hdr { if !has_hdr {
return Err(WsHandshakeError::NoWebsocketUpgrade) return Err(HandshakeError::NoWebsocketUpgrade)
} }
// Upgrade connection // Upgrade connection
if !req.upgrade() { if !req.upgrade() {
return Err(WsHandshakeError::NoConnectionUpgrade) return Err(HandshakeError::NoConnectionUpgrade)
} }
// check supported version // check supported version
if !req.headers().contains_key(SEC_WEBSOCKET_VERSION) { if !req.headers().contains_key(header::SEC_WEBSOCKET_VERSION) {
return Err(WsHandshakeError::NoVersionHeader) return Err(HandshakeError::NoVersionHeader)
} }
let supported_ver = { let supported_ver = {
if let Some(hdr) = req.headers().get(SEC_WEBSOCKET_VERSION) { if let Some(hdr) = req.headers().get(header::SEC_WEBSOCKET_VERSION) {
hdr == "13" || hdr == "8" || hdr == "7" hdr == "13" || hdr == "8" || hdr == "7"
} else { } else {
false false
} }
}; };
if !supported_ver { if !supported_ver {
return Err(WsHandshakeError::UnsupportedVersion) return Err(HandshakeError::UnsupportedVersion)
} }
// check client handshake for validity // check client handshake for validity
if !req.headers().contains_key(SEC_WEBSOCKET_KEY) { if !req.headers().contains_key(header::SEC_WEBSOCKET_KEY) {
return Err(WsHandshakeError::BadWebsocketKey) return Err(HandshakeError::BadWebsocketKey)
} }
let key = { let key = {
let key = req.headers().get(SEC_WEBSOCKET_KEY).unwrap(); let key = req.headers().get(header::SEC_WEBSOCKET_KEY).unwrap();
hash_key(key.as_ref()) hash_key(key.as_ref())
}; };
@ -160,112 +255,96 @@ pub fn handshake<S>(req: &HttpRequest<S>) -> Result<HttpResponseBuilder, WsHands
.connection_type(ConnectionType::Upgrade) .connection_type(ConnectionType::Upgrade)
.header(header::UPGRADE, "websocket") .header(header::UPGRADE, "websocket")
.header(header::TRANSFER_ENCODING, "chunked") .header(header::TRANSFER_ENCODING, "chunked")
.header(SEC_WEBSOCKET_ACCEPT, key.as_str()) .header(header::SEC_WEBSOCKET_ACCEPT, key.as_str())
.take()) .take())
} }
/// Maps `Payload` stream into stream of `ws::Message` items /// Maps `Payload` stream into stream of `ws::Message` items
pub struct WsStream { pub struct WsStream<S> {
rx: ReadAny, rx: PayloadHelper<S>,
buf: BytesMut,
closed: bool, closed: bool,
error_sent: bool, max_size: usize,
} }
impl WsStream { impl<S> WsStream<S> where S: Stream<Item=Bytes, Error=PayloadError> {
pub fn new(payload: ReadAny) -> WsStream { /// Create new websocket frames stream
WsStream { rx: payload, pub fn new(stream: S) -> WsStream<S> {
buf: BytesMut::new(), WsStream { rx: PayloadHelper::new(stream),
closed: false, closed: false,
error_sent: false } max_size: 65_536,
}
}
/// Set max frame size
///
/// By default max size is set to 64kb
pub fn max_size(mut self, size: usize) -> Self {
self.max_size = size;
self
} }
} }
impl Stream for WsStream { impl<S> Stream for WsStream<S> where S: Stream<Item=Bytes, Error=PayloadError> {
type Item = Message; type Item = Message;
type Error = (); type Error = ProtocolError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> { fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
let mut done = false; if self.closed {
return Ok(Async::Ready(None))
if !self.closed {
loop {
match self.rx.poll() {
Ok(Async::Ready(Some(chunk))) => {
self.buf.extend_from_slice(&chunk)
}
Ok(Async::Ready(None)) => {
done = true;
self.closed = true;
break;
}
Ok(Async::NotReady) => break,
Err(_) => {
self.closed = true;
break;
}
}
}
} }
loop { match Frame::parse(&mut self.rx, true, self.max_size) {
match Frame::parse(&mut self.buf, true) { Ok(Async::Ready(Some(frame))) => {
Ok(Some(frame)) => { let (finished, opcode, payload) = frame.unpack();
// trace!("WsFrame {}", frame);
let (_finished, opcode, payload) = frame.unpack(); // continuation is not supported
if !finished {
self.closed = true;
return Err(ProtocolError::NoContinuation)
}
match opcode { match opcode {
OpCode::Continue => continue, OpCode::Continue => unimplemented!(),
OpCode::Bad => OpCode::Bad => {
return Ok(Async::Ready(Some(Message::Error))), self.closed = true;
Err(ProtocolError::BadOpCode)
}
OpCode::Close => { OpCode::Close => {
self.closed = true; self.closed = true;
self.error_sent = true; let code = NetworkEndian::read_uint(payload.as_ref(), 2) as u16;
return Ok(Async::Ready(Some(Message::Closed))) Ok(Async::Ready(
Some(Message::Close(CloseCode::from(code)))))
}, },
OpCode::Ping => OpCode::Ping =>
return Ok(Async::Ready(Some( Ok(Async::Ready(Some(
Message::Ping( Message::Ping(
String::from_utf8_lossy(payload.as_ref()).into())))), String::from_utf8_lossy(payload.as_ref()).into())))),
OpCode::Pong => OpCode::Pong =>
return Ok(Async::Ready(Some( Ok(Async::Ready(Some(
Message::Pong( Message::Pong(String::from_utf8_lossy(payload.as_ref()).into())))),
String::from_utf8_lossy(payload.as_ref()).into())))),
OpCode::Binary => OpCode::Binary =>
return Ok(Async::Ready(Some(Message::Binary(payload)))), Ok(Async::Ready(Some(Message::Binary(payload)))),
OpCode::Text => { OpCode::Text => {
let tmp = Vec::from(payload.as_ref()); let tmp = Vec::from(payload.as_ref());
match String::from_utf8(tmp) { match String::from_utf8(tmp) {
Ok(s) => Ok(s) =>
return Ok(Async::Ready(Some(Message::Text(s)))), Ok(Async::Ready(Some(Message::Text(s)))),
Err(_) =>
return Ok(Async::Ready(Some(Message::Error))),
}
}
}
}
Ok(None) => {
if done {
return Ok(Async::Ready(None))
} else if self.closed {
if !self.error_sent {
self.error_sent = true;
return Ok(Async::Ready(Some(Message::Closed)))
} else {
return Ok(Async::Ready(None))
}
} else {
return Ok(Async::NotReady)
}
},
Err(_) => { Err(_) => {
self.closed = true; self.closed = true;
self.error_sent = true; Err(ProtocolError::BadEncoding)
return Ok(Async::Ready(Some(Message::Error)));
} }
} }
} }
} }
}
Ok(Async::Ready(None)) => Ok(Async::Ready(None)),
Ok(Async::NotReady) => Ok(Async::NotReady),
Err(e) => {
self.closed = true;
Err(e)
}
}
}
} }
#[cfg(test)] #[cfg(test)]
@ -278,25 +357,25 @@ mod tests {
fn test_handshake() { fn test_handshake() {
let req = HttpRequest::new(Method::POST, Uri::from_str("/").unwrap(), let req = HttpRequest::new(Method::POST, Uri::from_str("/").unwrap(),
Version::HTTP_11, HeaderMap::new(), None); Version::HTTP_11, HeaderMap::new(), None);
assert_eq!(WsHandshakeError::GetMethodRequired, handshake(&req).err().unwrap()); assert_eq!(HandshakeError::GetMethodRequired, handshake(&req).err().unwrap());
let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(),
Version::HTTP_11, HeaderMap::new(), None); Version::HTTP_11, HeaderMap::new(), None);
assert_eq!(WsHandshakeError::NoWebsocketUpgrade, handshake(&req).err().unwrap()); assert_eq!(HandshakeError::NoWebsocketUpgrade, handshake(&req).err().unwrap());
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();
headers.insert(header::UPGRADE, headers.insert(header::UPGRADE,
header::HeaderValue::from_static("test")); header::HeaderValue::from_static("test"));
let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(),
Version::HTTP_11, headers, None); Version::HTTP_11, headers, None);
assert_eq!(WsHandshakeError::NoWebsocketUpgrade, handshake(&req).err().unwrap()); assert_eq!(HandshakeError::NoWebsocketUpgrade, handshake(&req).err().unwrap());
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();
headers.insert(header::UPGRADE, headers.insert(header::UPGRADE,
header::HeaderValue::from_static("websocket")); header::HeaderValue::from_static("websocket"));
let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(),
Version::HTTP_11, headers, None); Version::HTTP_11, headers, None);
assert_eq!(WsHandshakeError::NoConnectionUpgrade, handshake(&req).err().unwrap()); assert_eq!(HandshakeError::NoConnectionUpgrade, handshake(&req).err().unwrap());
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();
headers.insert(header::UPGRADE, headers.insert(header::UPGRADE,
@ -305,42 +384,58 @@ mod tests {
header::HeaderValue::from_static("upgrade")); header::HeaderValue::from_static("upgrade"));
let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(),
Version::HTTP_11, headers, None); Version::HTTP_11, headers, None);
assert_eq!(WsHandshakeError::NoVersionHeader, handshake(&req).err().unwrap()); assert_eq!(HandshakeError::NoVersionHeader, handshake(&req).err().unwrap());
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();
headers.insert(header::UPGRADE, headers.insert(header::UPGRADE,
header::HeaderValue::from_static("websocket")); header::HeaderValue::from_static("websocket"));
headers.insert(header::CONNECTION, headers.insert(header::CONNECTION,
header::HeaderValue::from_static("upgrade")); header::HeaderValue::from_static("upgrade"));
headers.insert(SEC_WEBSOCKET_VERSION, headers.insert(header::SEC_WEBSOCKET_VERSION,
header::HeaderValue::from_static("5")); header::HeaderValue::from_static("5"));
let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(),
Version::HTTP_11, headers, None); Version::HTTP_11, headers, None);
assert_eq!(WsHandshakeError::UnsupportedVersion, handshake(&req).err().unwrap()); assert_eq!(HandshakeError::UnsupportedVersion, handshake(&req).err().unwrap());
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();
headers.insert(header::UPGRADE, headers.insert(header::UPGRADE,
header::HeaderValue::from_static("websocket")); header::HeaderValue::from_static("websocket"));
headers.insert(header::CONNECTION, headers.insert(header::CONNECTION,
header::HeaderValue::from_static("upgrade")); header::HeaderValue::from_static("upgrade"));
headers.insert(SEC_WEBSOCKET_VERSION, headers.insert(header::SEC_WEBSOCKET_VERSION,
header::HeaderValue::from_static("13")); header::HeaderValue::from_static("13"));
let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(),
Version::HTTP_11, headers, None); Version::HTTP_11, headers, None);
assert_eq!(WsHandshakeError::BadWebsocketKey, handshake(&req).err().unwrap()); assert_eq!(HandshakeError::BadWebsocketKey, handshake(&req).err().unwrap());
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();
headers.insert(header::UPGRADE, headers.insert(header::UPGRADE,
header::HeaderValue::from_static("websocket")); header::HeaderValue::from_static("websocket"));
headers.insert(header::CONNECTION, headers.insert(header::CONNECTION,
header::HeaderValue::from_static("upgrade")); header::HeaderValue::from_static("upgrade"));
headers.insert(SEC_WEBSOCKET_VERSION, headers.insert(header::SEC_WEBSOCKET_VERSION,
header::HeaderValue::from_static("13")); header::HeaderValue::from_static("13"));
headers.insert(SEC_WEBSOCKET_KEY, headers.insert(header::SEC_WEBSOCKET_KEY,
header::HeaderValue::from_static("13")); header::HeaderValue::from_static("13"));
let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(),
Version::HTTP_11, headers, None); Version::HTTP_11, headers, None);
assert_eq!(StatusCode::SWITCHING_PROTOCOLS, assert_eq!(StatusCode::SWITCHING_PROTOCOLS,
handshake(&req).unwrap().finish().unwrap().status()); handshake(&req).unwrap().finish().unwrap().status());
} }
#[test]
fn test_wserror_http_response() {
let resp: HttpResponse = HandshakeError::GetMethodRequired.error_response();
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
let resp: HttpResponse = HandshakeError::NoWebsocketUpgrade.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp: HttpResponse = HandshakeError::NoConnectionUpgrade.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp: HttpResponse = HandshakeError::NoVersionHeader.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp: HttpResponse = HandshakeError::UnsupportedVersion.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp: HttpResponse = HandshakeError::BadWebsocketKey.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
}
} }

View file

@ -2,8 +2,14 @@ extern crate actix;
extern crate actix_web; extern crate actix_web;
extern crate bytes; extern crate bytes;
extern crate futures; extern crate futures;
extern crate flate2;
use std::io::Read;
use bytes::Bytes; use bytes::Bytes;
use futures::Future;
use futures::stream::once;
use flate2::read::GzDecoder;
use actix_web::*; use actix_web::*;
@ -57,3 +63,171 @@ fn test_simple() {
let bytes = srv.execute(response.body()).unwrap(); let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref())); assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
} }
#[test]
fn test_no_decompress() {
let mut srv = test::TestServer::new(
|app| app.handler(|_| httpcodes::HTTPOk.build().body(STR)));
let request = srv.get().disable_decompress().finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
let mut e = GzDecoder::new(&bytes[..]);
let mut dec = Vec::new();
e.read_to_end(&mut dec).unwrap();
assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref()));
// POST
let request = srv.post().disable_decompress().finish().unwrap();
let response = srv.execute(request.send()).unwrap();
let bytes = srv.execute(response.body()).unwrap();
let mut e = GzDecoder::new(&bytes[..]);
let mut dec = Vec::new();
e.read_to_end(&mut dec).unwrap();
assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref()));
}
#[test]
fn test_client_gzip_encoding() {
let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(httpcodes::HTTPOk
.build()
.content_encoding(headers::ContentEncoding::Deflate)
.body(bytes))
}).responder()}
));
// client request
let request = srv.post()
.content_encoding(headers::ContentEncoding::Gzip)
.body(STR).unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
}
#[test]
fn test_client_gzip_encoding_large() {
let data = STR.repeat(10);
let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(httpcodes::HTTPOk
.build()
.content_encoding(headers::ContentEncoding::Deflate)
.body(bytes))
}).responder()}
));
// client request
let request = srv.post()
.content_encoding(headers::ContentEncoding::Gzip)
.body(data.clone()).unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from(data));
}
#[test]
fn test_client_brotli_encoding() {
let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(httpcodes::HTTPOk
.build()
.content_encoding(headers::ContentEncoding::Deflate)
.body(bytes))
}).responder()}
));
// client request
let request = srv.client(Method::POST, "/")
.content_encoding(headers::ContentEncoding::Br)
.body(STR).unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
}
#[test]
fn test_client_deflate_encoding() {
let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(httpcodes::HTTPOk
.build()
.content_encoding(headers::ContentEncoding::Br)
.body(bytes))
}).responder()}
));
// client request
let request = srv.post()
.content_encoding(headers::ContentEncoding::Deflate)
.body(STR).unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
}
#[test]
fn test_client_streaming_explicit() {
let mut srv = test::TestServer::new(
|app| app.handler(
|req: HttpRequest| req.body()
.map_err(Error::from)
.and_then(|body| {
Ok(httpcodes::HTTPOk.build()
.chunked()
.content_encoding(headers::ContentEncoding::Identity)
.body(body)?)})
.responder()));
let body = once(Ok(Bytes::from_static(STR.as_ref())));
let request = srv.get().body(Body::Streaming(Box::new(body))).unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
}
#[test]
fn test_body_streaming_implicit() {
let mut 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 request = srv.get().finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
}

View file

@ -93,6 +93,39 @@ fn test_start() {
} }
} }
#[test]
#[cfg(unix)]
fn test_shutdown() {
let _ = test::TestServer::unused_addr();
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let sys = System::new("test");
let srv = HttpServer::new(
|| vec![Application::new()
.resource("/", |r| r.method(Method::GET).h(httpcodes::HTTPOk))]);
let srv = srv.bind("127.0.0.1:0").unwrap();
let addr = srv.addrs()[0];
let srv_addr = srv.shutdown_timeout(1).start();
let _ = tx.send((addr, srv_addr));
sys.run();
});
let (addr, srv_addr) = rx.recv().unwrap();
let mut sys = System::new("test-server");
{
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()).finish().unwrap();
let response = sys.run_until_complete(req.send()).unwrap();
srv_addr.do_send(server::StopServer{graceful: true});
assert!(response.status().is_success());
}
thread::sleep(time::Duration::from_millis(1000));
assert!(net::TcpStream::connect(addr).is_err());
}
#[test] #[test]
fn test_simple() { fn test_simple() {
let mut srv = test::TestServer::new(|app| app.handler(httpcodes::HTTPOk)); let mut srv = test::TestServer::new(|app| app.handler(httpcodes::HTTPOk));
@ -101,6 +134,44 @@ fn test_simple() {
assert!(response.status().is_success()); assert!(response.status().is_success());
} }
#[test]
fn test_headers() {
let data = STR.repeat(10);
let srv_data = Arc::new(data.clone());
let mut srv = test::TestServer::new(
move |app| {
let data = srv_data.clone();
app.handler(move |_| {
let mut builder = httpcodes::HTTPOk.build();
for idx in 0..90 {
builder.header(
format!("X-TEST-{}", idx).as_str(),
"TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ");
}
builder.body(data.as_ref())})
});
let request = srv.get().finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from(data));
}
#[test] #[test]
fn test_body() { fn test_body() {
let mut srv = test::TestServer::new( let mut srv = test::TestServer::new(
@ -123,7 +194,7 @@ fn test_body_gzip() {
.content_encoding(headers::ContentEncoding::Gzip) .content_encoding(headers::ContentEncoding::Gzip)
.body(STR))); .body(STR)));
let request = srv.get().finish().unwrap(); let request = srv.get().disable_decompress().finish().unwrap();
let response = srv.execute(request.send()).unwrap(); let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
@ -138,7 +209,34 @@ fn test_body_gzip() {
} }
#[test] #[test]
fn test_body_streaming_implicit() { fn test_body_gzip_large() {
let data = STR.repeat(10);
let srv_data = Arc::new(data.clone());
let mut srv = test::TestServer::new(
move |app| {
let data = srv_data.clone();
app.handler(
move |_| httpcodes::HTTPOk.build()
.content_encoding(headers::ContentEncoding::Gzip)
.body(data.as_ref()))});
let request = srv.get().disable_decompress().finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
// decode
let mut e = GzDecoder::new(&bytes[..]);
let mut dec = Vec::new();
e.read_to_end(&mut dec).unwrap();
assert_eq!(Bytes::from(dec), Bytes::from(data));
}
#[test]
fn test_body_chunked_implicit() {
let mut srv = test::TestServer::new( let mut srv = test::TestServer::new(
|app| app.handler(|_| { |app| app.handler(|_| {
let body = once(Ok(Bytes::from_static(STR.as_ref()))); let body = once(Ok(Bytes::from_static(STR.as_ref())));
@ -146,7 +244,7 @@ fn test_body_streaming_implicit() {
.content_encoding(headers::ContentEncoding::Gzip) .content_encoding(headers::ContentEncoding::Gzip)
.body(Body::Streaming(Box::new(body)))})); .body(Body::Streaming(Box::new(body)))}));
let request = srv.get().finish().unwrap(); let request = srv.get().disable_decompress().finish().unwrap();
let response = srv.execute(request.send()).unwrap(); let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
@ -169,7 +267,7 @@ fn test_body_br_streaming() {
.content_encoding(headers::ContentEncoding::Br) .content_encoding(headers::ContentEncoding::Br)
.body(Body::Streaming(Box::new(body)))})); .body(Body::Streaming(Box::new(body)))}));
let request = srv.get().finish().unwrap(); let request = srv.get().disable_decompress().finish().unwrap();
let response = srv.execute(request.send()).unwrap(); let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
@ -252,6 +350,7 @@ fn test_body_length() {
let body = once(Ok(Bytes::from_static(STR.as_ref()))); let body = once(Ok(Bytes::from_static(STR.as_ref())));
httpcodes::HTTPOk.build() httpcodes::HTTPOk.build()
.content_length(STR.len() as u64) .content_length(STR.len() as u64)
.content_encoding(headers::ContentEncoding::Identity)
.body(Body::Streaming(Box::new(body)))})); .body(Body::Streaming(Box::new(body)))}));
let request = srv.get().finish().unwrap(); let request = srv.get().finish().unwrap();
@ -264,7 +363,7 @@ fn test_body_length() {
} }
#[test] #[test]
fn test_body_streaming_explicit() { fn test_body_chunked_explicit() {
let mut srv = test::TestServer::new( let mut srv = test::TestServer::new(
|app| app.handler(|_| { |app| app.handler(|_| {
let body = once(Ok(Bytes::from_static(STR.as_ref()))); let body = once(Ok(Bytes::from_static(STR.as_ref())));
@ -273,7 +372,7 @@ fn test_body_streaming_explicit() {
.content_encoding(headers::ContentEncoding::Gzip) .content_encoding(headers::ContentEncoding::Gzip)
.body(Body::Streaming(Box::new(body)))})); .body(Body::Streaming(Box::new(body)))}));
let request = srv.get().finish().unwrap(); let request = srv.get().disable_decompress().finish().unwrap();
let response = srv.execute(request.send()).unwrap(); let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
@ -297,7 +396,7 @@ fn test_body_deflate() {
.body(STR))); .body(STR)));
// client request // client request
let request = srv.get().finish().unwrap(); let request = srv.get().disable_decompress().finish().unwrap();
let response = srv.execute(request.send()).unwrap(); let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
@ -321,7 +420,7 @@ fn test_body_brotli() {
.body(STR))); .body(STR)));
// client request // client request
let request = srv.get().finish().unwrap(); let request = srv.get().disable_decompress().finish().unwrap();
let response = srv.execute(request.send()).unwrap(); let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
@ -364,31 +463,32 @@ fn test_gzip_encoding() {
} }
#[test] #[test]
fn test_client_gzip_encoding() { fn test_gzip_encoding_large() {
let data = STR.repeat(10);
let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| {
req.body() req.body()
.and_then(|bytes: Bytes| { .and_then(|bytes: Bytes| {
Ok(httpcodes::HTTPOk Ok(httpcodes::HTTPOk
.build() .build()
.content_encoding(headers::ContentEncoding::Deflate) .content_encoding(headers::ContentEncoding::Identity)
.body(bytes)) .body(bytes))
}).responder()} }).responder()}
)); ));
// client request // client request
let mut e = GzEncoder::new(Vec::new(), Compression::default());
e.write_all(data.as_ref()).unwrap();
let enc = e.finish().unwrap();
let request = srv.post() let request = srv.post()
.content_encoding(headers::ContentEncoding::Gzip) .header(header::CONTENT_ENCODING, "gzip")
.body(STR).unwrap(); .body(enc.clone()).unwrap();
let response = srv.execute(request.send()).unwrap(); let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
// read response // read response
let bytes = srv.execute(response.body()).unwrap(); let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from(data));
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] #[test]
@ -420,32 +520,32 @@ fn test_deflate_encoding() {
} }
#[test] #[test]
fn test_client_deflate_encoding() { fn test_deflate_encoding_large() {
let data = STR.repeat(10);
let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| {
req.body() req.body()
.and_then(|bytes: Bytes| { .and_then(|bytes: Bytes| {
Ok(httpcodes::HTTPOk Ok(httpcodes::HTTPOk
.build() .build()
.content_encoding(headers::ContentEncoding::Br) .content_encoding(headers::ContentEncoding::Identity)
.body(bytes)) .body(bytes))
}).responder()} }).responder()}
)); ));
let mut e = DeflateEncoder::new(Vec::new(), Compression::default());
e.write_all(data.as_ref()).unwrap();
let enc = e.finish().unwrap();
// client request // client request
let request = srv.post() let request = srv.post()
.content_encoding(headers::ContentEncoding::Deflate) .header(header::CONTENT_ENCODING, "deflate")
.body(STR).unwrap(); .body(enc).unwrap();
let response = srv.execute(request.send()).unwrap(); let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
// read response // read response
let bytes = srv.execute(response.body()).unwrap(); let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from(data));
// decode brotli
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] #[test]
@ -477,32 +577,32 @@ fn test_brotli_encoding() {
} }
#[test] #[test]
fn test_client_brotli_encoding() { fn test_brotli_encoding_large() {
let data = STR.repeat(10);
let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| {
req.body() req.body()
.and_then(|bytes: Bytes| { .and_then(|bytes: Bytes| {
Ok(httpcodes::HTTPOk Ok(httpcodes::HTTPOk
.build() .build()
.content_encoding(headers::ContentEncoding::Deflate) .content_encoding(headers::ContentEncoding::Identity)
.body(bytes)) .body(bytes))
}).responder()} }).responder()}
)); ));
let mut e = BrotliEncoder::new(Vec::new(), 5);
e.write_all(data.as_ref()).unwrap();
let enc = e.finish().unwrap();
// client request // client request
let request = srv.client(Method::POST, "/") let request = srv.post()
.content_encoding(headers::ContentEncoding::Br) .header(header::CONTENT_ENCODING, "br")
.body(STR).unwrap(); .body(enc).unwrap();
let response = srv.execute(request.send()).unwrap(); let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
// read response // read response
let bytes = srv.execute(response.body()).unwrap(); let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from(data));
// decode brotli
let mut e = DeflateDecoder::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] #[test]
@ -545,30 +645,6 @@ fn test_h2() {
// assert_eq!(_res.unwrap(), Bytes::from_static(STR.as_ref())); // assert_eq!(_res.unwrap(), Bytes::from_static(STR.as_ref()));
} }
#[test]
fn test_client_streaming_explicit() {
let mut srv = test::TestServer::new(
|app| app.handler(
|req: HttpRequest| req.body()
.map_err(Error::from)
.and_then(|body| {
Ok(httpcodes::HTTPOk.build()
.chunked()
.content_encoding(headers::ContentEncoding::Identity)
.body(body)?)})
.responder()));
let body = once(Ok(Bytes::from_static(STR.as_ref())));
let request = srv.get().body(Body::Streaming(Box::new(body))).unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
}
#[test] #[test]
fn test_application() { fn test_application() {
let mut srv = test::TestServer::with_factory( let mut srv = test::TestServer::with_factory(

View file

@ -16,14 +16,14 @@ impl Actor for Ws {
type Context = ws::WebsocketContext<Self>; type Context = ws::WebsocketContext<Self>;
} }
impl Handler<ws::Message> for Ws { impl StreamHandler<ws::Message, ws::ProtocolError> for Ws {
type Result = ();
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
match msg { match msg {
ws::Message::Ping(msg) => ctx.pong(&msg), ws::Message::Ping(msg) => ctx.pong(&msg),
ws::Message::Text(text) => ctx.text(text), ws::Message::Text(text) => ctx.text(text),
ws::Message::Binary(bin) => ctx.binary(bin), ws::Message::Binary(bin) => ctx.binary(bin),
ws::Message::Close(reason) => ctx.close(reason, ""),
_ => (), _ => (),
} }
} }
@ -49,5 +49,5 @@ fn test_simple() {
writer.close(ws::CloseCode::Normal, ""); writer.close(ws::CloseCode::Normal, "");
let (item, _) = srv.execute(reader.into_future()).unwrap(); let (item, _) = srv.execute(reader.into_future()).unwrap();
assert!(item.is_none()); assert_eq!(item, Some(ws::Message::Close(ws::CloseCode::Normal)));
} }

View file

@ -19,7 +19,7 @@ use futures::Future;
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
use actix::prelude::*; use actix::prelude::*;
use actix_web::ws::{Message, WsClientError, WsClient, WsClientWriter}; use actix_web::ws;
fn main() { fn main() {
@ -71,21 +71,21 @@ fn main() {
let perf = perf_counters.clone(); let perf = perf_counters.clone();
let addr = Arbiter::new(format!("test {}", t)); let addr = Arbiter::new(format!("test {}", t));
addr.send(actix::msgs::Execute::new(move || -> Result<(), ()> { addr.do_send(actix::msgs::Execute::new(move || -> Result<(), ()> {
let mut reps = report; let mut reps = report;
for _ in 0..concurrency { for _ in 0..concurrency {
let pl2 = pl.clone(); let pl2 = pl.clone();
let perf2 = perf.clone(); let perf2 = perf.clone();
Arbiter::handle().spawn( Arbiter::handle().spawn(
WsClient::new(&ws).connect().unwrap() ws::Client::new(&ws).connect()
.map_err(|e| { .map_err(|e| {
println!("Error: {}", e); println!("Error: {}", e);
Arbiter::system().send(actix::msgs::SystemExit(0)); Arbiter::system().do_send(actix::msgs::SystemExit(0));
() ()
}) })
.map(move |(reader, writer)| { .map(move |(reader, writer)| {
let addr: SyncAddress<_> = ChatClient::create(move |ctx| { let addr: Addr<Syn, _> = ChatClient::create(move |ctx| {
ChatClient::add_stream(reader, ctx); ChatClient::add_stream(reader, ctx);
ChatClient{conn: writer, ChatClient{conn: writer,
payload: pl2, payload: pl2,
@ -114,7 +114,7 @@ fn parse_u64_default(input: Option<&str>, default: u64) -> u64 {
} }
struct ChatClient{ struct ChatClient{
conn: WsClientWriter, conn: ws::ClientWriter,
payload: Arc<String>, payload: Arc<String>,
ts: u64, ts: u64,
bin: bool, bin: bool,
@ -133,9 +133,9 @@ impl Actor for ChatClient {
} }
} }
fn stopping(&mut self, _: &mut Context<Self>) -> bool { fn stopping(&mut self, _: &mut Context<Self>) -> Running {
Arbiter::system().send(actix::msgs::SystemExit(0)); Arbiter::system().do_send(actix::msgs::SystemExit(0));
true Running::Stop
} }
} }
@ -171,15 +171,15 @@ impl ChatClient {
} }
/// Handle server websocket messages /// Handle server websocket messages
impl StreamHandler<Message, WsClientError> for ChatClient { impl StreamHandler<ws::Message, ws::ProtocolError> for ChatClient {
fn finished(&mut self, ctx: &mut Context<Self>) { fn finished(&mut self, ctx: &mut Context<Self>) {
ctx.stop() ctx.stop()
} }
fn handle(&mut self, msg: Message, ctx: &mut Context<Self>) { fn handle(&mut self, msg: ws::Message, ctx: &mut Context<Self>) {
match msg { match msg {
Message::Text(txt) => { ws::Message::Text(txt) => {
if txt == self.payload.as_ref().as_str() { if txt == self.payload.as_ref().as_str() {
self.perf_counters.register_request(); self.perf_counters.register_request();
self.perf_counters.register_latency(time::precise_time_ns() - self.ts); self.perf_counters.register_latency(time::precise_time_ns() - self.ts);