mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-03 09:36:36 +02:00
Compare commits
47 Commits
Author | SHA1 | Date | |
---|---|---|---|
e708f51156 | |||
cbb821148b | |||
d6b021e185 | |||
0adb7e8553 | |||
dbfa1f0ac8 | |||
11347e3c7d | |||
631fe72a46 | |||
f673dba759 | |||
ab978a18ff | |||
327df159c6 | |||
2ccbd5fa18 | |||
058630d041 | |||
f456be0309 | |||
9bd6cb03ac | |||
16afeda79c | |||
83fcdfd91f | |||
8f94ae41cc | |||
4e41347de8 | |||
6acb6dd4e7 | |||
791a980e2d | |||
c2d8abcee7 | |||
16c05f07ba | |||
2158ad29ee | |||
feba5aeffd | |||
343888017e | |||
3a5d445b2f | |||
e60acb7607 | |||
bebfc6c9b5 | |||
3b2928a391 | |||
10f57dac31 | |||
b640b49b05 | |||
1fea4bd9a6 | |||
206c4e581a | |||
4e13505b92 | |||
5b6d7cddbf | |||
4aaf9f08f8 | |||
b0ba23ff55 | |||
42b19b1819 | |||
0335fde3f9 | |||
f27edbff89 | |||
d62d6e68e0 | |||
1284264511 | |||
d977fe563b | |||
bb68f9dd90 | |||
313396d9b5 | |||
171a23561e | |||
67f33a4760 |
@ -47,7 +47,7 @@ script:
|
||||
USE_SKEPTIC=1 cargo test --features=alpn
|
||||
else
|
||||
cargo clean
|
||||
cargo test
|
||||
cargo test -- --nocapture
|
||||
# --features=alpn
|
||||
fi
|
||||
|
||||
|
37
CHANGES.md
37
CHANGES.md
@ -1,5 +1,42 @@
|
||||
# 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)
|
||||
|
||||
* Actix 0.5 compatibility
|
||||
|
@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "actix-web"
|
||||
version = "0.4.0"
|
||||
version = "0.4.4"
|
||||
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"
|
||||
keywords = ["http", "web", "framework", "async", "futures"]
|
||||
homepage = "https://github.com/actix/actix-web"
|
||||
@ -42,7 +42,7 @@ brotli2 = "^0.3.2"
|
||||
failure = "0.1.1"
|
||||
flate2 = "1.0"
|
||||
h2 = "0.1"
|
||||
http = "^0.1.2"
|
||||
http = "^0.1.5"
|
||||
httparse = "1.2"
|
||||
http-range = "0.1"
|
||||
libc = "0.2"
|
||||
@ -81,7 +81,7 @@ openssl = { version="0.10", optional = true }
|
||||
tokio-openssl = { version="0.2", optional = true }
|
||||
|
||||
[dependencies.actix]
|
||||
version = "0.5"
|
||||
version = "^0.5.1"
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.5"
|
||||
@ -108,6 +108,7 @@ members = [
|
||||
"examples/hello-world",
|
||||
"examples/multipart",
|
||||
"examples/state",
|
||||
"examples/redis-session",
|
||||
"examples/template_tera",
|
||||
"examples/tls",
|
||||
"examples/websocket",
|
||||
|
@ -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)
|
||||
* Graceful server shutdown
|
||||
* Multipart streams
|
||||
* SSL support with openssl or native-tls
|
||||
* 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),
|
||||
[Redis sessions](https://github.com/actix/actix-redis),
|
||||
[DefaultHeaders](https://actix.github.io/actix-web/guide/qs_10.html#default-headers),
|
||||
[CORS](https://actix.github.io/actix-web/actix_web/middleware/cors/index.html))
|
||||
* Built on top of [Actix](https://github.com/actix/actix).
|
||||
* Built on top of [Actix actor framework](https://github.com/actix/actix).
|
||||
|
||||
## Documentation
|
||||
|
||||
@ -57,6 +59,9 @@ fn main() {
|
||||
* [SockJS Server](https://github.com/actix/actix-sockjs)
|
||||
* [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
|
||||
|
||||
* [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r15&hw=ph&test=plaintext)
|
||||
|
@ -139,7 +139,7 @@ fn main() {
|
||||
// default
|
||||
.default_resource(|r| {
|
||||
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")
|
||||
|
@ -62,7 +62,7 @@ impl Handler<GraphQLData> for GraphQLExecutor {
|
||||
}
|
||||
|
||||
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)
|
||||
.content_type("text/html; charset=utf-8")
|
||||
.body(html).unwrap())
|
||||
|
11
examples/redis-session/Cargo.toml
Normal file
11
examples/redis-session/Cargo.toml
Normal 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"] }
|
48
examples/redis-session/src/main.rs
Normal file
48
examples/redis-session/src/main.rs
Normal 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();
|
||||
}
|
@ -36,7 +36,7 @@ impl Actor for MyWebSocket {
|
||||
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) {
|
||||
self.counter += 1;
|
||||
|
@ -92,7 +92,7 @@ impl Handler<session::Message> for WsChatSession {
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
println!("WEBSOCKET MESSAGE: {:?}", msg);
|
||||
|
@ -12,7 +12,7 @@ use std::time::Duration;
|
||||
|
||||
use actix::*;
|
||||
use futures::Future;
|
||||
use actix_web::ws::{Message, WsError, WsClient, WsClientWriter};
|
||||
use actix_web::ws::{Message, ProtocolError, Client, ClientWriter};
|
||||
|
||||
|
||||
fn main() {
|
||||
@ -21,7 +21,7 @@ fn main() {
|
||||
let sys = actix::System::new("ws-example");
|
||||
|
||||
Arbiter::handle().spawn(
|
||||
WsClient::new("http://127.0.0.1:8080/ws/")
|
||||
Client::new("http://127.0.0.1:8080/ws/")
|
||||
.connect()
|
||||
.map_err(|e| {
|
||||
println!("Error: {}", e);
|
||||
@ -53,7 +53,7 @@ fn main() {
|
||||
}
|
||||
|
||||
|
||||
struct ChatClient(WsClientWriter);
|
||||
struct ChatClient(ClientWriter);
|
||||
|
||||
#[derive(Message)]
|
||||
struct ClientCommand(String);
|
||||
@ -88,12 +88,12 @@ impl Handler<ClientCommand> for ChatClient {
|
||||
type Result = ();
|
||||
|
||||
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
|
||||
impl StreamHandler<Message, WsError> for ChatClient {
|
||||
impl StreamHandler<Message, ProtocolError> for ChatClient {
|
||||
|
||||
fn handle(&mut self, msg: Message, ctx: &mut Context<Self>) {
|
||||
match msg {
|
||||
|
@ -25,7 +25,7 @@ impl Actor for MyWebSocket {
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
// process websocket messages
|
||||
|
@ -53,7 +53,7 @@ impl<S> Middleware<S> for Headers {
|
||||
fn main() {
|
||||
Application::new()
|
||||
.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")
|
||||
.finish())
|
||||
.resource("/test", |r| {
|
||||
r.method(Method::GET).f(|req| httpcodes::HTTPOk);
|
||||
r.method(Method::HEAD).f(|req| httpcodes::HTTPMethodNotAllowed);
|
||||
r.method(Method::GET).f(|req| httpcodes::HttpOk);
|
||||
r.method(Method::HEAD).f(|req| httpcodes::HttpMethodNotAllowed);
|
||||
})
|
||||
.finish();
|
||||
}
|
||||
|
@ -110,8 +110,8 @@ fn index(req: HttpRequest<State>) -> Box<Future<Item=HttpResponse, Error=Error>>
|
||||
.from_err()
|
||||
.and_then(|res| {
|
||||
match res {
|
||||
Ok(user) => Ok(httpcodes::HTTPOk.build().json(user)?),
|
||||
Err(_) => Ok(httpcodes::HTTPInternalServerError.into())
|
||||
Ok(user) => Ok(httpcodes::HttpOk.build().json(user)?),
|
||||
Err(_) => Ok(httpcodes::HttpInternalServerError.into())
|
||||
}
|
||||
})
|
||||
.responder()
|
||||
|
@ -49,12 +49,12 @@ fn main() {
|
||||
HttpServer::new(|| vec![
|
||||
Application::new()
|
||||
.prefix("/app1")
|
||||
.resource("/", |r| r.f(|r| httpcodes::HTTPOk)),
|
||||
.resource("/", |r| r.f(|r| httpcodes::HttpOk)),
|
||||
Application::new()
|
||||
.prefix("/app2")
|
||||
.resource("/", |r| r.f(|r| httpcodes::HTTPOk)),
|
||||
.resource("/", |r| r.f(|r| httpcodes::HttpOk)),
|
||||
Application::new()
|
||||
.resource("/", |r| r.f(|r| httpcodes::HTTPOk)),
|
||||
.resource("/", |r| r.f(|r| httpcodes::HttpOk)),
|
||||
]);
|
||||
}
|
||||
```
|
||||
|
@ -20,7 +20,7 @@ fn main() {
|
||||
|
||||
HttpServer::new(
|
||||
|| Application::new()
|
||||
.resource("/", |r| r.h(httpcodes::HTTPOk)))
|
||||
.resource("/", |r| r.h(httpcodes::HttpOk)))
|
||||
.bind("127.0.0.1:59080").unwrap()
|
||||
.start();
|
||||
|
||||
@ -57,7 +57,7 @@ fn main() {
|
||||
let sys = actix::System::new("http-server");
|
||||
let addr = HttpServer::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")
|
||||
.shutdown_timeout(60) // <- Set shutdown timeout to 60 seconds
|
||||
.start();
|
||||
@ -85,7 +85,7 @@ use actix_web::*;
|
||||
fn main() {
|
||||
HttpServer::new(
|
||||
|| Application::new()
|
||||
.resource("/", |r| r.h(httpcodes::HTTPOk)))
|
||||
.resource("/", |r| r.h(httpcodes::HttpOk)))
|
||||
.threads(4); // <- Start 4 workers
|
||||
}
|
||||
```
|
||||
@ -146,7 +146,7 @@ use actix_web::*;
|
||||
fn main() {
|
||||
HttpServer::new(||
|
||||
Application::new()
|
||||
.resource("/", |r| r.h(httpcodes::HTTPOk)))
|
||||
.resource("/", |r| r.h(httpcodes::HttpOk)))
|
||||
.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
|
||||
`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*
|
||||
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.
|
||||
|
||||
@ -165,7 +165,7 @@ and is on for *HTTP/1.1* and "HTTP/2.0".
|
||||
use actix_web::*;
|
||||
|
||||
fn index(req: HttpRequest) -> HttpResponse {
|
||||
HTTPOk.build()
|
||||
HttpOk.build()
|
||||
.connection_type(headers::ConnectionType::Close) // <- Close connection
|
||||
.force_close() // <- Alternative method
|
||||
.finish().unwrap()
|
||||
|
@ -65,7 +65,7 @@ impl<S> Handler<S> for MyHandler {
|
||||
/// Handle request
|
||||
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
|
||||
self.0 += 1;
|
||||
httpcodes::HTTPOk.into()
|
||||
httpcodes::HttpOk.into()
|
||||
}
|
||||
}
|
||||
# fn main() {}
|
||||
@ -90,7 +90,7 @@ impl<S> Handler<S> for MyHandler {
|
||||
/// Handle request
|
||||
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
|
||||
self.0.fetch_add(1, Ordering::Relaxed);
|
||||
httpcodes::HTTPOk.into()
|
||||
httpcodes::HttpOk.into()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
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.
|
||||
|
||||
```rust
|
||||
|
@ -32,7 +32,7 @@ fn main() {
|
||||
Application::new()
|
||||
.resource("/prefix", |r| r.f(index))
|
||||
.resource("/user/{name}",
|
||||
|r| r.method(Method::GET).f(|req| HTTPOk))
|
||||
|r| r.method(Method::GET).f(|req| HttpOk))
|
||||
.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.
|
||||
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
|
||||
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
|
||||
resource registration and route registration. Resource matches all routes it contains in
|
||||
@ -68,9 +68,9 @@ fn main() {
|
||||
Application::new()
|
||||
.resource("/path", |resource|
|
||||
resource.route()
|
||||
.p(pred::Get())
|
||||
.p(pred::Header("content-type", "text/plain"))
|
||||
.f(|req| HTTPOk)
|
||||
.filter(pred::Get())
|
||||
.filter(pred::Header("content-type", "text/plain"))
|
||||
.f(|req| HttpOk)
|
||||
)
|
||||
.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
|
||||
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.
|
||||
|
||||
* [*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 {
|
||||
let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource
|
||||
HTTPOk.into()
|
||||
HttpOk.into()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let app = Application::new()
|
||||
.resource("/test/{a}/{b}/{c}", |r| {
|
||||
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();
|
||||
}
|
||||
@ -367,7 +367,7 @@ use actix_web::*;
|
||||
fn index(mut req: HttpRequest) -> Result<HttpResponse> {
|
||||
let url = req.url_for("youtube", &["oHg5SJYRHA0"])?;
|
||||
assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0");
|
||||
Ok(httpcodes::HTTPOk.into())
|
||||
Ok(httpcodes::HttpOk.into())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
@ -404,7 +404,7 @@ This handler designed to be use as a handler for application's *default resource
|
||||
# use actix_web::*;
|
||||
#
|
||||
# fn index(req: HttpRequest) -> httpcodes::StaticResponse {
|
||||
# httpcodes::HTTPOk
|
||||
# httpcodes::HttpOk
|
||||
# }
|
||||
fn main() {
|
||||
let app = Application::new()
|
||||
@ -429,7 +429,7 @@ It is possible to register path normalization only for *GET* requests only
|
||||
# use actix_web::*;
|
||||
#
|
||||
# fn index(req: HttpRequest) -> httpcodes::StaticResponse {
|
||||
# httpcodes::HTTPOk
|
||||
# httpcodes::HttpOk
|
||||
# }
|
||||
fn main() {
|
||||
let app = Application::new()
|
||||
@ -502,8 +502,8 @@ fn main() {
|
||||
Application::new()
|
||||
.resource("/index.html", |r|
|
||||
r.route()
|
||||
.p(ContentTypeHeader)
|
||||
.h(HTTPOk));
|
||||
.filter(ContentTypeHeader)
|
||||
.h(HttpOk));
|
||||
}
|
||||
```
|
||||
|
||||
@ -530,8 +530,8 @@ fn main() {
|
||||
Application::new()
|
||||
.resource("/index.html", |r|
|
||||
r.route()
|
||||
.p(pred::Not(pred::Get()))
|
||||
.f(|req| HTTPMethodNotAllowed))
|
||||
.filter(pred::Not(pred::Get()))
|
||||
.f(|req| HttpMethodNotAllowed))
|
||||
.finish();
|
||||
}
|
||||
```
|
||||
@ -567,8 +567,8 @@ use actix_web::httpcodes::*;
|
||||
fn main() {
|
||||
Application::new()
|
||||
.default_resource(|r| {
|
||||
r.method(Method::GET).f(|req| HTTPNotFound);
|
||||
r.route().p(pred::Not(pred::Get())).f(|req| HTTPMethodNotAllowed);
|
||||
r.method(Method::GET).f(|req| HttpNotFound);
|
||||
r.route().filter(pred::Not(pred::Get())).f(|req| HttpMethodNotAllowed);
|
||||
})
|
||||
# .finish();
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||
req.json().from_err()
|
||||
.and_then(|val: MyObj| {
|
||||
println!("model: {:?}", val);
|
||||
Ok(httpcodes::HTTPOk.build().json(val)?) // <- send response
|
||||
Ok(httpcodes::HttpOk.build().json(val)?) // <- send response
|
||||
})
|
||||
.responder()
|
||||
}
|
||||
@ -117,7 +117,7 @@ fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||
// synchronous workflow
|
||||
.and_then(|body| { // <- body is loaded, now we can deserialize json
|
||||
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()
|
||||
}
|
||||
@ -251,7 +251,7 @@ fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||
.from_err()
|
||||
.and_then(|params| { // <- url encoded parameters
|
||||
println!("==== BODY ==== {:?}", params);
|
||||
ok(httpcodes::HTTPOk.into())
|
||||
ok(httpcodes::HttpOk.into())
|
||||
})
|
||||
.responder()
|
||||
}
|
||||
|
@ -20,10 +20,10 @@ use actix_web::test::TestRequest;
|
||||
fn index(req: HttpRequest) -> HttpResponse {
|
||||
if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) {
|
||||
if let Ok(s) = hdr.to_str() {
|
||||
return httpcodes::HTTPOk.into()
|
||||
return httpcodes::HttpOk.into()
|
||||
}
|
||||
}
|
||||
httpcodes::HTTPBadRequest.into()
|
||||
httpcodes::HttpBadRequest.into()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
@ -59,16 +59,16 @@ use actix_web::*;
|
||||
use actix_web::test::TestServer;
|
||||
|
||||
fn index(req: HttpRequest) -> HttpResponse {
|
||||
httpcodes::HTTPOk.into()
|
||||
httpcodes::HttpOk.into()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut srv = TestServer::new(|app| app.handler(index)); // <- Start new test server
|
||||
|
||||
|
||||
let request = srv.get().finish().unwrap(); // <- create client request
|
||||
let response = srv.execute(request.send()).unwrap(); // <- send request to the server
|
||||
assert!(response.status().is_success()); // <- check response
|
||||
|
||||
|
||||
let bytes = srv.execute(response.body()).unwrap(); // <- read response body
|
||||
}
|
||||
```
|
||||
@ -84,7 +84,7 @@ use actix_web::*;
|
||||
use actix_web::test::TestServer;
|
||||
|
||||
fn index(req: HttpRequest) -> HttpResponse {
|
||||
httpcodes::HTTPOk.into()
|
||||
httpcodes::HttpOk.into()
|
||||
}
|
||||
|
||||
/// This function get called by http server.
|
||||
|
@ -22,7 +22,7 @@ impl Actor for Ws {
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
match msg {
|
||||
|
@ -183,8 +183,8 @@ impl<S> Application<S> where S: 'static {
|
||||
/// let app = Application::new()
|
||||
/// .prefix("/app")
|
||||
/// .resource("/test", |r| {
|
||||
/// r.method(Method::GET).f(|_| httpcodes::HTTPOk);
|
||||
/// r.method(Method::HEAD).f(|_| httpcodes::HTTPMethodNotAllowed);
|
||||
/// r.method(Method::GET).f(|_| httpcodes::HttpOk);
|
||||
/// r.method(Method::HEAD).f(|_| httpcodes::HttpMethodNotAllowed);
|
||||
/// })
|
||||
/// .finish();
|
||||
/// }
|
||||
@ -226,8 +226,8 @@ impl<S> Application<S> where S: 'static {
|
||||
/// fn main() {
|
||||
/// let app = Application::new()
|
||||
/// .resource("/test", |r| {
|
||||
/// r.method(Method::GET).f(|_| httpcodes::HTTPOk);
|
||||
/// r.method(Method::HEAD).f(|_| httpcodes::HTTPMethodNotAllowed);
|
||||
/// r.method(Method::GET).f(|_| httpcodes::HttpOk);
|
||||
/// 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> {
|
||||
/// let url = req.url_for("youtube", &["oHg5SJYRHA0"])?;
|
||||
/// assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0");
|
||||
/// Ok(httpcodes::HTTPOk.into())
|
||||
/// Ok(httpcodes::HttpOk.into())
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
@ -320,9 +320,9 @@ impl<S> Application<S> where S: 'static {
|
||||
/// let app = Application::new()
|
||||
/// .handler("/app", |req: HttpRequest| {
|
||||
/// match *req.method() {
|
||||
/// Method::GET => httpcodes::HTTPOk,
|
||||
/// Method::POST => httpcodes::HTTPMethodNotAllowed,
|
||||
/// _ => httpcodes::HTTPNotFound,
|
||||
/// Method::GET => httpcodes::HttpOk,
|
||||
/// Method::POST => httpcodes::HttpMethodNotAllowed,
|
||||
/// _ => httpcodes::HttpNotFound,
|
||||
/// }});
|
||||
/// }
|
||||
/// ```
|
||||
@ -394,11 +394,11 @@ impl<S> Application<S> where S: 'static {
|
||||
/// HttpServer::new(|| { vec![
|
||||
/// Application::with_state(State1)
|
||||
/// .prefix("/app1")
|
||||
/// .resource("/", |r| r.h(httpcodes::HTTPOk))
|
||||
/// .resource("/", |r| r.h(httpcodes::HttpOk))
|
||||
/// .boxed(),
|
||||
/// Application::with_state(State2)
|
||||
/// .prefix("/app2")
|
||||
/// .resource("/", |r| r.h(httpcodes::HTTPOk))
|
||||
/// .resource("/", |r| r.h(httpcodes::HttpOk))
|
||||
/// .boxed() ]})
|
||||
/// .bind("127.0.0.1:8080").unwrap()
|
||||
/// .run()
|
||||
@ -459,7 +459,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_default_resource() {
|
||||
let mut app = Application::new()
|
||||
.resource("/test", |r| r.h(httpcodes::HTTPOk))
|
||||
.resource("/test", |r| r.h(httpcodes::HttpOk))
|
||||
.finish();
|
||||
|
||||
let req = TestRequest::with_uri("/test").finish();
|
||||
@ -471,7 +471,7 @@ mod tests {
|
||||
assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND);
|
||||
|
||||
let mut app = Application::new()
|
||||
.default_resource(|r| r.h(httpcodes::HTTPMethodNotAllowed))
|
||||
.default_resource(|r| r.h(httpcodes::HttpMethodNotAllowed))
|
||||
.finish();
|
||||
let req = TestRequest::with_uri("/blah").finish();
|
||||
let resp = app.run(req);
|
||||
@ -482,7 +482,7 @@ mod tests {
|
||||
fn test_unhandled_prefix() {
|
||||
let mut app = Application::new()
|
||||
.prefix("/test")
|
||||
.resource("/test", |r| r.h(httpcodes::HTTPOk))
|
||||
.resource("/test", |r| r.h(httpcodes::HttpOk))
|
||||
.finish();
|
||||
assert!(app.handle(HttpRequest::default()).is_err());
|
||||
}
|
||||
@ -490,7 +490,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_state() {
|
||||
let mut app = Application::with_state(10)
|
||||
.resource("/", |r| r.h(httpcodes::HTTPOk))
|
||||
.resource("/", |r| r.h(httpcodes::HttpOk))
|
||||
.finish();
|
||||
let req = HttpRequest::default().with_state(Rc::clone(&app.state), app.router.clone());
|
||||
let resp = app.run(req);
|
||||
@ -501,7 +501,7 @@ mod tests {
|
||||
fn test_prefix() {
|
||||
let mut app = Application::new()
|
||||
.prefix("/test")
|
||||
.resource("/blah", |r| r.h(httpcodes::HTTPOk))
|
||||
.resource("/blah", |r| r.h(httpcodes::HttpOk))
|
||||
.finish();
|
||||
let req = TestRequest::with_uri("/test").finish();
|
||||
let resp = app.handle(req);
|
||||
@ -523,7 +523,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_handler() {
|
||||
let mut app = Application::new()
|
||||
.handler("/test", httpcodes::HTTPOk)
|
||||
.handler("/test", httpcodes::HttpOk)
|
||||
.finish();
|
||||
|
||||
let req = TestRequest::with_uri("/test").finish();
|
||||
@ -551,7 +551,7 @@ mod tests {
|
||||
fn test_handler_prefix() {
|
||||
let mut app = Application::new()
|
||||
.prefix("/app")
|
||||
.handler("/test", httpcodes::HTTPOk)
|
||||
.handler("/test", httpcodes::HttpOk)
|
||||
.finish();
|
||||
|
||||
let req = TestRequest::with_uri("/test").finish();
|
||||
|
31
src/body.rs
31
src/body.rs
@ -36,6 +36,8 @@ pub enum Binary {
|
||||
/// Shared string body
|
||||
#[doc(hidden)]
|
||||
ArcSharedString(Arc<String>),
|
||||
/// Shared vec body
|
||||
SharedVec(Arc<Vec<u8>>),
|
||||
}
|
||||
|
||||
impl Body {
|
||||
@ -115,6 +117,7 @@ impl Binary {
|
||||
Binary::Slice(slice) => slice.len(),
|
||||
Binary::SharedString(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 {
|
||||
Binary::Bytes(ref bytes) => Binary::Bytes(bytes.clone()),
|
||||
Binary::Slice(slice) => Binary::Bytes(Bytes::from(slice)),
|
||||
Binary::SharedString(ref s) => Binary::Bytes(Bytes::from(s.as_str())),
|
||||
Binary::ArcSharedString(ref s) => Binary::Bytes(Bytes::from(s.as_str())),
|
||||
Binary::SharedString(ref s) => Binary::SharedString(s.clone()),
|
||||
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::SharedString(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 {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
match *self {
|
||||
@ -224,6 +241,7 @@ impl AsRef<[u8]> for Binary {
|
||||
Binary::Slice(slice) => slice,
|
||||
Binary::SharedString(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());
|
||||
}
|
||||
|
||||
#[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]
|
||||
fn test_bytes_mut() {
|
||||
let b = BytesMut::from("test");
|
||||
|
@ -2,7 +2,7 @@ use std::mem;
|
||||
use httparse;
|
||||
use http::{Version, HttpTryFrom, HeaderMap, StatusCode};
|
||||
use http::header::{self, HeaderName, HeaderValue};
|
||||
use bytes::{Bytes, BytesMut, BufMut};
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::{Poll, Async};
|
||||
|
||||
use error::{ParseError, PayloadError};
|
||||
@ -37,25 +37,22 @@ impl HttpResponseParser {
|
||||
where T: IoStream
|
||||
{
|
||||
// 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) {
|
||||
Ok(Async::Ready(0)) => {
|
||||
// debug!("Ignored premature client disconnection");
|
||||
return Err(HttpResponseParserError::Disconnect);
|
||||
},
|
||||
Ok(Async::Ready(0)) =>
|
||||
return Err(HttpResponseParserError::Disconnect),
|
||||
Ok(Async::Ready(_)) => (),
|
||||
Ok(Async::NotReady) =>
|
||||
return Ok(Async::NotReady),
|
||||
Err(err) =>
|
||||
return Err(HttpResponseParserError::Error(err.into()))
|
||||
}
|
||||
false
|
||||
} else {
|
||||
true
|
||||
};
|
||||
}
|
||||
|
||||
loop {
|
||||
match HttpResponseParser::parse_message(buf).map_err(HttpResponseParserError::Error)? {
|
||||
match HttpResponseParser::parse_message(buf)
|
||||
.map_err(HttpResponseParserError::Error)?
|
||||
{
|
||||
Async::Ready((msg, decoder)) => {
|
||||
self.decoder = decoder;
|
||||
return Ok(Async::Ready(msg));
|
||||
@ -64,15 +61,13 @@ impl HttpResponseParser {
|
||||
if buf.capacity() >= MAX_BUFFER_SIZE {
|
||||
return Err(HttpResponseParserError::Error(ParseError::TooLarge));
|
||||
}
|
||||
if read || buf.remaining_mut() == 0 {
|
||||
match utils::read_from_io(io, buf) {
|
||||
Ok(Async::Ready(0)) => return Err(HttpResponseParserError::Disconnect),
|
||||
Ok(Async::Ready(_)) => (),
|
||||
Ok(Async::NotReady) => return Ok(Async::NotReady),
|
||||
Err(err) => return Err(HttpResponseParserError::Error(err.into())),
|
||||
}
|
||||
} else {
|
||||
return Ok(Async::NotReady)
|
||||
match utils::read_from_io(io, buf) {
|
||||
Ok(Async::Ready(0)) =>
|
||||
return Err(HttpResponseParserError::Disconnect),
|
||||
Ok(Async::Ready(_)) => (),
|
||||
Ok(Async::NotReady) => return Ok(Async::NotReady),
|
||||
Err(err) =>
|
||||
return Err(HttpResponseParserError::Error(err.into())),
|
||||
}
|
||||
},
|
||||
}
|
||||
@ -84,32 +79,42 @@ impl HttpResponseParser {
|
||||
where T: IoStream
|
||||
{
|
||||
if self.decoder.is_some() {
|
||||
// read payload
|
||||
match utils::read_from_io(io, buf) {
|
||||
Ok(Async::Ready(0)) => {
|
||||
if buf.is_empty() {
|
||||
return Err(PayloadError::Incomplete)
|
||||
loop {
|
||||
// read payload
|
||||
let not_ready = match utils::read_from_io(io, buf) {
|
||||
Ok(Async::Ready(0)) => {
|
||||
if buf.is_empty() {
|
||||
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) {
|
||||
Ok(Async::Ready(Some(b))) => Ok(Async::Ready(Some(b))),
|
||||
Ok(Async::Ready(None)) => {
|
||||
self.decoder.take();
|
||||
Ok(Async::Ready(None))
|
||||
match self.decoder.as_mut().unwrap().decode(buf) {
|
||||
Ok(Async::Ready(Some(b))) =>
|
||||
return Ok(Async::Ready(Some(b))),
|
||||
Ok(Async::Ready(None)) => {
|
||||
self.decoder.take();
|
||||
return Ok(Async::Ready(None))
|
||||
}
|
||||
Ok(Async::NotReady) => {
|
||||
if not_ready {
|
||||
return Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
Err(err) => return Err(err.into()),
|
||||
}
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
Err(err) => Err(err.into()),
|
||||
}
|
||||
} else {
|
||||
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
|
||||
let bytes_ptr = buf.as_ref().as_ptr() as usize;
|
||||
|
@ -111,7 +111,7 @@ impl Future for SendRequest {
|
||||
_ => IoBody::Done,
|
||||
};
|
||||
|
||||
let mut pl = Box::new(Pipeline {
|
||||
let pl = Box::new(Pipeline {
|
||||
body, conn, writer,
|
||||
parser: Some(HttpResponseParser::default()),
|
||||
parser_buf: BytesMut::new(),
|
||||
|
@ -516,7 +516,7 @@ impl ClientRequestBuilder {
|
||||
self.header(header::ACCEPT_ENCODING, "gzip, deflate");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let mut request = self.request.take().expect("cannot reuse request builder");
|
||||
|
||||
// set cookies
|
||||
|
14
src/error.rs
14
src/error.rs
@ -24,7 +24,7 @@ use body::Body;
|
||||
use handler::Responder;
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
use httpcodes::{self, HTTPExpectationFailed};
|
||||
use httpcodes::{self, HttpExpectationFailed};
|
||||
|
||||
/// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html)
|
||||
/// for actix web operations
|
||||
@ -336,7 +336,7 @@ pub enum ExpectError {
|
||||
|
||||
impl ResponseError for ExpectError {
|
||||
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 {
|
||||
match *self {
|
||||
UrlencodedError::Overflow => httpcodes::HTTPPayloadTooLarge.into(),
|
||||
UrlencodedError::UnknownLength => httpcodes::HTTPLengthRequired.into(),
|
||||
_ => httpcodes::HTTPBadRequest.into(),
|
||||
UrlencodedError::Overflow => httpcodes::HttpPayloadTooLarge.into(),
|
||||
UrlencodedError::UnknownLength => httpcodes::HttpLengthRequired.into(),
|
||||
_ => httpcodes::HttpBadRequest.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -421,8 +421,8 @@ impl ResponseError for JsonPayloadError {
|
||||
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
match *self {
|
||||
JsonPayloadError::Overflow => httpcodes::HTTPPayloadTooLarge.into(),
|
||||
_ => httpcodes::HTTPBadRequest.into(),
|
||||
JsonPayloadError::Overflow => httpcodes::HttpPayloadTooLarge.into(),
|
||||
_ => httpcodes::HttpBadRequest.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ use handler::{Handler, Responder};
|
||||
use headers::ContentEncoding;
|
||||
use httprequest::HttpRequest;
|
||||
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
|
||||
/// file extension.
|
||||
@ -84,7 +84,7 @@ impl Responder for NamedFile {
|
||||
type Error = io::Error;
|
||||
|
||||
fn respond_to(mut self, _: HttpRequest) -> Result<HttpResponse, io::Error> {
|
||||
let mut resp = HTTPOk.build();
|
||||
let mut resp = HttpOk.build();
|
||||
resp.content_encoding(ContentEncoding::Identity);
|
||||
if let Some(ext) = self.path().extension() {
|
||||
let mime = get_mime_type(&ext.to_string_lossy());
|
||||
@ -164,7 +164,7 @@ impl Responder for Directory {
|
||||
<ul>\
|
||||
{}\
|
||||
</ul></body>\n</html>", index_of, index_of, body);
|
||||
Ok(HTTPOk.build()
|
||||
Ok(HttpOk.build()
|
||||
.content_type("text/html; charset=utf-8")
|
||||
.body(html).unwrap())
|
||||
}
|
||||
@ -289,7 +289,7 @@ impl<S> Handler<S> for StaticFiles {
|
||||
}
|
||||
new_path.push_str(redir_index);
|
||||
Ok(FilesystemElement::Redirect(
|
||||
HTTPFound
|
||||
HttpFound
|
||||
.build()
|
||||
.header::<_, &str>("LOCATION", &new_path)
|
||||
.finish().unwrap()))
|
||||
|
@ -309,7 +309,7 @@ impl<S, H, F, R, E> RouteHandler<S> for AsyncHandler<S, H, F, R, E>
|
||||
/// # use actix_web::*;
|
||||
/// #
|
||||
/// # fn index(req: HttpRequest) -> httpcodes::StaticResponse {
|
||||
/// # httpcodes::HTTPOk
|
||||
/// # httpcodes::HttpOk
|
||||
/// # }
|
||||
/// fn main() {
|
||||
/// let app = Application::new()
|
||||
|
107
src/httpcodes.rs
107
src/httpcodes.rs
@ -7,67 +7,174 @@ use handler::{Reply, Handler, RouteHandler, Responder};
|
||||
use httprequest::HttpRequest;
|
||||
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);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPCreated: StaticResponse = StaticResponse(StatusCode::CREATED);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPAccepted: StaticResponse = StaticResponse(StatusCode::ACCEPTED);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPNonAuthoritativeInformation: StaticResponse =
|
||||
StaticResponse(StatusCode::NON_AUTHORITATIVE_INFORMATION);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPNoContent: StaticResponse = StaticResponse(StatusCode::NO_CONTENT);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPResetContent: StaticResponse = StaticResponse(StatusCode::RESET_CONTENT);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPPartialContent: StaticResponse = StaticResponse(StatusCode::PARTIAL_CONTENT);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPMultiStatus: StaticResponse = StaticResponse(StatusCode::MULTI_STATUS);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPAlreadyReported: StaticResponse = StaticResponse(StatusCode::ALREADY_REPORTED);
|
||||
|
||||
#[doc(hidden)]
|
||||
pub const HTTPMultipleChoices: StaticResponse = StaticResponse(StatusCode::MULTIPLE_CHOICES);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPMovedPermanenty: StaticResponse = StaticResponse(StatusCode::MOVED_PERMANENTLY);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPFound: StaticResponse = StaticResponse(StatusCode::FOUND);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPSeeOther: StaticResponse = StaticResponse(StatusCode::SEE_OTHER);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPNotModified: StaticResponse = StaticResponse(StatusCode::NOT_MODIFIED);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPUseProxy: StaticResponse = StaticResponse(StatusCode::USE_PROXY);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPTemporaryRedirect: StaticResponse =
|
||||
StaticResponse(StatusCode::TEMPORARY_REDIRECT);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPPermanentRedirect: StaticResponse =
|
||||
StaticResponse(StatusCode::PERMANENT_REDIRECT);
|
||||
|
||||
#[doc(hidden)]
|
||||
pub const HTTPBadRequest: StaticResponse = StaticResponse(StatusCode::BAD_REQUEST);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPUnauthorized: StaticResponse = StaticResponse(StatusCode::UNAUTHORIZED);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPPaymentRequired: StaticResponse = StaticResponse(StatusCode::PAYMENT_REQUIRED);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPForbidden: StaticResponse = StaticResponse(StatusCode::FORBIDDEN);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPNotFound: StaticResponse = StaticResponse(StatusCode::NOT_FOUND);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPMethodNotAllowed: StaticResponse =
|
||||
StaticResponse(StatusCode::METHOD_NOT_ALLOWED);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPNotAcceptable: StaticResponse = StaticResponse(StatusCode::NOT_ACCEPTABLE);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPProxyAuthenticationRequired: StaticResponse =
|
||||
StaticResponse(StatusCode::PROXY_AUTHENTICATION_REQUIRED);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPRequestTimeout: StaticResponse = StaticResponse(StatusCode::REQUEST_TIMEOUT);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPConflict: StaticResponse = StaticResponse(StatusCode::CONFLICT);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPGone: StaticResponse = StaticResponse(StatusCode::GONE);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPLengthRequired: StaticResponse = StaticResponse(StatusCode::LENGTH_REQUIRED);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPPreconditionFailed: StaticResponse =
|
||||
StaticResponse(StatusCode::PRECONDITION_FAILED);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPPayloadTooLarge: StaticResponse = StaticResponse(StatusCode::PAYLOAD_TOO_LARGE);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPUriTooLong: StaticResponse = StaticResponse(StatusCode::URI_TOO_LONG);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPUnsupportedMediaType: StaticResponse =
|
||||
StaticResponse(StatusCode::UNSUPPORTED_MEDIA_TYPE);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPRangeNotSatisfiable: StaticResponse =
|
||||
StaticResponse(StatusCode::RANGE_NOT_SATISFIABLE);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPExpectationFailed: StaticResponse =
|
||||
StaticResponse(StatusCode::EXPECTATION_FAILED);
|
||||
|
||||
#[doc(hidden)]
|
||||
pub const HTTPInternalServerError: StaticResponse =
|
||||
StaticResponse(StatusCode::INTERNAL_SERVER_ERROR);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPNotImplemented: StaticResponse = StaticResponse(StatusCode::NOT_IMPLEMENTED);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPBadGateway: StaticResponse = StaticResponse(StatusCode::BAD_GATEWAY);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPServiceUnavailable: StaticResponse =
|
||||
StaticResponse(StatusCode::SERVICE_UNAVAILABLE);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPGatewayTimeout: StaticResponse =
|
||||
StaticResponse(StatusCode::GATEWAY_TIMEOUT);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPVersionNotSupported: StaticResponse =
|
||||
StaticResponse(StatusCode::HTTP_VERSION_NOT_SUPPORTED);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPVariantAlsoNegotiates: StaticResponse =
|
||||
StaticResponse(StatusCode::VARIANT_ALSO_NEGOTIATES);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPInsufficientStorage: StaticResponse =
|
||||
StaticResponse(StatusCode::INSUFFICIENT_STORAGE);
|
||||
#[doc(hidden)]
|
||||
pub const HTTPLoopDetected: StaticResponse = StaticResponse(StatusCode::LOOP_DETECTED);
|
||||
|
||||
|
||||
|
@ -114,7 +114,7 @@ pub trait HttpMessage {
|
||||
/// .from_err()
|
||||
/// .and_then(|bytes: Bytes| { // <- complete body
|
||||
/// println!("==== BODY ==== {:?}", bytes);
|
||||
/// Ok(httpcodes::HTTPOk.into())
|
||||
/// Ok(httpcodes::HttpOk.into())
|
||||
/// }).responder()
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
@ -148,7 +148,7 @@ pub trait HttpMessage {
|
||||
/// .from_err()
|
||||
/// .and_then(|params| { // <- url encoded parameters
|
||||
/// println!("==== BODY ==== {:?}", params);
|
||||
/// ok(httpcodes::HTTPOk.into())
|
||||
/// ok(httpcodes::HttpOk.into())
|
||||
/// })
|
||||
/// .responder()
|
||||
/// }
|
||||
@ -187,7 +187,7 @@ pub trait HttpMessage {
|
||||
/// .from_err()
|
||||
/// .and_then(|val: MyObj| { // <- deserialized value
|
||||
/// println!("==== BODY ==== {:?}", val);
|
||||
/// Ok(httpcodes::HTTPOk.into())
|
||||
/// Ok(httpcodes::HttpOk.into())
|
||||
/// }).responder()
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
|
@ -249,14 +249,14 @@ impl<S> HttpRequest<S> {
|
||||
/// #
|
||||
/// fn index(req: HttpRequest) -> HttpResponse {
|
||||
/// let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource
|
||||
/// HTTPOk.into()
|
||||
/// HttpOk.into()
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = Application::new()
|
||||
/// .resource("/test/{one}/{two}/{three}", |r| {
|
||||
/// 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();
|
||||
/// }
|
||||
@ -365,7 +365,7 @@ impl<S> HttpRequest<S> {
|
||||
|
||||
/// Get mutable reference to request's Params.
|
||||
#[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) }
|
||||
}
|
||||
|
||||
|
@ -252,7 +252,7 @@ impl HttpResponseBuilder {
|
||||
/// use http::header;
|
||||
///
|
||||
/// fn index(req: HttpRequest) -> Result<HttpResponse> {
|
||||
/// Ok(HTTPOk.build()
|
||||
/// Ok(HttpOk.build()
|
||||
/// .header("X-TEST", "value")
|
||||
/// .header(header::CONTENT_TYPE, "application/json")
|
||||
/// .finish()?)
|
||||
@ -372,7 +372,7 @@ impl HttpResponseBuilder {
|
||||
/// use actix_web::headers::Cookie;
|
||||
///
|
||||
/// fn index(req: HttpRequest) -> Result<HttpResponse> {
|
||||
/// Ok(HTTPOk.build()
|
||||
/// Ok(HttpOk.build()
|
||||
/// .cookie(
|
||||
/// Cookie::build("name", "value")
|
||||
/// .domain("www.rust-lang.org")
|
||||
@ -753,7 +753,7 @@ mod tests {
|
||||
Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None);
|
||||
let cookies = req.cookies().unwrap();
|
||||
|
||||
let resp = httpcodes::HTTPOk
|
||||
let resp = httpcodes::HttpOk
|
||||
.build()
|
||||
.cookie(headers::Cookie::build("name", "value")
|
||||
.domain("www.rust-lang.org")
|
||||
|
@ -75,7 +75,7 @@ impl<T: Serialize> Responder for Json<T> {
|
||||
/// .from_err()
|
||||
/// .and_then(|val: MyObj| { // <- deserialized value
|
||||
/// println!("==== BODY ==== {:?}", val);
|
||||
/// Ok(httpcodes::HTTPOk.into())
|
||||
/// Ok(httpcodes::HttpOk.into())
|
||||
/// }).responder()
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
|
@ -35,10 +35,11 @@
|
||||
//! * `WebSockets` server/client
|
||||
//! * Transparent content compression/decompression (br, gzip, deflate)
|
||||
//! * Configurable request routing
|
||||
//! * Multipart streams
|
||||
//! * Middlewares (`Logger`, `Session`, `CORS`, `DefaultHeaders`)
|
||||
//! * 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(
|
||||
specialization, // for impl ErrorResponse for std::error::Error
|
||||
@ -188,6 +189,7 @@ pub mod dev {
|
||||
//! ```
|
||||
|
||||
pub use body::BodyStream;
|
||||
pub use context::Drain;
|
||||
pub use info::ConnectionInfo;
|
||||
pub use handler::Handler;
|
||||
pub use json::JsonBody;
|
||||
|
@ -38,8 +38,8 @@
|
||||
//! .max_age(3600)
|
||||
//! .finish().expect("Can not create CORS middleware")
|
||||
//! .register(r); // <- Register CORS middleware
|
||||
//! r.method(Method::GET).f(|_| httpcodes::HTTPOk);
|
||||
//! r.method(Method::HEAD).f(|_| httpcodes::HTTPMethodNotAllowed);
|
||||
//! r.method(Method::GET).f(|_| httpcodes::HttpOk);
|
||||
//! r.method(Method::HEAD).f(|_| httpcodes::HttpMethodNotAllowed);
|
||||
//! })
|
||||
//! .finish();
|
||||
//! }
|
||||
@ -58,7 +58,7 @@ use resource::Resource;
|
||||
use httpmessage::HttpMessage;
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
use httpcodes::{HTTPOk, HTTPBadRequest};
|
||||
use httpcodes::{HttpOk, HttpBadRequest};
|
||||
use middleware::{Middleware, Response, Started};
|
||||
|
||||
/// A set of errors that can occur during processing CORS
|
||||
@ -110,7 +110,7 @@ pub enum CorsBuilderError {
|
||||
impl ResponseError for CorsError {
|
||||
|
||||
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*
|
||||
/// requests.
|
||||
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);
|
||||
}
|
||||
|
||||
@ -307,7 +307,7 @@ impl<S> Middleware<S> for Cors {
|
||||
};
|
||||
|
||||
Ok(Started::Response(
|
||||
HTTPOk.build()
|
||||
HttpOk.build()
|
||||
.if_some(self.max_age.as_ref(), |max_age, resp| {
|
||||
let _ = resp.header(
|
||||
header::ACCESS_CONTROL_MAX_AGE, format!("{}", max_age).as_str());})
|
||||
@ -823,7 +823,7 @@ mod tests {
|
||||
.method(Method::OPTIONS)
|
||||
.finish();
|
||||
|
||||
let resp: HttpResponse = HTTPOk.into();
|
||||
let resp: HttpResponse = HttpOk.into();
|
||||
let resp = cors.response(&mut req, resp).unwrap().response();
|
||||
assert_eq!(
|
||||
&b"*"[..],
|
||||
@ -832,7 +832,7 @@ mod tests {
|
||||
&b"Origin"[..],
|
||||
resp.headers().get(header::VARY).unwrap().as_bytes());
|
||||
|
||||
let resp: HttpResponse = HTTPOk.build()
|
||||
let resp: HttpResponse = HttpOk.build()
|
||||
.header(header::VARY, "Accept")
|
||||
.finish().unwrap();
|
||||
let resp = cors.response(&mut req, resp).unwrap().response();
|
||||
@ -844,7 +844,7 @@ mod tests {
|
||||
.disable_vary_header()
|
||||
.allowed_origin("https://www.example.com")
|
||||
.finish().unwrap();
|
||||
let resp: HttpResponse = HTTPOk.into();
|
||||
let resp: HttpResponse = HttpOk.into();
|
||||
let resp = cors.response(&mut req, resp).unwrap().response();
|
||||
assert_eq!(
|
||||
&b"https://www.example.com"[..],
|
||||
|
266
src/middleware/csrf.rs
Normal file
266
src/middleware/csrf.rs
Normal 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());
|
||||
}
|
||||
}
|
@ -22,8 +22,8 @@ use middleware::{Response, Middleware};
|
||||
/// .header("X-Version", "0.2")
|
||||
/// .finish())
|
||||
/// .resource("/test", |r| {
|
||||
/// r.method(Method::GET).f(|_| httpcodes::HTTPOk);
|
||||
/// r.method(Method::HEAD).f(|_| httpcodes::HTTPMethodNotAllowed);
|
||||
/// r.method(Method::GET).f(|_| httpcodes::HttpOk);
|
||||
/// r.method(Method::HEAD).f(|_| httpcodes::HttpMethodNotAllowed);
|
||||
/// })
|
||||
/// .finish();
|
||||
/// }
|
||||
|
@ -9,6 +9,7 @@ mod logger;
|
||||
mod session;
|
||||
mod defaultheaders;
|
||||
pub mod cors;
|
||||
pub mod csrf;
|
||||
pub use self::logger::Logger;
|
||||
pub use self::defaultheaders::{DefaultHeaders, DefaultHeadersBuilder};
|
||||
pub use self::session::{RequestSession, Session, SessionImpl, SessionBackend, SessionStorage,
|
||||
|
@ -86,7 +86,7 @@ impl<'a> Session<'a> {
|
||||
}
|
||||
|
||||
/// 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)?);
|
||||
Ok(())
|
||||
}
|
||||
|
@ -168,7 +168,7 @@ impl Inner {
|
||||
len: 0,
|
||||
err: None,
|
||||
items: VecDeque::new(),
|
||||
need_read: false,
|
||||
need_read: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -173,7 +173,8 @@ impl<S: 'static, H: PipelineHandler<S>> HttpHandlerTask for Pipeline<S, H> {
|
||||
PipelineState::None =>
|
||||
return Ok(Async::Ready(true)),
|
||||
PipelineState::Error =>
|
||||
return Err(io::Error::new(io::ErrorKind::Other, "Internal error").into()),
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other, "Internal error").into()),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
|
@ -28,8 +28,8 @@ pub trait Predicate<S> {
|
||||
/// fn main() {
|
||||
/// Application::new()
|
||||
/// .resource("/index.html", |r| r.route()
|
||||
/// .p(pred::Any(pred::Get()).or(pred::Post()))
|
||||
/// .h(HTTPMethodNotAllowed));
|
||||
/// .filter(pred::Any(pred::Get()).or(pred::Post()))
|
||||
/// .h(HttpMethodNotAllowed));
|
||||
/// }
|
||||
/// ```
|
||||
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() {
|
||||
/// Application::new()
|
||||
/// .resource("/index.html", |r| r.route()
|
||||
/// .p(pred::All(pred::Get())
|
||||
/// .filter(pred::All(pred::Get())
|
||||
/// .and(pred::Header("content-type", "plain/text")))
|
||||
/// .h(HTTPMethodNotAllowed));
|
||||
/// .h(HttpMethodNotAllowed));
|
||||
/// }
|
||||
/// ```
|
||||
pub fn All<S: 'static, P: Predicate<S> + 'static>(pred: P) -> AllPredicate<S> {
|
||||
|
@ -81,8 +81,8 @@ impl<S: 'static> Resource<S> {
|
||||
/// let app = Application::new()
|
||||
/// .resource(
|
||||
/// "/", |r| r.route()
|
||||
/// .p(pred::Any(pred::Get()).or(pred::Put()))
|
||||
/// .p(pred::Header("Content-Type", "text/plain"))
|
||||
/// .filter(pred::Any(pred::Get()).or(pred::Put()))
|
||||
/// .filter(pred::Header("Content-Type", "text/plain"))
|
||||
/// .f(|r| HttpResponse::Ok()))
|
||||
/// .finish();
|
||||
/// }
|
||||
@ -97,11 +97,11 @@ impl<S: 'static> Resource<S> {
|
||||
/// This is shortcut for:
|
||||
///
|
||||
/// ```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> {
|
||||
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.
|
||||
|
18
src/route.rs
18
src/route.rs
@ -8,7 +8,7 @@ use pred::Predicate;
|
||||
use handler::{Reply, ReplyItem, Handler,
|
||||
Responder, RouteHandler, AsyncHandler, WrapHandler};
|
||||
use middleware::{Middleware, Response as MiddlewareResponse, Started as MiddlewareStarted};
|
||||
use httpcodes::HTTPNotFound;
|
||||
use httpcodes::HttpNotFound;
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
|
||||
@ -26,7 +26,7 @@ impl<S: 'static> Default for Route<S> {
|
||||
fn default() -> Route<S> {
|
||||
Route {
|
||||
preds: Vec::new(),
|
||||
handler: InnerHandler::new(|_| HTTPNotFound),
|
||||
handler: InnerHandler::new(|_| HttpNotFound),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -65,18 +65,24 @@ impl<S: 'static> Route<S> {
|
||||
/// Application::new()
|
||||
/// .resource("/path", |r|
|
||||
/// r.route()
|
||||
/// .p(pred::Get())
|
||||
/// .p(pred::Header("content-type", "text/plain"))
|
||||
/// .f(|req| HTTPOk)
|
||||
/// .filter(pred::Get())
|
||||
/// .filter(pred::Header("content-type", "text/plain"))
|
||||
/// .f(|req| HttpOk)
|
||||
/// )
|
||||
/// # .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
|
||||
}
|
||||
|
||||
#[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
|
||||
/// during route configuration, because it does not return reference to self.
|
||||
pub fn h<H: Handler<S>>(&mut self, handler: H) {
|
||||
|
@ -148,7 +148,12 @@ impl Pattern {
|
||||
///
|
||||
/// Panics if path pattern is wrong.
|
||||
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 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 {
|
||||
PatternType::Static(ref s) => s == path,
|
||||
PatternType::Dynamic(ref re, ref names) => {
|
||||
@ -236,11 +243,11 @@ impl Pattern {
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
fn parse(pattern: &str) -> (String, Vec<PatternElement>, bool) {
|
||||
fn parse(pattern: &str, prefix: &str) -> (String, Vec<PatternElement>, bool) {
|
||||
const DEFAULT_PATTERN: &str = "[^/]+";
|
||||
|
||||
let mut re1 = String::from("^/");
|
||||
let mut re2 = String::from("/");
|
||||
let mut re1 = String::from("^") + prefix;
|
||||
let mut re2 = String::from(prefix);
|
||||
let mut el = String::new();
|
||||
let mut in_param = false;
|
||||
let mut in_param_pattern = false;
|
||||
|
@ -18,15 +18,6 @@ enum HttpProtocol<T: IoStream, H: 'static> {
|
||||
Unknown(Rc<WorkerSettings<H>>, Option<SocketAddr>, T, BytesMut),
|
||||
}
|
||||
|
||||
impl<T: IoStream, H: 'static> HttpProtocol<T, H> {
|
||||
fn is_unknown(&self) -> bool {
|
||||
match *self {
|
||||
HttpProtocol::Unknown(_, _, _, _) => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ProtocolKind {
|
||||
Http1,
|
||||
Http2,
|
||||
@ -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
|
||||
{
|
||||
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();
|
||||
let _ = io.set_nodelay(true);
|
||||
|
||||
if http2 {
|
||||
HttpChannel {
|
||||
node: None,
|
||||
proto: Some(HttpProtocol::H2(
|
||||
node: None, proto: Some(HttpProtocol::H2(
|
||||
h2::Http2::new(settings, io, peer, Bytes::new()))) }
|
||||
} else {
|
||||
HttpChannel {
|
||||
node: None,
|
||||
proto: Some(HttpProtocol::Unknown(
|
||||
node: None, proto: Some(HttpProtocol::Unknown(
|
||||
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 = ();
|
||||
|
||||
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() {
|
||||
self.node = Some(Node::new(self));
|
||||
match self.proto {
|
||||
if !self.node.is_none() {
|
||||
let el = self as *mut _;
|
||||
self.node = Some(Node::new(el));
|
||||
let _ = match self.proto {
|
||||
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)) =>
|
||||
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 {
|
||||
@ -95,7 +89,7 @@ impl<T, H> Future for HttpChannel<T, H> where T: IoStream, H: HttpHandler + 'sta
|
||||
match result {
|
||||
Ok(Async::Ready(())) | Err(_) => {
|
||||
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 {
|
||||
Ok(Async::Ready(())) | Err(_) => {
|
||||
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(_) => {
|
||||
debug!("Ignored premature client disconnection");
|
||||
settings.remove_channel();
|
||||
self.node.as_mut().map(|n| n.remove());
|
||||
return Err(())
|
||||
},
|
||||
_ => (),
|
||||
@ -163,11 +158,11 @@ pub(crate) struct Node<T>
|
||||
|
||||
impl<T> Node<T>
|
||||
{
|
||||
fn new(el: &mut T) -> Self {
|
||||
fn new(el: *mut T) -> Self {
|
||||
Node {
|
||||
next: None,
|
||||
prev: None,
|
||||
element: el as *mut _,
|
||||
element: el,
|
||||
}
|
||||
}
|
||||
|
||||
@ -186,13 +181,14 @@ impl<T> Node<T>
|
||||
}
|
||||
}
|
||||
|
||||
fn remove(&self) {
|
||||
#[allow(mutable_transmutes)]
|
||||
fn remove(&mut self) {
|
||||
unsafe {
|
||||
if let Some(ref prev) = self.prev {
|
||||
let p: &mut Node<()> = mem::transmute(prev.as_ref().unwrap());
|
||||
let slf: &mut Node<T> = mem::transmute(self);
|
||||
p.next = slf.next.take();
|
||||
self.element = ptr::null_mut();
|
||||
let next = self.next.take();
|
||||
let mut prev = self.prev.take();
|
||||
|
||||
if let Some(ref mut prev) = prev {
|
||||
prev.as_mut().unwrap().next = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ use flate2::Compression;
|
||||
use flate2::read::GzDecoder;
|
||||
use flate2::write::{GzEncoder, DeflateDecoder, DeflateEncoder};
|
||||
use brotli2::write::{BrotliDecoder, BrotliEncoder};
|
||||
use bytes::{Bytes, BytesMut, BufMut, Writer};
|
||||
use bytes::{Bytes, BytesMut, BufMut};
|
||||
|
||||
use headers::ContentEncoding;
|
||||
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
|
||||
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 {
|
||||
Deflate(DeflateEncoder<TransferEncoding>),
|
||||
Gzip(GzEncoder<TransferEncoding>),
|
||||
|
107
src/server/h1.rs
107
src/server/h1.rs
@ -10,12 +10,12 @@ use actix::Arbiter;
|
||||
use httparse;
|
||||
use http::{Uri, Method, Version, HttpTryFrom, HeaderMap};
|
||||
use http::header::{self, HeaderName, HeaderValue};
|
||||
use bytes::{Bytes, BytesMut, BufMut};
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::{Future, Poll, Async};
|
||||
use tokio_core::reactor::Timeout;
|
||||
|
||||
use pipeline::Pipeline;
|
||||
use httpcodes::HTTPNotFound;
|
||||
use httpcodes::HttpNotFound;
|
||||
use httprequest::HttpRequest;
|
||||
use error::{ParseError, PayloadError, ResponseError};
|
||||
use payload::{Payload, PayloadWriter, PayloadStatus};
|
||||
@ -125,8 +125,8 @@ impl<T, H> Http1<T, H>
|
||||
// TODO: refactor
|
||||
pub fn poll_io(&mut self) -> Poll<bool, ()> {
|
||||
// read incoming data
|
||||
let need_read =
|
||||
if !self.flags.contains(Flags::ERROR) && self.tasks.len() < MAX_PIPELINED_MESSAGES
|
||||
let need_read = if !self.flags.intersects(Flags::ERROR) &&
|
||||
self.tasks.len() < MAX_PIPELINED_MESSAGES
|
||||
{
|
||||
'outer: loop {
|
||||
match self.reader.parse(self.stream.get_mut(),
|
||||
@ -151,7 +151,7 @@ impl<T, H> Http1<T, H>
|
||||
}
|
||||
|
||||
self.tasks.push_back(
|
||||
Entry {pipe: Pipeline::error(HTTPNotFound),
|
||||
Entry {pipe: Pipeline::error(HttpNotFound),
|
||||
flags: EntryFlags::empty()});
|
||||
continue
|
||||
},
|
||||
@ -402,40 +402,49 @@ impl Reader {
|
||||
// read payload
|
||||
let done = {
|
||||
if let Some(ref mut payload) = self.payload {
|
||||
match utils::read_from_io(io, buf) {
|
||||
Ok(Async::Ready(0)) => {
|
||||
payload.tx.set_error(PayloadError::Incomplete);
|
||||
'buf: loop {
|
||||
let not_ready = match utils::read_from_io(io, buf) {
|
||||
Ok(Async::Ready(0)) => {
|
||||
payload.tx.set_error(PayloadError::Incomplete);
|
||||
|
||||
// http channel should not deal with payload errors
|
||||
return Err(ReaderError::Payload)
|
||||
},
|
||||
Err(err) => {
|
||||
payload.tx.set_error(err.into());
|
||||
|
||||
// http channel should not deal with payload errors
|
||||
return Err(ReaderError::Payload)
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
loop {
|
||||
match payload.decoder.decode(buf) {
|
||||
Ok(Async::Ready(Some(bytes))) => {
|
||||
payload.tx.feed_data(bytes);
|
||||
if payload.decoder.is_eof() {
|
||||
payload.tx.feed_eof();
|
||||
break true
|
||||
}
|
||||
// http channel should not deal with payload errors
|
||||
return Err(ReaderError::Payload)
|
||||
},
|
||||
Ok(Async::Ready(None)) => {
|
||||
payload.tx.feed_eof();
|
||||
break true
|
||||
},
|
||||
Ok(Async::NotReady) =>
|
||||
break false,
|
||||
Ok(Async::NotReady) => true,
|
||||
Err(err) => {
|
||||
payload.tx.set_error(err.into());
|
||||
|
||||
// http channel should not deal with payload errors
|
||||
return Err(ReaderError::Payload)
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
loop {
|
||||
match payload.decoder.decode(buf) {
|
||||
Ok(Async::Ready(Some(bytes))) => {
|
||||
payload.tx.feed_data(bytes);
|
||||
if payload.decoder.is_eof() {
|
||||
payload.tx.feed_eof();
|
||||
break 'buf true
|
||||
}
|
||||
},
|
||||
Ok(Async::Ready(None)) => {
|
||||
payload.tx.feed_eof();
|
||||
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
|
||||
},
|
||||
Err(err) => {
|
||||
payload.tx.set_error(err.into());
|
||||
return Err(ReaderError::Payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -445,16 +454,13 @@ impl Reader {
|
||||
if done { self.payload = None }
|
||||
|
||||
// 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) {
|
||||
Ok(Async::Ready(0)) => return Err(ReaderError::Disconnect),
|
||||
Ok(Async::Ready(_)) => (),
|
||||
Ok(Async::NotReady) => return Ok(Async::NotReady),
|
||||
Err(err) => return Err(ReaderError::Error(err.into()))
|
||||
}
|
||||
false
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
loop {
|
||||
@ -470,22 +476,18 @@ impl Reader {
|
||||
return Ok(Async::Ready(msg));
|
||||
},
|
||||
Async::NotReady => {
|
||||
if buf.capacity() >= MAX_BUFFER_SIZE {
|
||||
if buf.len() >= MAX_BUFFER_SIZE {
|
||||
error!("MAX_BUFFER_SIZE unprocessed data reached, closing");
|
||||
return Err(ReaderError::Error(ParseError::TooLarge));
|
||||
}
|
||||
if read || buf.remaining_mut() == 0 {
|
||||
match utils::read_from_io(io, buf) {
|
||||
Ok(Async::Ready(0)) => {
|
||||
debug!("Ignored premature client disconnection");
|
||||
return Err(ReaderError::Disconnect);
|
||||
},
|
||||
Ok(Async::Ready(_)) => (),
|
||||
Ok(Async::NotReady) => return Ok(Async::NotReady),
|
||||
Err(err) => return Err(ReaderError::Error(err.into())),
|
||||
}
|
||||
} else {
|
||||
return Ok(Async::NotReady)
|
||||
match utils::read_from_io(io, buf) {
|
||||
Ok(Async::Ready(0)) => {
|
||||
debug!("Ignored premature client disconnection");
|
||||
return Err(ReaderError::Disconnect);
|
||||
},
|
||||
Ok(Async::Ready(_)) => (),
|
||||
Ok(Async::NotReady) => return Ok(Async::NotReady),
|
||||
Err(err) => return Err(ReaderError::Error(err.into())),
|
||||
}
|
||||
},
|
||||
}
|
||||
@ -1413,6 +1415,10 @@ mod tests {
|
||||
assert!(req.chunked().unwrap());
|
||||
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");
|
||||
not_ready!(reader.parse(&mut buf, &mut readbuf, &settings));
|
||||
|
||||
@ -1430,6 +1436,7 @@ mod tests {
|
||||
buf.feed_data("ne\r\n0\r\n");
|
||||
not_ready!(reader.parse(&mut buf, &mut readbuf, &settings));
|
||||
|
||||
//trailers
|
||||
//buf.feed_data("test: test\r\n");
|
||||
//not_ready!(reader.parse(&mut buf, &mut readbuf));
|
||||
|
||||
|
@ -18,7 +18,7 @@ use tokio_core::reactor::Timeout;
|
||||
|
||||
use pipeline::Pipeline;
|
||||
use error::PayloadError;
|
||||
use httpcodes::HTTPNotFound;
|
||||
use httpcodes::HttpNotFound;
|
||||
use httpmessage::HttpMessage;
|
||||
use httprequest::HttpRequest;
|
||||
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,
|
||||
stream: H2Writer::new(resp, settings.get_shared_bytes()),
|
||||
flags: EntryFlags::empty(),
|
||||
|
@ -63,7 +63,7 @@ pub(crate) struct WorkerSettings<H> {
|
||||
bytes: Rc<SharedBytesPool>,
|
||||
messages: Rc<helpers::SharedMessagePool>,
|
||||
channels: Cell<usize>,
|
||||
node: Node<()>,
|
||||
node: Box<Node<()>>,
|
||||
}
|
||||
|
||||
impl<H> WorkerSettings<H> {
|
||||
@ -75,7 +75,7 @@ impl<H> WorkerSettings<H> {
|
||||
bytes: Rc::new(SharedBytesPool::new()),
|
||||
messages: Rc::new(helpers::SharedMessagePool::new()),
|
||||
channels: Cell::new(0),
|
||||
node: Node::head(),
|
||||
node: Box::new(Node::head()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -261,7 +261,7 @@ impl<H: IntoHttpHandler> HttpServer<H>
|
||||
///
|
||||
/// HttpServer::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")
|
||||
/// .start();
|
||||
/// # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
|
||||
@ -312,7 +312,7 @@ impl<H: IntoHttpHandler> HttpServer<H>
|
||||
/// fn main() {
|
||||
/// HttpServer::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")
|
||||
/// .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::V6(_) => TcpBuilder::new_v6()?,
|
||||
};
|
||||
builder.bind(addr)?;
|
||||
builder.reuse_address(true)?;
|
||||
builder.bind(addr)?;
|
||||
Ok(builder.listen(backlog)?)
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ use futures::{Async, Poll};
|
||||
use super::IoStream;
|
||||
|
||||
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> {
|
||||
|
14
src/test.rs
14
src/test.rs
@ -14,6 +14,7 @@ use tokio_core::net::TcpListener;
|
||||
use tokio_core::reactor::Core;
|
||||
use net2::TcpBuilder;
|
||||
|
||||
use ws;
|
||||
use body::Binary;
|
||||
use error::Error;
|
||||
use handler::{Handler, Responder, ReplyItem};
|
||||
@ -25,7 +26,6 @@ use payload::Payload;
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
use server::{HttpServer, IntoHttpHandler, ServerSettings};
|
||||
use ws::{WsClient, WsClientError, WsClientReader, WsClientWriter};
|
||||
use client::{ClientRequest, ClientRequestBuilder};
|
||||
|
||||
/// The `TestServer` type.
|
||||
@ -41,7 +41,7 @@ use client::{ClientRequest, ClientRequestBuilder};
|
||||
/// # use actix_web::*;
|
||||
/// #
|
||||
/// # fn my_handler(req: HttpRequest) -> HttpResponse {
|
||||
/// # httpcodes::HTTPOk.into()
|
||||
/// # httpcodes::HttpOk.into()
|
||||
/// # }
|
||||
/// #
|
||||
/// # fn main() {
|
||||
@ -180,9 +180,9 @@ impl TestServer {
|
||||
}
|
||||
|
||||
/// 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("/");
|
||||
self.system.run_until_complete(WsClient::new(url).connect())
|
||||
self.system.run_until_complete(ws::Client::new(url).connect())
|
||||
}
|
||||
|
||||
/// Create `GET` request
|
||||
@ -282,9 +282,9 @@ impl<S: 'static> Iterator for TestApp<S> {
|
||||
///
|
||||
/// fn index(req: HttpRequest) -> HttpResponse {
|
||||
/// if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) {
|
||||
/// httpcodes::HTTPOk.into()
|
||||
/// httpcodes::HttpOk.into()
|
||||
/// } else {
|
||||
/// httpcodes::HTTPBadRequest.into()
|
||||
/// httpcodes::HttpBadRequest.into()
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
@ -403,7 +403,7 @@ impl<S> TestRequest<S> {
|
||||
self.payload = Some(payload);
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
/// Complete request creation and generate `HttpRequest` instance
|
||||
pub fn finish(self) -> HttpRequest<S> {
|
||||
let TestRequest { state, method, uri, version, headers, params, cookies, payload } = self;
|
||||
|
175
src/ws/client.rs
175
src/ws/client.rs
@ -25,14 +25,32 @@ use client::{ClientRequest, ClientRequestBuilder, ClientResponse,
|
||||
ClientConnector, SendRequest, SendRequestError,
|
||||
HttpResponseParserError};
|
||||
|
||||
use super::{Message, WsError};
|
||||
use super::{Message, ProtocolError};
|
||||
use super::frame::Frame;
|
||||
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
|
||||
#[derive(Fail, Debug)]
|
||||
pub enum WsClientError {
|
||||
pub enum ClientError {
|
||||
#[fail(display="Invalid url")]
|
||||
InvalidUrl,
|
||||
#[fail(display="Invalid response status")]
|
||||
@ -56,46 +74,46 @@ pub enum WsClientError {
|
||||
#[fail(display="{}", _0)]
|
||||
SendRequest(SendRequestError),
|
||||
#[fail(display="{}", _0)]
|
||||
Protocol(#[cause] WsError),
|
||||
Protocol(#[cause] ProtocolError),
|
||||
#[fail(display="{}", _0)]
|
||||
Io(io::Error),
|
||||
#[fail(display="Disconnected")]
|
||||
Disconnected,
|
||||
}
|
||||
|
||||
impl From<HttpError> for WsClientError {
|
||||
fn from(err: HttpError) -> WsClientError {
|
||||
WsClientError::Http(err)
|
||||
impl From<HttpError> for ClientError {
|
||||
fn from(err: HttpError) -> ClientError {
|
||||
ClientError::Http(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UrlParseError> for WsClientError {
|
||||
fn from(err: UrlParseError) -> WsClientError {
|
||||
WsClientError::Url(err)
|
||||
impl From<UrlParseError> for ClientError {
|
||||
fn from(err: UrlParseError) -> ClientError {
|
||||
ClientError::Url(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SendRequestError> for WsClientError {
|
||||
fn from(err: SendRequestError) -> WsClientError {
|
||||
WsClientError::SendRequest(err)
|
||||
impl From<SendRequestError> for ClientError {
|
||||
fn from(err: SendRequestError) -> ClientError {
|
||||
ClientError::SendRequest(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WsError> for WsClientError {
|
||||
fn from(err: WsError) -> WsClientError {
|
||||
WsClientError::Protocol(err)
|
||||
impl From<ProtocolError> for ClientError {
|
||||
fn from(err: ProtocolError) -> ClientError {
|
||||
ClientError::Protocol(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for WsClientError {
|
||||
fn from(err: io::Error) -> WsClientError {
|
||||
WsClientError::Io(err)
|
||||
impl From<io::Error> for ClientError {
|
||||
fn from(err: io::Error) -> ClientError {
|
||||
ClientError::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HttpResponseParserError> for WsClientError {
|
||||
fn from(err: HttpResponseParserError) -> WsClientError {
|
||||
WsClientError::ResponseParseError(err)
|
||||
impl From<HttpResponseParserError> for ClientError {
|
||||
fn from(err: HttpResponseParserError) -> ClientError {
|
||||
ClientError::ResponseParseError(err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,9 +122,9 @@ impl From<HttpResponseParserError> for WsClientError {
|
||||
/// Example of `WebSocket` client usage is available in
|
||||
/// [websocket example](
|
||||
/// https://github.com/actix/actix-web/blob/master/examples/websocket/src/client.rs#L24)
|
||||
pub struct WsClient {
|
||||
pub struct Client {
|
||||
request: ClientRequestBuilder,
|
||||
err: Option<WsClientError>,
|
||||
err: Option<ClientError>,
|
||||
http_err: Option<HttpError>,
|
||||
origin: Option<HeaderValue>,
|
||||
protocols: Option<String>,
|
||||
@ -114,16 +132,16 @@ pub struct WsClient {
|
||||
max_size: usize,
|
||||
}
|
||||
|
||||
impl WsClient {
|
||||
impl Client {
|
||||
|
||||
/// Create new websocket connection
|
||||
pub fn new<S: AsRef<str>>(uri: S) -> WsClient {
|
||||
WsClient::with_connector(uri, ClientConnector::from_registry())
|
||||
pub fn new<S: AsRef<str>>(uri: S) -> Client {
|
||||
Client::with_connector(uri, ClientConnector::from_registry())
|
||||
}
|
||||
|
||||
/// Create new websocket connection with custom `ClientConnector`
|
||||
pub fn with_connector<S: AsRef<str>>(uri: S, conn: Addr<Unsync, ClientConnector>) -> WsClient {
|
||||
let mut cl = WsClient {
|
||||
pub fn with_connector<S: AsRef<str>>(uri: S, conn: Addr<Unsync, ClientConnector>) -> Client {
|
||||
let mut cl = Client {
|
||||
request: ClientRequest::build(),
|
||||
err: None,
|
||||
http_err: None,
|
||||
@ -182,12 +200,12 @@ impl WsClient {
|
||||
}
|
||||
|
||||
/// 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() {
|
||||
WsClientHandshake::error(e)
|
||||
ClientHandshake::error(e)
|
||||
}
|
||||
else if let Some(e) = self.http_err.take() {
|
||||
WsClientHandshake::error(e.into())
|
||||
ClientHandshake::error(e.into())
|
||||
} else {
|
||||
// origin
|
||||
if let Some(origin) = self.origin.take() {
|
||||
@ -197,50 +215,50 @@ impl WsClient {
|
||||
self.request.upgrade();
|
||||
self.request.set_header(header::UPGRADE, "websocket");
|
||||
self.request.set_header(header::CONNECTION, "upgrade");
|
||||
self.request.set_header("SEC-WEBSOCKET-VERSION", "13");
|
||||
self.request.set_header(header::SEC_WEBSOCKET_VERSION, "13");
|
||||
self.request.with_connector(self.conn.clone());
|
||||
|
||||
if let Some(protocols) = self.protocols.take() {
|
||||
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() {
|
||||
Ok(req) => req,
|
||||
Err(err) => return WsClientHandshake::error(err.into()),
|
||||
Err(err) => return ClientHandshake::error(err.into()),
|
||||
};
|
||||
|
||||
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 scheme != "http" && scheme != "https" && scheme != "ws" && scheme != "wss" {
|
||||
return WsClientHandshake::error(WsClientError::InvalidUrl)
|
||||
return ClientHandshake::error(ClientError::InvalidUrl)
|
||||
}
|
||||
} else {
|
||||
return WsClientHandshake::error(WsClientError::InvalidUrl)
|
||||
return ClientHandshake::error(ClientError::InvalidUrl)
|
||||
}
|
||||
|
||||
// start handshake
|
||||
WsClientHandshake::new(request, self.max_size)
|
||||
ClientHandshake::new(request, self.max_size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct WsInner {
|
||||
struct Inner {
|
||||
tx: UnboundedSender<Bytes>,
|
||||
rx: PayloadHelper<ClientResponse>,
|
||||
closed: bool,
|
||||
}
|
||||
|
||||
pub struct WsClientHandshake {
|
||||
pub struct ClientHandshake {
|
||||
request: Option<SendRequest>,
|
||||
tx: Option<UnboundedSender<Bytes>>,
|
||||
key: String,
|
||||
error: Option<WsClientError>,
|
||||
error: Option<ClientError>,
|
||||
max_size: usize,
|
||||
}
|
||||
|
||||
impl WsClientHandshake {
|
||||
fn new(mut request: ClientRequest, max_size: usize) -> WsClientHandshake
|
||||
impl ClientHandshake {
|
||||
fn new(mut request: ClientRequest, max_size: usize) -> ClientHandshake
|
||||
{
|
||||
// Generate a random key for the `Sec-WebSocket-Key` header.
|
||||
// a base64-encoded (see Section 4 of [RFC4648]) value that,
|
||||
@ -249,7 +267,7 @@ impl WsClientHandshake {
|
||||
let key = base64::encode(&sec_key);
|
||||
|
||||
request.headers_mut().insert(
|
||||
HeaderName::try_from("SEC-WEBSOCKET-KEY").unwrap(),
|
||||
header::SEC_WEBSOCKET_KEY,
|
||||
HeaderValue::try_from(key.as_str()).unwrap());
|
||||
|
||||
let (tx, rx) = unbounded();
|
||||
@ -257,7 +275,7 @@ impl WsClientHandshake {
|
||||
Box::new(rx.map_err(|_| io::Error::new(
|
||||
io::ErrorKind::Other, "disconnected").into()))));
|
||||
|
||||
WsClientHandshake {
|
||||
ClientHandshake {
|
||||
key,
|
||||
max_size,
|
||||
request: Some(request.send()),
|
||||
@ -266,8 +284,8 @@ impl WsClientHandshake {
|
||||
}
|
||||
}
|
||||
|
||||
fn error(err: WsClientError) -> WsClientHandshake {
|
||||
WsClientHandshake {
|
||||
fn error(err: ClientError) -> ClientHandshake {
|
||||
ClientHandshake {
|
||||
key: String::new(),
|
||||
request: None,
|
||||
tx: None,
|
||||
@ -277,9 +295,9 @@ impl WsClientHandshake {
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for WsClientHandshake {
|
||||
type Item = (WsClientReader, WsClientWriter);
|
||||
type Error = WsClientError;
|
||||
impl Future for ClientHandshake {
|
||||
type Item = (ClientReader, ClientWriter);
|
||||
type Error = ClientError;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
if let Some(err) = self.error.take() {
|
||||
@ -296,7 +314,7 @@ impl Future for WsClientHandshake {
|
||||
|
||||
// verify response
|
||||
if resp.status() != StatusCode::SWITCHING_PROTOCOLS {
|
||||
return Err(WsClientError::InvalidResponseStatus(resp.status()))
|
||||
return Err(ClientError::InvalidResponseStatus(resp.status()))
|
||||
}
|
||||
// Check for "UPGRADE" to websocket header
|
||||
let has_hdr = if let Some(hdr) = resp.headers().get(header::UPGRADE) {
|
||||
@ -310,26 +328,25 @@ impl Future for WsClientHandshake {
|
||||
};
|
||||
if !has_hdr {
|
||||
trace!("Invalid upgrade header");
|
||||
return Err(WsClientError::InvalidUpgradeHeader)
|
||||
return Err(ClientError::InvalidUpgradeHeader)
|
||||
}
|
||||
// Check for "CONNECTION" header
|
||||
if let Some(conn) = resp.headers().get(header::CONNECTION) {
|
||||
if let Ok(s) = conn.to_str() {
|
||||
if !s.to_lowercase().contains("upgrade") {
|
||||
trace!("Invalid connection header: {}", s);
|
||||
return Err(WsClientError::InvalidConnectionHeader(conn.clone()))
|
||||
return Err(ClientError::InvalidConnectionHeader(conn.clone()))
|
||||
}
|
||||
} else {
|
||||
trace!("Invalid connection header: {:?}", conn);
|
||||
return Err(WsClientError::InvalidConnectionHeader(conn.clone()))
|
||||
return Err(ClientError::InvalidConnectionHeader(conn.clone()))
|
||||
}
|
||||
} else {
|
||||
trace!("Missing connection header");
|
||||
return Err(WsClientError::MissingConnectionHeader)
|
||||
return Err(ClientError::MissingConnectionHeader)
|
||||
}
|
||||
|
||||
if let Some(key) = resp.headers().get(
|
||||
HeaderName::try_from("SEC-WEBSOCKET-ACCEPT").unwrap())
|
||||
if let Some(key) = resp.headers().get(header::SEC_WEBSOCKET_ACCEPT)
|
||||
{
|
||||
// field is constructed by concatenating /key/
|
||||
// with the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" (RFC 6455)
|
||||
@ -342,14 +359,14 @@ impl Future for WsClientHandshake {
|
||||
trace!(
|
||||
"Invalid challenge response: expected: {} received: {:?}",
|
||||
encoded, key);
|
||||
return Err(WsClientError::InvalidChallengeResponse(encoded, key.clone()));
|
||||
return Err(ClientError::InvalidChallengeResponse(encoded, key.clone()));
|
||||
}
|
||||
} else {
|
||||
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(),
|
||||
rx: PayloadHelper::new(resp),
|
||||
closed: false,
|
||||
@ -357,33 +374,33 @@ impl Future for WsClientHandshake {
|
||||
|
||||
let inner = Rc::new(UnsafeCell::new(inner));
|
||||
Ok(Async::Ready(
|
||||
(WsClientReader{inner: Rc::clone(&inner), max_size: self.max_size},
|
||||
WsClientWriter{inner})))
|
||||
(ClientReader{inner: Rc::clone(&inner), max_size: self.max_size},
|
||||
ClientWriter{inner})))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct WsClientReader {
|
||||
inner: Rc<UnsafeCell<WsInner>>,
|
||||
pub struct ClientReader {
|
||||
inner: Rc<UnsafeCell<Inner>>,
|
||||
max_size: usize,
|
||||
}
|
||||
|
||||
impl fmt::Debug for WsClientReader {
|
||||
impl fmt::Debug for ClientReader {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "WsClientReader()")
|
||||
write!(f, "ws::ClientReader()")
|
||||
}
|
||||
}
|
||||
|
||||
impl WsClientReader {
|
||||
impl ClientReader {
|
||||
#[inline]
|
||||
fn as_mut(&mut self) -> &mut WsInner {
|
||||
fn as_mut(&mut self) -> &mut Inner {
|
||||
unsafe{ &mut *self.inner.get() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for WsClientReader {
|
||||
impl Stream for ClientReader {
|
||||
type Item = Message;
|
||||
type Error = WsError;
|
||||
type Error = ProtocolError;
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
let max_size = self.max_size;
|
||||
@ -400,14 +417,14 @@ impl Stream for WsClientReader {
|
||||
// continuation is not supported
|
||||
if !finished {
|
||||
inner.closed = true;
|
||||
return Err(WsError::NoContinuation)
|
||||
return Err(ProtocolError::NoContinuation)
|
||||
}
|
||||
|
||||
match opcode {
|
||||
OpCode::Continue => unimplemented!(),
|
||||
OpCode::Bad => {
|
||||
inner.closed = true;
|
||||
Err(WsError::BadOpCode)
|
||||
Err(ProtocolError::BadOpCode)
|
||||
},
|
||||
OpCode::Close => {
|
||||
inner.closed = true;
|
||||
@ -431,7 +448,7 @@ impl Stream for WsClientReader {
|
||||
Ok(Async::Ready(Some(Message::Text(s)))),
|
||||
Err(_) => {
|
||||
inner.closed = true;
|
||||
Err(WsError::BadEncoding)
|
||||
Err(ProtocolError::BadEncoding)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -447,18 +464,18 @@ impl Stream for WsClientReader {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WsClientWriter {
|
||||
inner: Rc<UnsafeCell<WsInner>>
|
||||
pub struct ClientWriter {
|
||||
inner: Rc<UnsafeCell<Inner>>
|
||||
}
|
||||
|
||||
impl WsClientWriter {
|
||||
impl ClientWriter {
|
||||
#[inline]
|
||||
fn as_mut(&mut self) -> &mut WsInner {
|
||||
fn as_mut(&mut self) -> &mut Inner {
|
||||
unsafe{ &mut *self.inner.get() }
|
||||
}
|
||||
}
|
||||
|
||||
impl WsClientWriter {
|
||||
impl ClientWriter {
|
||||
|
||||
/// Write payload
|
||||
#[inline]
|
||||
@ -472,7 +489,7 @@ impl WsClientWriter {
|
||||
|
||||
/// Send text frame
|
||||
#[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));
|
||||
}
|
||||
|
||||
|
@ -132,7 +132,7 @@ impl<A, S> WebsocketContext<A, S> where A: Actor<Context=Self> {
|
||||
|
||||
/// Send text frame
|
||||
#[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));
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ use body::Binary;
|
||||
use error::{PayloadError};
|
||||
use payload::PayloadHelper;
|
||||
|
||||
use ws::WsError;
|
||||
use ws::ProtocolError;
|
||||
use ws::proto::{OpCode, CloseCode};
|
||||
use ws::mask::apply_mask;
|
||||
|
||||
@ -53,7 +53,7 @@ impl Frame {
|
||||
|
||||
/// Parse the input stream into a frame.
|
||||
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>
|
||||
{
|
||||
let mut idx = 2;
|
||||
@ -69,9 +69,9 @@ impl Frame {
|
||||
// check masking
|
||||
let masked = second & 0x80 != 0;
|
||||
if !masked && server {
|
||||
return Err(WsError::UnmaskedFrame)
|
||||
return Err(ProtocolError::UnmaskedFrame)
|
||||
} else if masked && !server {
|
||||
return Err(WsError::MaskedFrame)
|
||||
return Err(ProtocolError::MaskedFrame)
|
||||
}
|
||||
|
||||
let rsv1 = first & 0x40 != 0;
|
||||
@ -104,7 +104,7 @@ impl Frame {
|
||||
|
||||
// check for max allowed size
|
||||
if length > max_size {
|
||||
return Err(WsError::Overflow)
|
||||
return Err(ProtocolError::Overflow)
|
||||
}
|
||||
|
||||
let mask = if server {
|
||||
@ -133,13 +133,13 @@ impl Frame {
|
||||
|
||||
// Disallow 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
|
||||
match opcode {
|
||||
OpCode::Ping | OpCode::Pong if length > 125 => {
|
||||
return Err(WsError::InvalidLength(length))
|
||||
return Err(ProtocolError::InvalidLength(length))
|
||||
}
|
||||
OpCode::Close if length > 125 => {
|
||||
debug!("Received close frame with payload length exceeding 125. Morphing to protocol close frame.");
|
||||
@ -257,14 +257,14 @@ mod tests {
|
||||
use super::*;
|
||||
use futures::stream::once;
|
||||
|
||||
fn is_none(frm: Poll<Option<Frame>, WsError>) -> bool {
|
||||
fn is_none(frm: Poll<Option<Frame>, ProtocolError>) -> bool {
|
||||
match frm {
|
||||
Ok(Async::Ready(None)) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn extract(frm: Poll<Option<Frame>, WsError>) -> Frame {
|
||||
fn extract(frm: Poll<Option<Frame>, ProtocolError>) -> Frame {
|
||||
match frm {
|
||||
Ok(Async::Ready(Some(frame))) => frame,
|
||||
_ => panic!("error"),
|
||||
@ -370,7 +370,7 @@ mod tests {
|
||||
|
||||
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 {
|
||||
panic!("error");
|
||||
}
|
||||
|
124
src/ws/mod.rs
124
src/ws/mod.rs
@ -55,7 +55,7 @@ use error::{Error, PayloadError, ResponseError};
|
||||
use httpmessage::HttpMessage;
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::{ConnectionType, HttpResponse, HttpResponseBuilder};
|
||||
use httpcodes::{HTTPBadRequest, HTTPMethodNotAllowed};
|
||||
use httpcodes::{HttpBadRequest, HttpMethodNotAllowed};
|
||||
|
||||
mod frame;
|
||||
mod proto;
|
||||
@ -67,18 +67,24 @@ use self::frame::Frame;
|
||||
use self::proto::{hash_key, OpCode};
|
||||
pub use self::proto::CloseCode;
|
||||
pub use self::context::WebsocketContext;
|
||||
pub use self::client::{Client, ClientError,
|
||||
ClientReader, ClientWriter, ClientHandshake};
|
||||
|
||||
#[allow(deprecated)]
|
||||
pub use self::client::{WsClient, WsClientError,
|
||||
WsClientReader, WsClientWriter, WsClientHandshake};
|
||||
|
||||
const SEC_WEBSOCKET_ACCEPT: &str = "SEC-WEBSOCKET-ACCEPT";
|
||||
const SEC_WEBSOCKET_KEY: &str = "SEC-WEBSOCKET-KEY";
|
||||
const SEC_WEBSOCKET_VERSION: &str = "SEC-WEBSOCKET-VERSION";
|
||||
// const SEC_WEBSOCKET_PROTOCOL: &'static str = "SEC-WEBSOCKET-PROTOCOL";
|
||||
|
||||
/// Backward compatibility
|
||||
#[doc(hidden)]
|
||||
#[deprecated(since="0.4.2", note="please use `ws::ProtocolError` instead")]
|
||||
pub type WsError = ProtocolError;
|
||||
#[doc(hidden)]
|
||||
#[deprecated(since="0.4.2", note="please use `ws::HandshakeError` instead")]
|
||||
pub type WsHandshakeError = HandshakeError;
|
||||
|
||||
/// Websocket errors
|
||||
#[derive(Fail, Debug)]
|
||||
pub enum WsError {
|
||||
pub enum ProtocolError {
|
||||
/// Received an unmasked frame from client
|
||||
#[fail(display="Received an unmasked frame from client")]
|
||||
UnmaskedFrame,
|
||||
@ -108,17 +114,17 @@ pub enum WsError {
|
||||
Payload(#[cause] PayloadError),
|
||||
}
|
||||
|
||||
impl ResponseError for WsError {}
|
||||
impl ResponseError for ProtocolError {}
|
||||
|
||||
impl From<PayloadError> for WsError {
|
||||
fn from(err: PayloadError) -> WsError {
|
||||
WsError::Payload(err)
|
||||
impl From<PayloadError> for ProtocolError {
|
||||
fn from(err: PayloadError) -> ProtocolError {
|
||||
ProtocolError::Payload(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// Websocket handshake errors
|
||||
#[derive(Fail, PartialEq, Debug)]
|
||||
pub enum WsHandshakeError {
|
||||
pub enum HandshakeError {
|
||||
/// Only get method is allowed
|
||||
#[fail(display="Method not allowed")]
|
||||
GetMethodRequired,
|
||||
@ -139,27 +145,27 @@ pub enum WsHandshakeError {
|
||||
BadWebsocketKey,
|
||||
}
|
||||
|
||||
impl ResponseError for WsHandshakeError {
|
||||
impl ResponseError for HandshakeError {
|
||||
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
match *self {
|
||||
WsHandshakeError::GetMethodRequired => {
|
||||
HTTPMethodNotAllowed
|
||||
HandshakeError::GetMethodRequired => {
|
||||
HttpMethodNotAllowed
|
||||
.build()
|
||||
.header(header::ALLOW, "GET")
|
||||
.finish()
|
||||
.unwrap()
|
||||
}
|
||||
WsHandshakeError::NoWebsocketUpgrade =>
|
||||
HTTPBadRequest.with_reason("No WebSocket UPGRADE header found"),
|
||||
WsHandshakeError::NoConnectionUpgrade =>
|
||||
HTTPBadRequest.with_reason("No CONNECTION upgrade"),
|
||||
WsHandshakeError::NoVersionHeader =>
|
||||
HTTPBadRequest.with_reason("Websocket version header is required"),
|
||||
WsHandshakeError::UnsupportedVersion =>
|
||||
HTTPBadRequest.with_reason("Unsupported version"),
|
||||
WsHandshakeError::BadWebsocketKey =>
|
||||
HTTPBadRequest.with_reason("Handshake error"),
|
||||
HandshakeError::NoWebsocketUpgrade =>
|
||||
HttpBadRequest.with_reason("No WebSocket UPGRADE header found"),
|
||||
HandshakeError::NoConnectionUpgrade =>
|
||||
HttpBadRequest.with_reason("No CONNECTION upgrade"),
|
||||
HandshakeError::NoVersionHeader =>
|
||||
HttpBadRequest.with_reason("Websocket version header is required"),
|
||||
HandshakeError::UnsupportedVersion =>
|
||||
HttpBadRequest.with_reason("Unsupported version"),
|
||||
HandshakeError::BadWebsocketKey =>
|
||||
HttpBadRequest.with_reason("Handshake error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -176,7 +182,7 @@ pub enum Message {
|
||||
|
||||
/// Do websocket handshake and start actor
|
||||
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
|
||||
{
|
||||
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,
|
||||
// /// the returned response headers contain the first protocol in this list
|
||||
// /// 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
|
||||
if *req.method() != Method::GET {
|
||||
return Err(WsHandshakeError::GetMethodRequired)
|
||||
return Err(HandshakeError::GetMethodRequired)
|
||||
}
|
||||
|
||||
// Check for "UPGRADE" to websocket header
|
||||
@ -213,35 +219,35 @@ pub fn handshake<S>(req: &HttpRequest<S>) -> Result<HttpResponseBuilder, WsHands
|
||||
false
|
||||
};
|
||||
if !has_hdr {
|
||||
return Err(WsHandshakeError::NoWebsocketUpgrade)
|
||||
return Err(HandshakeError::NoWebsocketUpgrade)
|
||||
}
|
||||
|
||||
// Upgrade connection
|
||||
if !req.upgrade() {
|
||||
return Err(WsHandshakeError::NoConnectionUpgrade)
|
||||
return Err(HandshakeError::NoConnectionUpgrade)
|
||||
}
|
||||
|
||||
// check supported version
|
||||
if !req.headers().contains_key(SEC_WEBSOCKET_VERSION) {
|
||||
return Err(WsHandshakeError::NoVersionHeader)
|
||||
if !req.headers().contains_key(header::SEC_WEBSOCKET_VERSION) {
|
||||
return Err(HandshakeError::NoVersionHeader)
|
||||
}
|
||||
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"
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
if !supported_ver {
|
||||
return Err(WsHandshakeError::UnsupportedVersion)
|
||||
return Err(HandshakeError::UnsupportedVersion)
|
||||
}
|
||||
|
||||
// check client handshake for validity
|
||||
if !req.headers().contains_key(SEC_WEBSOCKET_KEY) {
|
||||
return Err(WsHandshakeError::BadWebsocketKey)
|
||||
if !req.headers().contains_key(header::SEC_WEBSOCKET_KEY) {
|
||||
return Err(HandshakeError::BadWebsocketKey)
|
||||
}
|
||||
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())
|
||||
};
|
||||
|
||||
@ -249,7 +255,7 @@ pub fn handshake<S>(req: &HttpRequest<S>) -> Result<HttpResponseBuilder, WsHands
|
||||
.connection_type(ConnectionType::Upgrade)
|
||||
.header(header::UPGRADE, "websocket")
|
||||
.header(header::TRANSFER_ENCODING, "chunked")
|
||||
.header(SEC_WEBSOCKET_ACCEPT, key.as_str())
|
||||
.header(header::SEC_WEBSOCKET_ACCEPT, key.as_str())
|
||||
.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> {
|
||||
type Item = Message;
|
||||
type Error = WsError;
|
||||
type Error = ProtocolError;
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
if self.closed {
|
||||
@ -294,14 +300,14 @@ impl<S> Stream for WsStream<S> where S: Stream<Item=Bytes, Error=PayloadError> {
|
||||
// continuation is not supported
|
||||
if !finished {
|
||||
self.closed = true;
|
||||
return Err(WsError::NoContinuation)
|
||||
return Err(ProtocolError::NoContinuation)
|
||||
}
|
||||
|
||||
match opcode {
|
||||
OpCode::Continue => unimplemented!(),
|
||||
OpCode::Bad => {
|
||||
self.closed = true;
|
||||
Err(WsError::BadOpCode)
|
||||
Err(ProtocolError::BadOpCode)
|
||||
}
|
||||
OpCode::Close => {
|
||||
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)))),
|
||||
Err(_) => {
|
||||
self.closed = true;
|
||||
Err(WsError::BadEncoding)
|
||||
Err(ProtocolError::BadEncoding)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -351,25 +357,25 @@ mod tests {
|
||||
fn test_handshake() {
|
||||
let req = HttpRequest::new(Method::POST, Uri::from_str("/").unwrap(),
|
||||
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(),
|
||||
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();
|
||||
headers.insert(header::UPGRADE,
|
||||
header::HeaderValue::from_static("test"));
|
||||
let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(),
|
||||
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();
|
||||
headers.insert(header::UPGRADE,
|
||||
header::HeaderValue::from_static("websocket"));
|
||||
let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(),
|
||||
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();
|
||||
headers.insert(header::UPGRADE,
|
||||
@ -378,38 +384,38 @@ mod tests {
|
||||
header::HeaderValue::from_static("upgrade"));
|
||||
let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(),
|
||||
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();
|
||||
headers.insert(header::UPGRADE,
|
||||
header::HeaderValue::from_static("websocket"));
|
||||
headers.insert(header::CONNECTION,
|
||||
header::HeaderValue::from_static("upgrade"));
|
||||
headers.insert(SEC_WEBSOCKET_VERSION,
|
||||
headers.insert(header::SEC_WEBSOCKET_VERSION,
|
||||
header::HeaderValue::from_static("5"));
|
||||
let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(),
|
||||
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();
|
||||
headers.insert(header::UPGRADE,
|
||||
header::HeaderValue::from_static("websocket"));
|
||||
headers.insert(header::CONNECTION,
|
||||
header::HeaderValue::from_static("upgrade"));
|
||||
headers.insert(SEC_WEBSOCKET_VERSION,
|
||||
headers.insert(header::SEC_WEBSOCKET_VERSION,
|
||||
header::HeaderValue::from_static("13"));
|
||||
let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(),
|
||||
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();
|
||||
headers.insert(header::UPGRADE,
|
||||
header::HeaderValue::from_static("websocket"));
|
||||
headers.insert(header::CONNECTION,
|
||||
header::HeaderValue::from_static("upgrade"));
|
||||
headers.insert(SEC_WEBSOCKET_VERSION,
|
||||
headers.insert(header::SEC_WEBSOCKET_VERSION,
|
||||
header::HeaderValue::from_static("13"));
|
||||
headers.insert(SEC_WEBSOCKET_KEY,
|
||||
headers.insert(header::SEC_WEBSOCKET_KEY,
|
||||
header::HeaderValue::from_static("13"));
|
||||
let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(),
|
||||
Version::HTTP_11, headers, None);
|
||||
@ -419,17 +425,17 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
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);
|
||||
let resp: HttpResponse = WsHandshakeError::NoWebsocketUpgrade.error_response();
|
||||
let resp: HttpResponse = HandshakeError::NoWebsocketUpgrade.error_response();
|
||||
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);
|
||||
let resp: HttpResponse = WsHandshakeError::NoVersionHeader.error_response();
|
||||
let resp: HttpResponse = HandshakeError::NoVersionHeader.error_response();
|
||||
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);
|
||||
let resp: HttpResponse = WsHandshakeError::BadWebsocketKey.error_response();
|
||||
let resp: HttpResponse = HandshakeError::BadWebsocketKey.error_response();
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
@ -116,6 +116,32 @@ fn test_client_gzip_encoding() {
|
||||
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_client_gzip_encoding_large() {
|
||||
let data = STR.repeat(10);
|
||||
|
||||
let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| {
|
||||
req.body()
|
||||
.and_then(|bytes: Bytes| {
|
||||
Ok(httpcodes::HTTPOk
|
||||
.build()
|
||||
.content_encoding(headers::ContentEncoding::Deflate)
|
||||
.body(bytes))
|
||||
}).responder()}
|
||||
));
|
||||
|
||||
// client request
|
||||
let request = srv.post()
|
||||
.content_encoding(headers::ContentEncoding::Gzip)
|
||||
.body(data.clone()).unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
// read response
|
||||
let bytes = srv.execute(response.body()).unwrap();
|
||||
assert_eq!(bytes, Bytes::from(data));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_client_brotli_encoding() {
|
||||
let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| {
|
||||
|
@ -94,6 +94,7 @@ fn test_start() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_shutdown() {
|
||||
let _ = test::TestServer::unused_addr();
|
||||
let (tx, rx) = mpsc::channel();
|
||||
@ -121,7 +122,7 @@ fn test_shutdown() {
|
||||
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());
|
||||
}
|
||||
|
||||
@ -135,27 +136,32 @@ fn test_simple() {
|
||||
|
||||
#[test]
|
||||
fn test_headers() {
|
||||
let data = STR.repeat(10);
|
||||
let srv_data = Arc::new(data.clone());
|
||||
let mut srv = test::TestServer::new(
|
||||
|app| app.handler(|_| {
|
||||
let mut builder = httpcodes::HTTPOk.build();
|
||||
for idx in 0..90 {
|
||||
builder.header(
|
||||
format!("X-TEST-{}", idx).as_str(),
|
||||
"TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ");
|
||||
}
|
||||
builder.body(STR)}));
|
||||
move |app| {
|
||||
let data = srv_data.clone();
|
||||
app.handler(move |_| {
|
||||
let mut builder = httpcodes::HTTPOk.build();
|
||||
for idx in 0..90 {
|
||||
builder.header(
|
||||
format!("X-TEST-{}", idx).as_str(),
|
||||
"TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ");
|
||||
}
|
||||
builder.body(data.as_ref())})
|
||||
});
|
||||
|
||||
let request = srv.get().finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
@ -163,7 +169,7 @@ fn test_headers() {
|
||||
|
||||
// read response
|
||||
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]
|
||||
@ -202,6 +208,33 @@ fn test_body_gzip() {
|
||||
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]
|
||||
fn test_body_chunked_implicit() {
|
||||
let mut srv = test::TestServer::new(
|
||||
@ -429,6 +462,35 @@ fn test_gzip_encoding() {
|
||||
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]
|
||||
fn test_deflate_encoding() {
|
||||
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()));
|
||||
}
|
||||
|
||||
#[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]
|
||||
fn test_brotli_encoding() {
|
||||
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()));
|
||||
}
|
||||
|
||||
#[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]
|
||||
fn test_h2() {
|
||||
let srv = test::TestServer::new(|app| app.handler(|_|{
|
||||
|
@ -16,7 +16,7 @@ impl Actor for Ws {
|
||||
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) {
|
||||
match msg {
|
||||
|
@ -19,7 +19,7 @@ use futures::Future;
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
use actix::prelude::*;
|
||||
use actix_web::ws::{Message, WsClientError, WsClient, WsClientWriter};
|
||||
use actix_web::ws;
|
||||
|
||||
|
||||
fn main() {
|
||||
@ -71,21 +71,21 @@ fn main() {
|
||||
let perf = perf_counters.clone();
|
||||
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;
|
||||
for _ in 0..concurrency {
|
||||
let pl2 = pl.clone();
|
||||
let perf2 = perf.clone();
|
||||
|
||||
Arbiter::handle().spawn(
|
||||
WsClient::new(&ws).connect().unwrap()
|
||||
ws::Client::new(&ws).connect()
|
||||
.map_err(|e| {
|
||||
println!("Error: {}", e);
|
||||
Arbiter::system().send(actix::msgs::SystemExit(0));
|
||||
Arbiter::system().do_send(actix::msgs::SystemExit(0));
|
||||
()
|
||||
})
|
||||
.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{conn: writer,
|
||||
payload: pl2,
|
||||
@ -114,7 +114,7 @@ fn parse_u64_default(input: Option<&str>, default: u64) -> u64 {
|
||||
}
|
||||
|
||||
struct ChatClient{
|
||||
conn: WsClientWriter,
|
||||
conn: ws::ClientWriter,
|
||||
payload: Arc<String>,
|
||||
ts: u64,
|
||||
bin: bool,
|
||||
@ -133,9 +133,9 @@ impl Actor for ChatClient {
|
||||
}
|
||||
}
|
||||
|
||||
fn stopping(&mut self, _: &mut Context<Self>) -> bool {
|
||||
Arbiter::system().send(actix::msgs::SystemExit(0));
|
||||
true
|
||||
fn stopping(&mut self, _: &mut Context<Self>) -> Running {
|
||||
Arbiter::system().do_send(actix::msgs::SystemExit(0));
|
||||
Running::Stop
|
||||
}
|
||||
}
|
||||
|
||||
@ -171,15 +171,15 @@ impl ChatClient {
|
||||
}
|
||||
|
||||
/// 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>) {
|
||||
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 {
|
||||
Message::Text(txt) => {
|
||||
ws::Message::Text(txt) => {
|
||||
if txt == self.payload.as_ref().as_str() {
|
||||
self.perf_counters.register_request();
|
||||
self.perf_counters.register_latency(time::precise_time_ns() - self.ts);
|
||||
|
Reference in New Issue
Block a user