1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-07 03:10:17 +02:00

Compare commits

...

47 Commits

Author SHA1 Message Date
e708f51156 prep release 2018-03-04 20:28:06 -08:00
cbb821148b explicitly set tcp nodelay 2018-03-04 20:14:58 -08:00
d6b021e185 Merge pull request #94 from messense/feature/str-repeat
Use str::repeat
2018-03-04 19:57:49 -08:00
0adb7e8553 Use str::repeat 2018-03-05 09:54:58 +08:00
dbfa1f0ac8 fix example 2018-03-04 10:44:41 -08:00
11347e3c7d Allow to use Arc<Vec<u8>> as response/request body 2018-03-04 10:33:18 -08:00
631fe72a46 websockets text() is more generic 2018-03-04 10:18:42 -08:00
f673dba759 Fix handling of requests with an encoded body with a length > 8192 #93 2018-03-04 09:48:34 -08:00
ab978a18ff unix only test 2018-03-03 18:50:00 -08:00
327df159c6 prepare release 2018-03-03 18:46:22 -08:00
2ccbd5fa18 fix socket polling 2018-03-03 12:17:26 -08:00
058630d041 simplify channels list management 2018-03-03 11:16:55 -08:00
f456be0309 simplify linked nodes 2018-03-03 10:06:13 -08:00
9bd6cb03ac Merge branch 'master' of github.com:actix/actix-web 2018-03-03 09:29:46 -08:00
16afeda79c update changes 2018-03-03 09:29:36 -08:00
83fcdfd91f fix potential bug in payload processing 2018-03-03 09:27:54 -08:00
8f94ae41cc Merge pull request #90 from rvlzzr/master
move reuse_address before bind
2018-03-02 23:08:33 -08:00
4e41347de8 move reuse_address before bind 2018-03-02 22:57:11 -08:00
6acb6dd4e7 set release date 2018-03-02 22:31:58 -08:00
791a980e2d update tests 2018-03-02 22:08:56 -08:00
c2d8abcee7 Fix disconnect on idle connections 2018-03-02 20:47:23 -08:00
16c05f07ba make HttpRequest::match_info_mut() public 2018-03-02 20:40:08 -08:00
2158ad29ee add Pattern::with_prefix, make it usable outside of actix 2018-03-02 20:39:22 -08:00
feba5aeffd bump version 2018-03-02 14:31:23 -08:00
343888017e Update CHANGES.md 2018-03-02 12:26:31 -08:00
3a5d445b2f Merge pull request #89 from niklasf/csrf-middleware
add csrf filter middleware
2018-03-02 12:25:23 -08:00
e60acb7607 Merge branch 'master' into csrf-middleware 2018-03-02 12:25:05 -08:00
bebfc6c9b5 sleep for test 2018-03-02 11:32:37 -08:00
3b2928a391 Better naming for websockets implementation 2018-03-02 11:29:55 -08:00
10f57dac31 add csrf filter middleware 2018-03-02 20:13:43 +01:00
b640b49b05 adjust low buf size 2018-03-01 20:13:50 -08:00
1fea4bd9a6 prepare release 2018-03-01 20:01:25 -08:00
206c4e581a rename httpcodes 2018-03-01 19:12:59 -08:00
4e13505b92 rename .p to a .filter 2018-03-01 18:42:50 -08:00
5b6d7cddbf Fix payload parse in situation when socket data is not ready 2018-03-01 18:27:04 -08:00
4aaf9f08f8 update readme 2018-02-28 22:31:54 -08:00
b0ba23ff55 Merge pull request #88 from rofrol/patch-2
be consistent with host - had CORS preflight once
2018-02-28 17:07:57 -08:00
42b19b1819 Merge branch 'master' into patch-2 2018-02-28 17:07:44 -08:00
0335fde3f9 Update README.md 2018-02-28 16:58:05 -08:00
f27edbff89 be consistent with host - had CORS preflight once 2018-03-01 01:01:27 +01:00
d62d6e68e0 use new version of http crate 2018-02-28 14:16:55 -08:00
1284264511 Update CHANGES.md 2018-02-28 12:35:16 -08:00
d977fe563b Merge pull request #87 from adwhit/fix-session-set
fix session mut borrow lifetime
2018-02-28 12:34:46 -08:00
bb68f9dd90 add session borrow fix to changes 2018-02-28 19:52:53 +00:00
313396d9b5 fix session mut borrow lifetime 2018-02-28 19:35:26 +00:00
171a23561e export Drain 2018-02-28 11:10:54 -08:00
67f33a4760 add redis session example 2018-02-28 10:26:40 -08:00
63 changed files with 1350 additions and 628 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

View File

@ -1,5 +1,42 @@
# Changes # Changes
## 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) ## 0.4.0 (2018-02-28)
* Actix 0.5 compatibility * Actix 0.5 compatibility

View File

@ -1,8 +1,8 @@
[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"
@ -42,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"
@ -81,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"
@ -108,6 +108,7 @@ members = [
"examples/hello-world", "examples/hello-world",
"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",

View File

@ -10,11 +10,13 @@ Actix web is a small, pragmatic, extremely fast, web framework for Rust.
* 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
@ -57,6 +59,9 @@ 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)

View File

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

@ -62,7 +62,7 @@ impl Handler<GraphQLData> for GraphQLExecutor {
} }
fn graphiql(_req: HttpRequest<State>) -> Result<HttpResponse> { fn graphiql(_req: HttpRequest<State>) -> Result<HttpResponse> {
let html = graphiql_source("http://localhost:8080/graphql"); let html = graphiql_source("http://127.0.0.1:8080/graphql");
Ok(HttpResponse::build(StatusCode::OK) Ok(HttpResponse::build(StatusCode::OK)
.content_type("text/html; charset=utf-8") .content_type("text/html; charset=utf-8")
.body(html).unwrap()) .body(html).unwrap())

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,7 +36,7 @@ impl Actor for MyWebSocket {
type Context = ws::WebsocketContext<Self, AppState>; type Context = ws::WebsocketContext<Self, AppState>;
} }
impl StreamHandler<ws::Message, ws::WsError> for MyWebSocket { impl StreamHandler<ws::Message, ws::ProtocolError> for MyWebSocket {
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;

View File

@ -92,7 +92,7 @@ impl Handler<session::Message> for WsChatSession {
} }
/// WebSocket message handler /// WebSocket message handler
impl StreamHandler<ws::Message, ws::WsError> for WsChatSession { impl StreamHandler<ws::Message, ws::ProtocolError> for WsChatSession {
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
println!("WEBSOCKET MESSAGE: {:?}", msg); println!("WEBSOCKET MESSAGE: {:?}", msg);

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, WsError, WsClient, WsClientWriter}; use actix_web::ws::{Message, ProtocolError, Client, ClientWriter};
fn main() { fn main() {
@ -21,7 +21,7 @@ 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() .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, WsError> 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,7 +25,7 @@ impl Actor for MyWebSocket {
} }
/// Handler for `ws::Message` /// Handler for `ws::Message`
impl StreamHandler<ws::Message, ws::WsError> for MyWebSocket { impl StreamHandler<ws::Message, ws::ProtocolError> for MyWebSocket {
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

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

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

@ -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()
} }
@ -117,7 +117,7 @@ fn index(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()
} }
@ -251,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()
} }

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.

View File

@ -22,7 +22,7 @@ impl Actor for Ws {
} }
/// Handler for ws::Message message /// Handler for ws::Message message
impl StreamHandler<ws::Message, ws::WsError> for Ws { impl StreamHandler<ws::Message, ws::ProtocolError> for Ws {
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

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

@ -2,7 +2,7 @@ use std::mem;
use httparse; use httparse;
use http::{Version, HttpTryFrom, HeaderMap, StatusCode}; use http::{Version, HttpTryFrom, HeaderMap, StatusCode};
use http::header::{self, HeaderName, HeaderValue}; use http::header::{self, HeaderName, HeaderValue};
use bytes::{Bytes, BytesMut, BufMut}; use bytes::{Bytes, BytesMut};
use futures::{Poll, Async}; use futures::{Poll, Async};
use error::{ParseError, PayloadError}; use error::{ParseError, PayloadError};
@ -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 || buf.remaining_mut() == 0 {
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)
} }
}, },
} }
@ -84,32 +79,42 @@ impl HttpResponseParser {
where T: IoStream where T: IoStream
{ {
if self.decoder.is_some() { 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)) => { Ok(Async::Ready(0)) => {
if buf.is_empty() { if buf.is_empty() {
return Err(PayloadError::Incomplete) return Err(PayloadError::Incomplete)
} }
true
} }
Err(err) => return Err(err.into()), Err(err) => return Err(err.into()),
_ => (), Ok(Async::NotReady) => true,
} _ => false,
};
match self.decoder.as_mut().unwrap().decode(buf) { match self.decoder.as_mut().unwrap().decode(buf) {
Ok(Async::Ready(Some(b))) => Ok(Async::Ready(Some(b))), Ok(Async::Ready(Some(b))) =>
return Ok(Async::Ready(Some(b))),
Ok(Async::Ready(None)) => { Ok(Async::Ready(None)) => {
self.decoder.take(); self.decoder.take();
Ok(Async::Ready(None)) return Ok(Async::Ready(None))
}
Ok(Async::NotReady) => {
if not_ready {
return Ok(Async::NotReady)
}
}
Err(err) => return Err(err.into()),
} }
Ok(Async::NotReady) => Ok(Async::NotReady),
Err(err) => Err(err.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;

View File

@ -111,7 +111,7 @@ impl Future for SendRequest {
_ => IoBody::Done, _ => IoBody::Done,
}; };
let mut pl = Box::new(Pipeline { let pl = Box::new(Pipeline {
body, conn, writer, body, conn, writer,
parser: Some(HttpResponseParser::default()), parser: Some(HttpResponseParser::default()),
parser_buf: BytesMut::new(), parser_buf: BytesMut::new(),

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, 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
@ -336,7 +336,7 @@ 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")
} }
} }
@ -386,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(),
} }
} }
} }
@ -421,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(),
} }
} }
} }

View File

@ -15,7 +15,7 @@ use handler::{Handler, Responder};
use headers::ContentEncoding; use headers::ContentEncoding;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
use httpcodes::{HTTPOk, HTTPFound}; use httpcodes::{HttpOk, HttpFound};
/// A file with an associated name; responds with the Content-Type based on the /// A file with an associated name; responds with the Content-Type based on the
/// file extension. /// file extension.
@ -84,7 +84,7 @@ impl Responder for NamedFile {
type Error = io::Error; type Error = io::Error;
fn respond_to(mut self, _: HttpRequest) -> Result<HttpResponse, io::Error> { fn respond_to(mut self, _: HttpRequest) -> Result<HttpResponse, io::Error> {
let mut resp = HTTPOk.build(); let mut resp = HttpOk.build();
resp.content_encoding(ContentEncoding::Identity); resp.content_encoding(ContentEncoding::Identity);
if let Some(ext) = self.path().extension() { if let Some(ext) = self.path().extension() {
let mime = get_mime_type(&ext.to_string_lossy()); let mime = get_mime_type(&ext.to_string_lossy());
@ -164,7 +164,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())
} }
@ -289,7 +289,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

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

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

View File

@ -114,7 +114,7 @@ pub trait HttpMessage {
/// .from_err() /// .from_err()
/// .and_then(|bytes: Bytes| { // <- complete body /// .and_then(|bytes: Bytes| { // <- complete body
/// println!("==== BODY ==== {:?}", bytes); /// println!("==== BODY ==== {:?}", bytes);
/// Ok(httpcodes::HTTPOk.into()) /// Ok(httpcodes::HttpOk.into())
/// }).responder() /// }).responder()
/// } /// }
/// # fn main() {} /// # fn main() {}
@ -148,7 +148,7 @@ pub trait HttpMessage {
/// .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()
/// } /// }
@ -187,7 +187,7 @@ pub trait HttpMessage {
/// .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() {}

View File

@ -249,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();
/// } /// }
@ -365,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) }
} }

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

@ -75,7 +75,7 @@ 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() {}

View File

@ -35,10 +35,11 @@
//! * `WebSockets` server/client //! * `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`, `CORS`, `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
@ -188,6 +189,7 @@ 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;

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();
//! } //! }
@ -58,7 +58,7 @@ use resource::Resource;
use httpmessage::HttpMessage; 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
@ -110,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()
} }
} }
@ -219,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);
} }
@ -307,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());})
@ -823,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"*"[..],
@ -832,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();
@ -844,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();
/// } /// }

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

@ -86,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(())
} }

View File

@ -168,7 +168,7 @@ impl Inner {
len: 0, len: 0,
err: None, err: None,
items: VecDeque::new(), items: VecDeque::new(),
need_read: false, need_read: true,
} }
} }

View File

@ -173,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()),
_ => (), _ => (),
} }

View File

@ -28,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>
@ -71,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) {

View File

@ -148,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) {
@ -188,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) => {
@ -236,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;
} }
} }
} }

View File

@ -11,7 +11,7 @@ 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};
@ -128,177 +128,6 @@ impl PayloadWriter for PayloadType {
} }
} }
pub(crate) enum Decoder {
Deflate(Box<DeflateDecoder<Writer<BytesMut>>>),
Gzip(Option<Box<GzDecoder<Wrapper>>>),
Br(Box<BrotliDecoder<Writer<BytesMut>>>),
Identity,
}
// should go after write::GzDecoder get implemented
#[derive(Debug)]
pub(crate) struct Wrapper {
pub buf: BytesMut,
pub eof: bool,
}
impl io::Read for Wrapper {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let len = cmp::min(buf.len(), self.buf.len());
buf[..len].copy_from_slice(&self.buf[..len]);
self.buf.split_to(len);
if len == 0 {
if self.eof {
Ok(0)
} else {
Err(io::Error::new(io::ErrorKind::WouldBlock, ""))
}
} else {
Ok(len)
}
}
}
impl io::Write for Wrapper {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.buf.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
/// Payload stream with decompression support
pub(crate) struct PayloadStream {
decoder: Decoder,
dst: BytesMut,
}
impl PayloadStream {
pub fn new(enc: ContentEncoding) -> PayloadStream {
let dec = match enc {
ContentEncoding::Br => Decoder::Br(
Box::new(BrotliDecoder::new(BytesMut::with_capacity(8192).writer()))),
ContentEncoding::Deflate => Decoder::Deflate(
Box::new(DeflateDecoder::new(BytesMut::with_capacity(8192).writer()))),
ContentEncoding::Gzip => Decoder::Gzip(None),
_ => Decoder::Identity,
};
PayloadStream{ decoder: dec, dst: BytesMut::new() }
}
}
impl PayloadStream {
pub fn feed_eof(&mut self) -> io::Result<Option<Bytes>> {
match self.decoder {
Decoder::Br(ref mut decoder) => {
match decoder.finish() {
Ok(mut writer) => {
let b = writer.get_mut().take().freeze();
if !b.is_empty() {
Ok(Some(b))
} else {
Ok(None)
}
},
Err(err) => Err(err),
}
},
Decoder::Gzip(ref mut decoder) => {
if let Some(ref mut decoder) = *decoder {
decoder.as_mut().get_mut().eof = true;
loop {
self.dst.reserve(8192);
match decoder.read(unsafe{self.dst.bytes_mut()}) {
Ok(n) => {
if n == 0 {
return Ok(Some(self.dst.take().freeze()))
} else {
unsafe{self.dst.set_len(n)};
}
}
Err(err) => return Err(err),
}
}
} else {
Ok(None)
}
},
Decoder::Deflate(ref mut decoder) => {
match decoder.try_finish() {
Ok(_) => {
let b = decoder.get_mut().get_mut().take().freeze();
if !b.is_empty() {
Ok(Some(b))
} else {
Ok(None)
}
},
Err(err) => Err(err),
}
},
Decoder::Identity => Ok(None),
}
}
pub fn feed_data(&mut self, data: Bytes) -> io::Result<Option<Bytes>> {
match self.decoder {
Decoder::Br(ref mut decoder) => {
match decoder.write(&data).and_then(|_| decoder.flush()) {
Ok(_) => {
let b = decoder.get_mut().get_mut().take().freeze();
if !b.is_empty() {
Ok(Some(b))
} else {
Ok(None)
}
},
Err(err) => Err(err)
}
},
Decoder::Gzip(ref mut decoder) => {
if decoder.is_none() {
*decoder = Some(
Box::new(GzDecoder::new(
Wrapper{buf: BytesMut::from(data), eof: false})));
} else {
let _ = decoder.as_mut().unwrap().write(&data);
}
loop {
self.dst.reserve(8192);
match decoder.as_mut().as_mut().unwrap().read(unsafe{self.dst.bytes_mut()}) {
Ok(n) => {
if n == 0 {
return Ok(Some(self.dst.split_to(n).freeze()));
} else {
unsafe{self.dst.set_len(n)};
}
}
Err(e) => return Err(e),
}
}
},
Decoder::Deflate(ref mut decoder) => {
match decoder.write(&data).and_then(|_| decoder.flush()) {
Ok(_) => {
let b = decoder.get_mut().get_mut().take().freeze();
if !b.is_empty() {
Ok(Some(b))
} else {
Ok(None)
}
},
Err(e) => Err(e),
}
},
Decoder::Identity => Ok(Some(data)),
}
}
}
/// Payload wrapper with content decompression support /// Payload wrapper with content decompression support
pub(crate) struct EncodedPayload { pub(crate) struct EncodedPayload {
@ -357,6 +186,203 @@ impl PayloadWriter for EncodedPayload {
} }
} }
pub(crate) enum Decoder {
Deflate(Box<DeflateDecoder<Writer>>),
Gzip(Option<Box<GzDecoder<Wrapper>>>),
Br(Box<BrotliDecoder<Writer>>),
Identity,
}
// should go after write::GzDecoder get implemented
#[derive(Debug)]
pub(crate) struct Wrapper {
pub buf: BytesMut,
pub eof: bool,
}
impl io::Read for Wrapper {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let len = cmp::min(buf.len(), self.buf.len());
buf[..len].copy_from_slice(&self.buf[..len]);
self.buf.split_to(len);
if len == 0 {
if self.eof {
Ok(0)
} else {
Err(io::Error::new(io::ErrorKind::WouldBlock, ""))
}
} else {
Ok(len)
}
}
}
impl io::Write for Wrapper {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.buf.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
pub(crate) struct Writer {
buf: BytesMut,
}
impl Writer {
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 {
ContentEncoding::Br => Decoder::Br(
Box::new(BrotliDecoder::new(Writer::new()))),
ContentEncoding::Deflate => Decoder::Deflate(
Box::new(DeflateDecoder::new(Writer::new()))),
ContentEncoding::Gzip => Decoder::Gzip(None),
_ => Decoder::Identity,
};
PayloadStream{ decoder: dec, dst: BytesMut::new() }
}
}
impl PayloadStream {
pub fn feed_eof(&mut self) -> io::Result<Option<Bytes>> {
match self.decoder {
Decoder::Br(ref mut decoder) => {
match decoder.finish() {
Ok(mut writer) => {
let b = writer.take();
if !b.is_empty() {
Ok(Some(b))
} else {
Ok(None)
}
},
Err(e) => Err(e),
}
},
Decoder::Gzip(ref mut decoder) => {
if let Some(ref mut decoder) = *decoder {
decoder.as_mut().get_mut().eof = true;
loop {
self.dst.reserve(8192);
match decoder.read(unsafe{self.dst.bytes_mut()}) {
Ok(n) => {
if n == 0 {
return Ok(Some(self.dst.take().freeze()))
} else {
unsafe{self.dst.advance_mut(n)};
}
}
Err(e) => return Err(e),
}
}
} else {
Ok(None)
}
},
Decoder::Deflate(ref mut decoder) => {
match decoder.try_finish() {
Ok(_) => {
let b = decoder.get_mut().take();
if !b.is_empty() {
Ok(Some(b))
} else {
Ok(None)
}
},
Err(e) => Err(e),
}
},
Decoder::Identity => Ok(None),
}
}
pub fn feed_data(&mut self, data: Bytes) -> io::Result<Option<Bytes>> {
match self.decoder {
Decoder::Br(ref mut decoder) => {
match decoder.write(&data).and_then(|_| decoder.flush()) {
Ok(_) => {
let b = decoder.get_mut().take();
if !b.is_empty() {
Ok(Some(b))
} else {
Ok(None)
}
},
Err(e) => Err(e)
}
},
Decoder::Gzip(ref mut decoder) => {
if decoder.is_none() {
*decoder = Some(
Box::new(GzDecoder::new(
Wrapper{buf: BytesMut::from(data), eof: false})));
} else {
let _ = decoder.as_mut().unwrap().write(&data);
}
loop {
self.dst.reserve(8192);
match decoder.as_mut().as_mut().unwrap().read(unsafe{self.dst.bytes_mut()}) {
Ok(n) => {
if n == 0 {
return Ok(Some(self.dst.take().freeze()));
} else {
unsafe{self.dst.advance_mut(n)};
}
}
Err(e) => {
return Err(e)
}
}
}
},
Decoder::Deflate(ref mut decoder) => {
match decoder.write(&data).and_then(|_| decoder.flush()) {
Ok(_) => {
let b = decoder.get_mut().take();
if !b.is_empty() {
Ok(Some(b))
} else {
Ok(None)
}
},
Err(e) => Err(e),
}
},
Decoder::Identity => Ok(Some(data)),
}
}
}
pub(crate) enum ContentEncoder { pub(crate) enum ContentEncoder {
Deflate(DeflateEncoder<TransferEncoding>), Deflate(DeflateEncoder<TransferEncoding>),
Gzip(GzEncoder<TransferEncoding>), Gzip(GzEncoder<TransferEncoding>),

View File

@ -10,12 +10,12 @@ use actix::Arbiter;
use httparse; use httparse;
use http::{Uri, Method, Version, HttpTryFrom, HeaderMap}; use http::{Uri, Method, Version, HttpTryFrom, HeaderMap};
use http::header::{self, HeaderName, HeaderValue}; use http::header::{self, HeaderName, HeaderValue};
use bytes::{Bytes, BytesMut, BufMut}; use bytes::{Bytes, BytesMut};
use futures::{Future, Poll, Async}; use futures::{Future, Poll, Async};
use tokio_core::reactor::Timeout; use tokio_core::reactor::Timeout;
use pipeline::Pipeline; use pipeline::Pipeline;
use 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, PayloadStatus}; use payload::{Payload, PayloadWriter, PayloadStatus};
@ -125,8 +125,8 @@ impl<T, H> Http1<T, H>
// TODO: refactor // TODO: refactor
pub fn poll_io(&mut self) -> Poll<bool, ()> { 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(),
@ -151,7 +151,7 @@ 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
}, },
@ -402,42 +402,51 @@ impl Reader {
// read payload // read payload
let done = { let done = {
if let Some(ref mut payload) = self.payload { if let Some(ref mut payload) = self.payload {
match utils::read_from_io(io, buf) { 'buf: loop {
let not_ready = 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() { if payload.decoder.is_eof() {
payload.tx.feed_eof(); payload.tx.feed_eof();
break true 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
} }
@ -445,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 {
@ -470,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 || buf.remaining_mut() == 0 {
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");
@ -484,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)
}
}, },
} }
} }
@ -1413,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));
@ -1430,6 +1436,7 @@ 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));

View File

@ -18,7 +18,7 @@ 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 httpmessage::HttpMessage;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use payload::{Payload, PayloadWriter, PayloadStatus}; use payload::{Payload, PayloadWriter, PayloadStatus};
@ -298,7 +298,7 @@ impl Entry {
} }
} }
Entry {task: task.unwrap_or_else(|| Pipeline::error(HTTPNotFound)), Entry {task: task.unwrap_or_else(|| Pipeline::error(HttpNotFound)),
payload: psender, payload: psender,
stream: H2Writer::new(resp, settings.get_shared_bytes()), stream: H2Writer::new(resp, settings.get_shared_bytes()),
flags: EntryFlags::empty(), flags: EntryFlags::empty(),

View File

@ -63,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> {
@ -75,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()),
} }
} }

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

@ -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() {
@ -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()) 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()
/// } /// }
/// } /// }
/// ///

View File

@ -25,14 +25,32 @@ use client::{ClientRequest, ClientRequestBuilder, ClientResponse,
ClientConnector, SendRequest, SendRequestError, ClientConnector, SendRequest, SendRequestError,
HttpResponseParserError}; HttpResponseParserError};
use super::{Message, WsError}; use super::{Message, ProtocolError};
use super::frame::Frame; use super::frame::Frame;
use super::proto::{CloseCode, OpCode}; use super::proto::{CloseCode, OpCode};
/// 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")]
@ -56,46 +74,46 @@ pub enum WsClientError {
#[fail(display="{}", _0)] #[fail(display="{}", _0)]
SendRequest(SendRequestError), SendRequest(SendRequestError),
#[fail(display="{}", _0)] #[fail(display="{}", _0)]
Protocol(#[cause] WsError), 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<SendRequestError> for WsClientError { impl From<SendRequestError> for ClientError {
fn from(err: SendRequestError) -> WsClientError { fn from(err: SendRequestError) -> ClientError {
WsClientError::SendRequest(err) ClientError::SendRequest(err)
} }
} }
impl From<WsError> for WsClientError { impl From<ProtocolError> for ClientError {
fn from(err: WsError) -> WsClientError { fn from(err: ProtocolError) -> ClientError {
WsClientError::Protocol(err) ClientError::Protocol(err)
} }
} }
impl From<io::Error> for WsClientError { impl From<io::Error> for ClientError {
fn from(err: io::Error) -> WsClientError { fn from(err: io::Error) -> ClientError {
WsClientError::Io(err) ClientError::Io(err)
} }
} }
impl From<HttpResponseParserError> for WsClientError { impl From<HttpResponseParserError> for ClientError {
fn from(err: HttpResponseParserError) -> WsClientError { fn from(err: HttpResponseParserError) -> ClientError {
WsClientError::ResponseParseError(err) ClientError::ResponseParseError(err)
} }
} }
@ -104,9 +122,9 @@ 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>,
@ -114,16 +132,16 @@ pub struct WsClient {
max_size: usize, 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,
@ -182,12 +200,12 @@ impl WsClient {
} }
/// Connect to websocket server and do ws handshake /// Connect to websocket server and do ws handshake
pub fn connect(&mut self) -> WsClientHandshake { pub fn connect(&mut self) -> ClientHandshake {
if let Some(e) = self.err.take() { if let Some(e) = self.err.take() {
WsClientHandshake::error(e) ClientHandshake::error(e)
} }
else if let Some(e) = self.http_err.take() { else if let Some(e) = self.http_err.take() {
WsClientHandshake::error(e.into()) ClientHandshake::error(e.into())
} else { } else {
// origin // origin
if let Some(origin) = self.origin.take() { if let Some(origin) = self.origin.take() {
@ -197,50 +215,50 @@ 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()); 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 = match self.request.finish() { let request = match self.request.finish() {
Ok(req) => req, Ok(req) => req,
Err(err) => return WsClientHandshake::error(err.into()), Err(err) => return ClientHandshake::error(err.into()),
}; };
if request.uri().host().is_none() { if request.uri().host().is_none() {
return WsClientHandshake::error(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 WsClientHandshake::error(WsClientError::InvalidUrl) return ClientHandshake::error(ClientError::InvalidUrl)
} }
} else { } else {
return WsClientHandshake::error(WsClientError::InvalidUrl) return ClientHandshake::error(ClientError::InvalidUrl)
} }
// start handshake // start handshake
WsClientHandshake::new(request, self.max_size) ClientHandshake::new(request, self.max_size)
} }
} }
} }
struct WsInner { struct Inner {
tx: UnboundedSender<Bytes>, tx: UnboundedSender<Bytes>,
rx: PayloadHelper<ClientResponse>, rx: PayloadHelper<ClientResponse>,
closed: bool, closed: bool,
} }
pub struct WsClientHandshake { pub struct ClientHandshake {
request: Option<SendRequest>, request: Option<SendRequest>,
tx: Option<UnboundedSender<Bytes>>, tx: Option<UnboundedSender<Bytes>>,
key: String, key: String,
error: Option<WsClientError>, error: Option<ClientError>,
max_size: usize, max_size: usize,
} }
impl WsClientHandshake { impl ClientHandshake {
fn new(mut request: ClientRequest, max_size: usize) -> WsClientHandshake 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,
@ -249,7 +267,7 @@ impl WsClientHandshake {
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 (tx, rx) = unbounded(); let (tx, rx) = unbounded();
@ -257,7 +275,7 @@ impl WsClientHandshake {
Box::new(rx.map_err(|_| io::Error::new( Box::new(rx.map_err(|_| io::Error::new(
io::ErrorKind::Other, "disconnected").into())))); io::ErrorKind::Other, "disconnected").into()))));
WsClientHandshake { ClientHandshake {
key, key,
max_size, max_size,
request: Some(request.send()), request: Some(request.send()),
@ -266,8 +284,8 @@ impl WsClientHandshake {
} }
} }
fn error(err: WsClientError) -> WsClientHandshake { fn error(err: ClientError) -> ClientHandshake {
WsClientHandshake { ClientHandshake {
key: String::new(), key: String::new(),
request: None, request: None,
tx: None, tx: None,
@ -277,9 +295,9 @@ impl WsClientHandshake {
} }
} }
impl Future for WsClientHandshake { 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> {
if let Some(err) = self.error.take() { if let Some(err) = self.error.take() {
@ -296,7 +314,7 @@ impl Future for WsClientHandshake {
// verify response // verify response
if resp.status() != StatusCode::SWITCHING_PROTOCOLS { if resp.status() != StatusCode::SWITCHING_PROTOCOLS {
return Err(WsClientError::InvalidResponseStatus(resp.status())) 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) {
@ -310,26 +328,25 @@ impl Future for WsClientHandshake {
}; };
if !has_hdr { if !has_hdr {
trace!("Invalid upgrade header"); trace!("Invalid upgrade header");
return Err(WsClientError::InvalidUpgradeHeader) return Err(ClientError::InvalidUpgradeHeader)
} }
// Check for "CONNECTION" header // Check for "CONNECTION" header
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() {
if !s.to_lowercase().contains("upgrade") { if !s.to_lowercase().contains("upgrade") {
trace!("Invalid connection header: {}", s); trace!("Invalid connection header: {}", s);
return Err(WsClientError::InvalidConnectionHeader(conn.clone())) return Err(ClientError::InvalidConnectionHeader(conn.clone()))
} }
} else { } else {
trace!("Invalid connection header: {:?}", conn); trace!("Invalid connection header: {:?}", conn);
return Err(WsClientError::InvalidConnectionHeader(conn.clone())) return Err(ClientError::InvalidConnectionHeader(conn.clone()))
} }
} else { } else {
trace!("Missing connection header"); trace!("Missing connection header");
return Err(WsClientError::MissingConnectionHeader) return Err(ClientError::MissingConnectionHeader)
} }
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)
@ -342,14 +359,14 @@ impl Future for WsClientHandshake {
trace!( trace!(
"Invalid challenge response: expected: {} received: {:?}", "Invalid challenge response: expected: {} received: {:?}",
encoded, key); encoded, key);
return Err(WsClientError::InvalidChallengeResponse(encoded, key.clone())); return Err(ClientError::InvalidChallengeResponse(encoded, key.clone()));
} }
} else { } else {
trace!("Missing SEC-WEBSOCKET-ACCEPT header"); trace!("Missing SEC-WEBSOCKET-ACCEPT header");
return Err(WsClientError::MissingWebSocketAcceptHeader) return Err(ClientError::MissingWebSocketAcceptHeader)
}; };
let inner = WsInner { let inner = Inner {
tx: self.tx.take().unwrap(), tx: self.tx.take().unwrap(),
rx: PayloadHelper::new(resp), rx: PayloadHelper::new(resp),
closed: false, closed: false,
@ -357,33 +374,33 @@ impl Future for WsClientHandshake {
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), max_size: self.max_size}, (ClientReader{inner: Rc::clone(&inner), max_size: self.max_size},
WsClientWriter{inner}))) ClientWriter{inner})))
} }
} }
pub struct WsClientReader { pub struct ClientReader {
inner: Rc<UnsafeCell<WsInner>>, inner: Rc<UnsafeCell<Inner>>,
max_size: usize, 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 = WsError; 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 max_size = self.max_size;
@ -400,14 +417,14 @@ impl Stream for WsClientReader {
// continuation is not supported // continuation is not supported
if !finished { if !finished {
inner.closed = true; inner.closed = true;
return Err(WsError::NoContinuation) return Err(ProtocolError::NoContinuation)
} }
match opcode { match opcode {
OpCode::Continue => unimplemented!(), OpCode::Continue => unimplemented!(),
OpCode::Bad => { OpCode::Bad => {
inner.closed = true; inner.closed = true;
Err(WsError::BadOpCode) Err(ProtocolError::BadOpCode)
}, },
OpCode::Close => { OpCode::Close => {
inner.closed = true; inner.closed = true;
@ -431,7 +448,7 @@ impl Stream for WsClientReader {
Ok(Async::Ready(Some(Message::Text(s)))), Ok(Async::Ready(Some(Message::Text(s)))),
Err(_) => { Err(_) => {
inner.closed = true; inner.closed = true;
Err(WsError::BadEncoding) Err(ProtocolError::BadEncoding)
} }
} }
} }
@ -447,18 +464,18 @@ impl Stream for WsClientReader {
} }
} }
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]
@ -472,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

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

View File

@ -9,7 +9,7 @@ use body::Binary;
use error::{PayloadError}; use error::{PayloadError};
use payload::PayloadHelper; use payload::PayloadHelper;
use ws::WsError; use ws::ProtocolError;
use ws::proto::{OpCode, CloseCode}; use ws::proto::{OpCode, CloseCode};
use ws::mask::apply_mask; use ws::mask::apply_mask;
@ -53,7 +53,7 @@ impl Frame {
/// Parse the input stream into a frame. /// Parse the input stream into a frame.
pub fn parse<S>(pl: &mut PayloadHelper<S>, server: bool, max_size: usize) pub fn parse<S>(pl: &mut PayloadHelper<S>, server: bool, max_size: usize)
-> Poll<Option<Frame>, WsError> -> Poll<Option<Frame>, ProtocolError>
where S: Stream<Item=Bytes, Error=PayloadError> where S: Stream<Item=Bytes, Error=PayloadError>
{ {
let mut idx = 2; let mut idx = 2;
@ -69,9 +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(WsError::UnmaskedFrame) return Err(ProtocolError::UnmaskedFrame)
} else if masked && !server { } else if masked && !server {
return Err(WsError::MaskedFrame) return Err(ProtocolError::MaskedFrame)
} }
let rsv1 = first & 0x40 != 0; let rsv1 = first & 0x40 != 0;
@ -104,7 +104,7 @@ impl Frame {
// check for max allowed size // check for max allowed size
if length > max_size { if length > max_size {
return Err(WsError::Overflow) return Err(ProtocolError::Overflow)
} }
let mask = if server { let mask = if server {
@ -133,13 +133,13 @@ impl Frame {
// Disallow bad opcode // Disallow bad opcode
if let OpCode::Bad = opcode { if let OpCode::Bad = opcode {
return Err(WsError::InvalidOpcode(first & 0x0F)) return Err(ProtocolError::InvalidOpcode(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(WsError::InvalidLength(length)) return Err(ProtocolError::InvalidLength(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.");
@ -257,14 +257,14 @@ mod tests {
use super::*; use super::*;
use futures::stream::once; use futures::stream::once;
fn is_none(frm: Poll<Option<Frame>, WsError>) -> bool { fn is_none(frm: Poll<Option<Frame>, ProtocolError>) -> bool {
match frm { match frm {
Ok(Async::Ready(None)) => true, Ok(Async::Ready(None)) => true,
_ => false, _ => false,
} }
} }
fn extract(frm: Poll<Option<Frame>, WsError>) -> Frame { fn extract(frm: Poll<Option<Frame>, ProtocolError>) -> Frame {
match frm { match frm {
Ok(Async::Ready(Some(frame))) => frame, Ok(Async::Ready(Some(frame))) => frame,
_ => panic!("error"), _ => panic!("error"),
@ -370,7 +370,7 @@ mod tests {
assert!(Frame::parse(&mut buf, true, 1).is_err()); assert!(Frame::parse(&mut buf, true, 1).is_err());
if let Err(WsError::Overflow) = Frame::parse(&mut buf, false, 0) { if let Err(ProtocolError::Overflow) = Frame::parse(&mut buf, false, 0) {
} else { } else {
panic!("error"); panic!("error");
} }

View File

@ -55,7 +55,7 @@ use error::{Error, PayloadError, ResponseError};
use httpmessage::HttpMessage; use httpmessage::HttpMessage;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::{ConnectionType, HttpResponse, HttpResponseBuilder}; use httpresponse::{ConnectionType, HttpResponse, HttpResponseBuilder};
use httpcodes::{HTTPBadRequest, HTTPMethodNotAllowed}; use httpcodes::{HttpBadRequest, HttpMethodNotAllowed};
mod frame; mod frame;
mod proto; mod proto;
@ -67,18 +67,24 @@ 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::{Client, ClientError,
ClientReader, ClientWriter, ClientHandshake};
#[allow(deprecated)]
pub use self::client::{WsClient, WsClientError, pub use self::client::{WsClient, WsClientError,
WsClientReader, WsClientWriter, WsClientHandshake}; WsClientReader, WsClientWriter, WsClientHandshake};
const SEC_WEBSOCKET_ACCEPT: &str = "SEC-WEBSOCKET-ACCEPT"; /// Backward compatibility
const SEC_WEBSOCKET_KEY: &str = "SEC-WEBSOCKET-KEY"; #[doc(hidden)]
const SEC_WEBSOCKET_VERSION: &str = "SEC-WEBSOCKET-VERSION"; #[deprecated(since="0.4.2", note="please use `ws::ProtocolError` instead")]
// const SEC_WEBSOCKET_PROTOCOL: &'static str = "SEC-WEBSOCKET-PROTOCOL"; pub type WsError = ProtocolError;
#[doc(hidden)]
#[deprecated(since="0.4.2", note="please use `ws::HandshakeError` instead")]
pub type WsHandshakeError = HandshakeError;
/// Websocket errors /// Websocket errors
#[derive(Fail, Debug)] #[derive(Fail, Debug)]
pub enum WsError { pub enum ProtocolError {
/// Received an unmasked frame from client /// Received an unmasked frame from client
#[fail(display="Received an unmasked frame from client")] #[fail(display="Received an unmasked frame from client")]
UnmaskedFrame, UnmaskedFrame,
@ -108,17 +114,17 @@ pub enum WsError {
Payload(#[cause] PayloadError), Payload(#[cause] PayloadError),
} }
impl ResponseError for WsError {} impl ResponseError for ProtocolError {}
impl From<PayloadError> for WsError { impl From<PayloadError> for ProtocolError {
fn from(err: PayloadError) -> WsError { fn from(err: PayloadError) -> ProtocolError {
WsError::Payload(err) ProtocolError::Payload(err)
} }
} }
/// Websocket handshake errors /// Websocket handshake errors
#[derive(Fail, PartialEq, Debug)] #[derive(Fail, PartialEq, Debug)]
pub enum WsHandshakeError { pub enum HandshakeError {
/// Only get method is allowed /// Only get method is allowed
#[fail(display="Method not allowed")] #[fail(display="Method not allowed")]
GetMethodRequired, GetMethodRequired,
@ -139,27 +145,27 @@ pub enum WsHandshakeError {
BadWebsocketKey, BadWebsocketKey,
} }
impl ResponseError for WsHandshakeError { impl ResponseError for HandshakeError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
match *self { match *self {
WsHandshakeError::GetMethodRequired => { HandshakeError::GetMethodRequired => {
HTTPMethodNotAllowed HttpMethodNotAllowed
.build() .build()
.header(header::ALLOW, "GET") .header(header::ALLOW, "GET")
.finish() .finish()
.unwrap() .unwrap()
} }
WsHandshakeError::NoWebsocketUpgrade => HandshakeError::NoWebsocketUpgrade =>
HTTPBadRequest.with_reason("No WebSocket UPGRADE header found"), HttpBadRequest.with_reason("No WebSocket UPGRADE header found"),
WsHandshakeError::NoConnectionUpgrade => HandshakeError::NoConnectionUpgrade =>
HTTPBadRequest.with_reason("No CONNECTION upgrade"), HttpBadRequest.with_reason("No CONNECTION upgrade"),
WsHandshakeError::NoVersionHeader => HandshakeError::NoVersionHeader =>
HTTPBadRequest.with_reason("Websocket version header is required"), HttpBadRequest.with_reason("Websocket version header is required"),
WsHandshakeError::UnsupportedVersion => HandshakeError::UnsupportedVersion =>
HTTPBadRequest.with_reason("Unsupported version"), HttpBadRequest.with_reason("Unsupported version"),
WsHandshakeError::BadWebsocketKey => HandshakeError::BadWebsocketKey =>
HTTPBadRequest.with_reason("Handshake error"), HttpBadRequest.with_reason("Handshake error"),
} }
} }
} }
@ -176,7 +182,7 @@ pub enum Message {
/// Do websocket handshake and start actor /// Do websocket handshake and start actor
pub fn start<A, S>(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>> + StreamHandler<Message, WsError>, where A: Actor<Context=WebsocketContext<A, S>> + StreamHandler<Message, ProtocolError>,
S: 'static S: 'static
{ {
let mut resp = handshake(&req)?; let mut resp = handshake(&req)?;
@ -196,10 +202,10 @@ pub fn start<A, S>(req: HttpRequest<S>, actor: A) -> Result<HttpResponse, Error>
// /// `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
@ -213,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())
}; };
@ -249,7 +255,7 @@ 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())
} }
@ -280,7 +286,7 @@ impl<S> WsStream<S> where S: Stream<Item=Bytes, Error=PayloadError> {
impl<S> Stream for WsStream<S> where S: Stream<Item=Bytes, Error=PayloadError> { impl<S> Stream for WsStream<S> where S: Stream<Item=Bytes, Error=PayloadError> {
type Item = Message; type Item = Message;
type Error = WsError; type Error = ProtocolError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> { fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
if self.closed { if self.closed {
@ -294,14 +300,14 @@ impl<S> Stream for WsStream<S> where S: Stream<Item=Bytes, Error=PayloadError> {
// continuation is not supported // continuation is not supported
if !finished { if !finished {
self.closed = true; self.closed = true;
return Err(WsError::NoContinuation) return Err(ProtocolError::NoContinuation)
} }
match opcode { match opcode {
OpCode::Continue => unimplemented!(), OpCode::Continue => unimplemented!(),
OpCode::Bad => { OpCode::Bad => {
self.closed = true; self.closed = true;
Err(WsError::BadOpCode) Err(ProtocolError::BadOpCode)
} }
OpCode::Close => { OpCode::Close => {
self.closed = true; self.closed = true;
@ -325,7 +331,7 @@ impl<S> Stream for WsStream<S> where S: Stream<Item=Bytes, Error=PayloadError> {
Ok(Async::Ready(Some(Message::Text(s)))), Ok(Async::Ready(Some(Message::Text(s)))),
Err(_) => { Err(_) => {
self.closed = true; self.closed = true;
Err(WsError::BadEncoding) Err(ProtocolError::BadEncoding)
} }
} }
} }
@ -351,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,
@ -378,38 +384,38 @@ 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);
@ -419,17 +425,17 @@ mod tests {
#[test] #[test]
fn test_wserror_http_response() { fn test_wserror_http_response() {
let resp: HttpResponse = WsHandshakeError::GetMethodRequired.error_response(); let resp: HttpResponse = HandshakeError::GetMethodRequired.error_response();
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
let resp: HttpResponse = WsHandshakeError::NoWebsocketUpgrade.error_response(); let resp: HttpResponse = HandshakeError::NoWebsocketUpgrade.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST); assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp: HttpResponse = WsHandshakeError::NoConnectionUpgrade.error_response(); let resp: HttpResponse = HandshakeError::NoConnectionUpgrade.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST); assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp: HttpResponse = WsHandshakeError::NoVersionHeader.error_response(); let resp: HttpResponse = HandshakeError::NoVersionHeader.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST); assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp: HttpResponse = WsHandshakeError::UnsupportedVersion.error_response(); let resp: HttpResponse = HandshakeError::UnsupportedVersion.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST); assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp: HttpResponse = WsHandshakeError::BadWebsocketKey.error_response(); let resp: HttpResponse = HandshakeError::BadWebsocketKey.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST); assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
} }
} }

View File

@ -116,6 +116,32 @@ fn test_client_gzip_encoding() {
assert_eq!(bytes, Bytes::from_static(STR.as_ref())); 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] #[test]
fn test_client_brotli_encoding() { fn test_client_brotli_encoding() {
let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| {

View File

@ -94,6 +94,7 @@ fn test_start() {
} }
#[test] #[test]
#[cfg(unix)]
fn test_shutdown() { fn test_shutdown() {
let _ = test::TestServer::unused_addr(); let _ = test::TestServer::unused_addr();
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
@ -121,7 +122,7 @@ fn test_shutdown() {
assert!(response.status().is_success()); assert!(response.status().is_success());
} }
thread::sleep(time::Duration::from_millis(100)); thread::sleep(time::Duration::from_millis(1000));
assert!(net::TcpStream::connect(addr).is_err()); assert!(net::TcpStream::connect(addr).is_err());
} }
@ -135,8 +136,12 @@ fn test_simple() {
#[test] #[test]
fn test_headers() { fn test_headers() {
let data = STR.repeat(10);
let srv_data = Arc::new(data.clone());
let mut srv = test::TestServer::new( let mut srv = test::TestServer::new(
|app| app.handler(|_| { move |app| {
let data = srv_data.clone();
app.handler(move |_| {
let mut builder = httpcodes::HTTPOk.build(); let mut builder = httpcodes::HTTPOk.build();
for idx in 0..90 { for idx in 0..90 {
builder.header( builder.header(
@ -155,7 +160,8 @@ fn test_headers() {
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(STR)})); builder.body(data.as_ref())})
});
let request = srv.get().finish().unwrap(); let request = srv.get().finish().unwrap();
let response = srv.execute(request.send()).unwrap(); let response = srv.execute(request.send()).unwrap();
@ -163,7 +169,7 @@ fn test_headers() {
// read response // read response
let bytes = srv.execute(response.body()).unwrap(); let bytes = srv.execute(response.body()).unwrap();
assert_eq!(Bytes::from(bytes), Bytes::from_static(STR.as_ref())); assert_eq!(bytes, Bytes::from(data));
} }
#[test] #[test]
@ -202,6 +208,33 @@ fn test_body_gzip() {
assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref()));
} }
#[test]
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] #[test]
fn test_body_chunked_implicit() { fn test_body_chunked_implicit() {
let mut srv = test::TestServer::new( let mut srv = test::TestServer::new(
@ -429,6 +462,35 @@ fn test_gzip_encoding() {
assert_eq!(bytes, Bytes::from_static(STR.as_ref())); assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
} }
#[test]
fn test_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::Identity)
.body(bytes))
}).responder()}
));
// 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()
.header(header::CONTENT_ENCODING, "gzip")
.body(enc.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] #[test]
fn test_deflate_encoding() { fn test_deflate_encoding() {
let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| {
@ -457,6 +519,35 @@ fn test_deflate_encoding() {
assert_eq!(bytes, Bytes::from_static(STR.as_ref())); assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
} }
#[test]
fn test_deflate_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::Identity)
.body(bytes))
}).responder()}
));
let mut e = DeflateEncoder::new(Vec::new(), Compression::default());
e.write_all(data.as_ref()).unwrap();
let enc = e.finish().unwrap();
// client request
let request = srv.post()
.header(header::CONTENT_ENCODING, "deflate")
.body(enc).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_brotli_encoding() { fn test_brotli_encoding() {
let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| {
@ -485,6 +576,35 @@ fn test_brotli_encoding() {
assert_eq!(bytes, Bytes::from_static(STR.as_ref())); assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
} }
#[test]
fn test_brotli_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::Identity)
.body(bytes))
}).responder()}
));
let mut e = BrotliEncoder::new(Vec::new(), 5);
e.write_all(data.as_ref()).unwrap();
let enc = e.finish().unwrap();
// client request
let request = srv.post()
.header(header::CONTENT_ENCODING, "br")
.body(enc).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_h2() { fn test_h2() {
let srv = test::TestServer::new(|app| app.handler(|_|{ let srv = test::TestServer::new(|app| app.handler(|_|{

View File

@ -16,7 +16,7 @@ impl Actor for Ws {
type Context = ws::WebsocketContext<Self>; type Context = ws::WebsocketContext<Self>;
} }
impl StreamHandler<ws::Message, ws::WsError> for Ws { impl StreamHandler<ws::Message, ws::ProtocolError> for Ws {
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

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