mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-03 09:36:36 +02:00
Compare commits
85 Commits
Author | SHA1 | Date | |
---|---|---|---|
ecda97aadd | |||
8cda362866 | |||
3c6c1268c9 | |||
72908d974c | |||
c755d71a8b | |||
a817ddb57b | |||
44c36e93d1 | |||
c92ebc22d7 | |||
599fd6af93 | |||
fa81d97004 | |||
c54f045b39 | |||
cd11293c1f | |||
45325a5f75 | |||
a7c40024ce | |||
0af4d01fe4 | |||
bd6e18b7fe | |||
f66cf16823 | |||
03d6b04eef | |||
f37880d89c | |||
8b43574bd5 | |||
b07d0e712f | |||
acd7380865 | |||
0208dfb6b2 | |||
bb61dd41af | |||
58079b5bbe | |||
3623383e83 | |||
7036656ae4 | |||
32a2866449 | |||
35a4078434 | |||
4ca5d8bcfc | |||
a38acb41e5 | |||
31e23d4ab1 | |||
1aadfee6f7 | |||
76b644365f | |||
80f385e703 | |||
a1958deaae | |||
8d65468c58 | |||
195246573e | |||
e01102bda2 | |||
9b6343d54b | |||
d9a4fadaae | |||
48e05a2d87 | |||
70d0c5c700 | |||
d43ca96c5c | |||
bfd46e6a71 | |||
25b245ac72 | |||
eefbe19651 | |||
ab4e889f96 | |||
91235ac816 | |||
9c1bda3eca | |||
4a29f12876 | |||
368730f5f1 | |||
aa757a5be8 | |||
03ded62337 | |||
c72d1381a6 | |||
d98d723f97 | |||
eb6e618812 | |||
de222fe33b | |||
de49796fd1 | |||
a38c3985f6 | |||
492c072564 | |||
fd876efa68 | |||
c5b9bed478 | |||
3eba383cdc | |||
927f2e594e | |||
fa9edf2180 | |||
5ca904d1db | |||
2e7d323e1a | |||
b66566f610 | |||
2477afcf30 | |||
bcd03a9c62 | |||
f8af3ef7f4 | |||
f8b75c157f | |||
b7b61afacc | |||
507361c1df | |||
f6fd9e70f9 | |||
de8a09254d | |||
f89b7a9bb8 | |||
59244b203c | |||
2adf8a3a48 | |||
805dbea8e7 | |||
dc9a24a189 | |||
5528cf62f0 | |||
9880a95603 | |||
2579c49865 |
@ -4,13 +4,13 @@ environment:
|
||||
matrix:
|
||||
# Stable channel
|
||||
- TARGET: i686-pc-windows-gnu
|
||||
CHANNEL: 1.21.0
|
||||
CHANNEL: 1.24.0
|
||||
- TARGET: i686-pc-windows-msvc
|
||||
CHANNEL: 1.21.0
|
||||
CHANNEL: 1.24.0
|
||||
- TARGET: x86_64-pc-windows-gnu
|
||||
CHANNEL: 1.21.0
|
||||
CHANNEL: 1.24.0
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
CHANNEL: 1.21.0
|
||||
CHANNEL: 1.24.0
|
||||
# Stable channel
|
||||
- TARGET: i686-pc-windows-gnu
|
||||
CHANNEL: stable
|
||||
@ -59,4 +59,4 @@ build: false
|
||||
|
||||
# Equivalent to Travis' `script` phase
|
||||
test_script:
|
||||
- cargo test --no-default-features
|
||||
- cargo test --no-default-features --features="flate2-rust"
|
||||
|
27
.travis.yml
27
.travis.yml
@ -8,19 +8,13 @@ cache:
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- rust: 1.21.0
|
||||
- rust: 1.24.0
|
||||
- rust: stable
|
||||
- rust: beta
|
||||
- rust: nightly
|
||||
allow_failures:
|
||||
- rust: nightly
|
||||
|
||||
#rust:
|
||||
# - 1.21.0
|
||||
# - stable
|
||||
# - beta
|
||||
# - nightly-2018-01-03
|
||||
|
||||
env:
|
||||
global:
|
||||
# - RUSTFLAGS="-C link-dead-code"
|
||||
@ -37,24 +31,25 @@ before_script:
|
||||
|
||||
script:
|
||||
- |
|
||||
if [[ "$TRAVIS_RUST_VERSION" != "beta" ]]; then
|
||||
cargo clean
|
||||
cargo test --features="alpn,tls" -- --nocapture
|
||||
fi
|
||||
- |
|
||||
if [[ "$TRAVIS_RUST_VERSION" == "beta" ]]; then
|
||||
bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh)
|
||||
USE_SKEPTIC=1 cargo tarpaulin --out Xml
|
||||
bash <(curl -s https://codecov.io/bash)
|
||||
echo "Uploaded code coverage"
|
||||
fi
|
||||
|
||||
# Upload docs
|
||||
after_success:
|
||||
- |
|
||||
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "beta" ]]; then
|
||||
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then
|
||||
cargo doc --features "alpn, tls, session" --no-deps &&
|
||||
echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html &&
|
||||
git clone https://github.com/davisp/ghp-import.git &&
|
||||
./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc &&
|
||||
echo "Uploaded documentation"
|
||||
fi
|
||||
|
||||
- |
|
||||
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "nightly" ]]; then
|
||||
bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh)
|
||||
USE_SKEPTIC=1 cargo tarpaulin --out Xml
|
||||
bash <(curl -s https://codecov.io/bash)
|
||||
echo "Uploaded code coverage"
|
||||
fi
|
||||
|
28
CHANGES.md
28
CHANGES.md
@ -1,5 +1,33 @@
|
||||
# Changes
|
||||
|
||||
## 0.6.0 (2018-05-08)
|
||||
|
||||
* Add route scopes #202
|
||||
|
||||
* Allow to use ssl and non-ssl connections at the same time #206
|
||||
|
||||
* Websocket CloseCode Empty/Status is ambiguous #193
|
||||
|
||||
* Add Content-Disposition to NamedFile #204
|
||||
|
||||
* Allow to access Error's backtrace object
|
||||
|
||||
* Allow to override files listing renderer for `StaticFiles` #203
|
||||
|
||||
* Various extractor usability improvements #207
|
||||
|
||||
|
||||
## 0.5.6 (2018-04-24)
|
||||
|
||||
* Make flate2 crate optional #200
|
||||
|
||||
|
||||
## 0.5.5 (2018-04-24)
|
||||
|
||||
* Fix panic when Websocket is closed with no error code #191
|
||||
|
||||
* Allow to use rust backend for flate2 crate #199
|
||||
|
||||
## 0.5.4 (2018-04-19)
|
||||
|
||||
* Add identity service middleware
|
||||
|
19
Cargo.toml
19
Cargo.toml
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-web"
|
||||
version = "0.5.4"
|
||||
version = "0.6.0"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
|
||||
readme = "README.md"
|
||||
@ -26,7 +26,7 @@ name = "actix_web"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = ["session", "brotli"]
|
||||
default = ["session", "brotli", "flate2-c"]
|
||||
|
||||
# tls
|
||||
tls = ["native-tls", "tokio-tls"]
|
||||
@ -34,19 +34,24 @@ tls = ["native-tls", "tokio-tls"]
|
||||
# openssl
|
||||
alpn = ["openssl", "tokio-openssl"]
|
||||
|
||||
# sessions
|
||||
# sessions feature, session require "ring" crate and c compiler
|
||||
session = ["cookie/secure"]
|
||||
|
||||
# brotli encoding
|
||||
# brotli encoding, requires c compiler
|
||||
brotli = ["brotli2"]
|
||||
|
||||
# miniz-sys backend for flate2 crate
|
||||
flate2-c = ["flate2/miniz-sys"]
|
||||
|
||||
# rust backend for flate2 crate
|
||||
flate2-rust = ["flate2/rust_backend"]
|
||||
|
||||
[dependencies]
|
||||
actix = "^0.5.5"
|
||||
|
||||
base64 = "0.9"
|
||||
bitflags = "1.0"
|
||||
failure = "0.1.1"
|
||||
flate2 = "1.0"
|
||||
h2 = "0.1"
|
||||
http = "^0.1.5"
|
||||
httparse = "1.2"
|
||||
@ -58,7 +63,7 @@ mime_guess = "2.0.0-alpha"
|
||||
num_cpus = "1.0"
|
||||
percent-encoding = "1.0"
|
||||
rand = "0.4"
|
||||
regex = "0.2"
|
||||
regex = "1.0"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
serde_urlencoded = "0.5"
|
||||
@ -71,6 +76,7 @@ lazy_static = "1.0"
|
||||
url = { version="1.7", features=["query_encoding"] }
|
||||
cookie = { version="0.10", features=["percent-encode"] }
|
||||
brotli2 = { version="^0.3.2", optional = true }
|
||||
flate2 = { version="1.0", optional = true, default-features = false }
|
||||
|
||||
# io
|
||||
mio = "^0.6.13"
|
||||
@ -79,6 +85,7 @@ bytes = "0.4"
|
||||
byteorder = "1"
|
||||
futures = "0.1"
|
||||
futures-cpupool = "0.1"
|
||||
slab = "0.4"
|
||||
tokio-io = "0.1"
|
||||
tokio-core = "0.1"
|
||||
|
||||
|
@ -1,4 +1,40 @@
|
||||
# Migration from 0.4 to 0.5
|
||||
## Migration from 0.5 to 0.6
|
||||
|
||||
* `ws::Message::Close` now includes optional close reason.
|
||||
`ws::CloseCode::Status` and `ws::CloseCode::Empty` have been removed.
|
||||
|
||||
* `HttpServer::threads()` renamed to `HttpServer::workers()`.
|
||||
|
||||
* `HttpServer::start_ssl()` and `HttpServer::start_tls()` deprecated.
|
||||
Use `HttpServer::bind_ssl()` and `HttpServer::bind_tls()` instead.
|
||||
|
||||
* `HttpRequest::extensions()` returns read only reference to the request's Extension
|
||||
`HttpRequest::extensions_mut()` returns mutable reference.
|
||||
|
||||
* `FromRequest::from_request()` accepts mutable reference to a request
|
||||
|
||||
* `FromRequest::Result` has to implement `Into<Reply<Self>>`
|
||||
|
||||
* [`Responder::respond_to()`](
|
||||
https://actix.rs/actix-web/actix_web/trait.Responder.html#tymethod.respond_to)
|
||||
is generic over `S`
|
||||
|
||||
* `HttpRequest::query()` is deprecated. Use `Query` extractor.
|
||||
|
||||
```rust
|
||||
fn index(q: Query<HashMap<String, String>>) -> Result<..> {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```rust
|
||||
let q = Query::<HashMap<String, String>>::extract(req);
|
||||
```
|
||||
|
||||
|
||||
## Migration from 0.4 to 0.5
|
||||
|
||||
* `HttpResponseBuilder::body()`, `.finish()`, `.json()`
|
||||
methods return `HttpResponse` instead of `Result<HttpResponse>`
|
14
README.md
14
README.md
@ -2,12 +2,12 @@
|
||||
|
||||
Actix web is a simple, pragmatic and extremely fast web framework for Rust.
|
||||
|
||||
* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/actix-web/guide/qs_13.html) protocols
|
||||
* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/book/actix-web/sec-12-http2.html) protocols
|
||||
* Streaming and pipelining
|
||||
* Keep-alive and slow requests handling
|
||||
* Client/server [WebSockets](https://actix.rs/actix-web/guide/qs_9.html) support
|
||||
* Client/server [WebSockets](https://actix.rs/book/actix-web/sec-11-websockets.html) support
|
||||
* Transparent content compression/decompression (br, gzip, deflate)
|
||||
* Configurable [request routing](https://actix.rs/actix-web/guide/qs_5.html)
|
||||
* Configurable [request routing](https://actix.rs/book/actix-web/sec-6-url-dispatch.html)
|
||||
* Graceful server shutdown
|
||||
* Multipart streams
|
||||
* Static assets
|
||||
@ -27,7 +27,7 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust.
|
||||
* [API Documentation (Releases)](https://docs.rs/actix-web/)
|
||||
* [Chat on gitter](https://gitter.im/actix/actix)
|
||||
* Cargo package: [actix-web](https://crates.io/crates/actix-web)
|
||||
* Minimum supported Rust version: 1.21 or later
|
||||
* Minimum supported Rust version: 1.24 or later
|
||||
|
||||
## Example
|
||||
|
||||
@ -54,9 +54,11 @@ fn main() {
|
||||
* [Stateful](https://github.com/actix/examples/tree/master/state/)
|
||||
* [Protobuf support](https://github.com/actix/examples/tree/master/protobuf/)
|
||||
* [Multipart streams](https://github.com/actix/examples/tree/master/multipart/)
|
||||
* [Simple websocket session](https://github.com/actix/examples/tree/master/websocket/)
|
||||
* [Tera templates](https://github.com/actix/examples/tree/master/template_tera/)
|
||||
* [Simple websocket](https://github.com/actix/examples/tree/master/websocket/)
|
||||
* [Tera](https://github.com/actix/examples/tree/master/template_tera/) /
|
||||
[Askama](https://github.com/actix/examples/tree/master/template_askama/) templates
|
||||
* [Diesel integration](https://github.com/actix/examples/tree/master/diesel/)
|
||||
* [r2d2](https://github.com/actix/examples/tree/master/r2d2/)
|
||||
* [SSL / HTTP/2.0](https://github.com/actix/examples/tree/master/tls/)
|
||||
* [Tcp/Websocket chat](https://github.com/actix/examples/tree/master/websocket-chat/)
|
||||
* [Json](https://github.com/actix/examples/tree/master/json/)
|
||||
|
@ -1,7 +1,5 @@
|
||||
max_width = 89
|
||||
reorder_imports = true
|
||||
reorder_imports_in_group = true
|
||||
reorder_imported_names = true
|
||||
wrap_comments = true
|
||||
fn_args_density = "Compressed"
|
||||
#use_small_heuristics = false
|
||||
|
@ -1,22 +1,20 @@
|
||||
use std::cell::UnsafeCell;
|
||||
use std::collections::HashMap;
|
||||
use std::mem;
|
||||
use std::rc::Rc;
|
||||
|
||||
use handler::Reply;
|
||||
use handler::{FromRequest, Handler, Responder, RouteHandler, WrapHandler};
|
||||
use handler::{AsyncResult, FromRequest, Handler, Responder, RouteHandler, WrapHandler};
|
||||
use header::ContentEncoding;
|
||||
use http::Method;
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
use middleware::Middleware;
|
||||
use pipeline::{HandlerType, Pipeline, PipelineHandler};
|
||||
use pred::Predicate;
|
||||
use resource::ResourceHandler;
|
||||
use router::{Resource, Router};
|
||||
use scope::Scope;
|
||||
use server::{HttpHandler, HttpHandlerTask, IntoHttpHandler, ServerSettings};
|
||||
|
||||
#[deprecated(since = "0.5.0", note = "please use `actix_web::App` instead")]
|
||||
pub type Application<S> = App<S>;
|
||||
|
||||
/// Application
|
||||
pub struct HttpApplication<S = ()> {
|
||||
state: Rc<S>,
|
||||
@ -32,7 +30,12 @@ pub(crate) struct Inner<S> {
|
||||
default: ResourceHandler<S>,
|
||||
encoding: ContentEncoding,
|
||||
resources: Vec<ResourceHandler<S>>,
|
||||
handlers: Vec<(String, Box<RouteHandler<S>>)>,
|
||||
handlers: Vec<PrefixHandlerType<S>>,
|
||||
}
|
||||
|
||||
enum PrefixHandlerType<S> {
|
||||
Handler(String, Box<RouteHandler<S>>),
|
||||
Scope(Resource, Box<RouteHandler<S>>, Vec<Box<Predicate<S>>>),
|
||||
}
|
||||
|
||||
impl<S: 'static> PipelineHandler<S> for Inner<S> {
|
||||
@ -40,12 +43,17 @@ impl<S: 'static> PipelineHandler<S> for Inner<S> {
|
||||
self.encoding
|
||||
}
|
||||
|
||||
fn handle(&mut self, req: HttpRequest<S>, htype: HandlerType) -> Reply {
|
||||
fn handle(
|
||||
&mut self, req: HttpRequest<S>, htype: HandlerType,
|
||||
) -> AsyncResult<HttpResponse> {
|
||||
match htype {
|
||||
HandlerType::Normal(idx) => {
|
||||
self.resources[idx].handle(req, Some(&mut self.default))
|
||||
}
|
||||
HandlerType::Handler(idx) => self.handlers[idx].1.handle(req),
|
||||
HandlerType::Handler(idx) => match self.handlers[idx] {
|
||||
PrefixHandlerType::Handler(_, ref mut hnd) => hnd.handle(req),
|
||||
PrefixHandlerType::Scope(_, ref mut hnd, _) => hnd.handle(req),
|
||||
},
|
||||
HandlerType::Default => self.default.handle(req, None),
|
||||
}
|
||||
}
|
||||
@ -63,25 +71,55 @@ impl<S: 'static> HttpApplication<S> {
|
||||
HandlerType::Normal(idx)
|
||||
} else {
|
||||
let inner = self.as_ref();
|
||||
for idx in 0..inner.handlers.len() {
|
||||
let &(ref prefix, _) = &inner.handlers[idx];
|
||||
let m = {
|
||||
let path = &req.path()[inner.prefix..];
|
||||
path.starts_with(prefix)
|
||||
&& (path.len() == prefix.len()
|
||||
|| path.split_at(prefix.len()).1.starts_with('/'))
|
||||
};
|
||||
let path: &'static str =
|
||||
unsafe { &*(&req.path()[inner.prefix..] as *const _) };
|
||||
let path_len = path.len();
|
||||
'outer: for idx in 0..inner.handlers.len() {
|
||||
match inner.handlers[idx] {
|
||||
PrefixHandlerType::Handler(ref prefix, _) => {
|
||||
let m = {
|
||||
path.starts_with(prefix)
|
||||
&& (path_len == prefix.len()
|
||||
|| path.split_at(prefix.len()).1.starts_with('/'))
|
||||
};
|
||||
|
||||
if m {
|
||||
let path: &'static str = unsafe {
|
||||
mem::transmute(&req.path()[inner.prefix + prefix.len()..])
|
||||
};
|
||||
if path.is_empty() {
|
||||
req.match_info_mut().add("tail", "");
|
||||
} else {
|
||||
req.match_info_mut().add("tail", path.split_at(1).1);
|
||||
if m {
|
||||
let prefix_len = inner.prefix + prefix.len();
|
||||
let path: &'static str =
|
||||
unsafe { &*(&req.path()[prefix_len..] as *const _) };
|
||||
|
||||
req.set_prefix_len(prefix_len as u16);
|
||||
if path.is_empty() {
|
||||
req.match_info_mut().add("tail", "/");
|
||||
} else {
|
||||
req.match_info_mut().add("tail", path);
|
||||
}
|
||||
return HandlerType::Handler(idx);
|
||||
}
|
||||
}
|
||||
PrefixHandlerType::Scope(ref pattern, _, ref filters) => {
|
||||
if let Some(prefix_len) =
|
||||
pattern.match_prefix_with_params(path, req.match_info_mut())
|
||||
{
|
||||
for filter in filters {
|
||||
if !filter.check(req) {
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
|
||||
let prefix_len = inner.prefix + prefix_len - 1;
|
||||
let path: &'static str =
|
||||
unsafe { &*(&req.path()[prefix_len..] as *const _) };
|
||||
|
||||
req.set_prefix_len(prefix_len as u16);
|
||||
if path.is_empty() {
|
||||
req.match_info_mut().set("tail", "/");
|
||||
} else {
|
||||
req.match_info_mut().set("tail", path);
|
||||
}
|
||||
return HandlerType::Handler(idx);
|
||||
}
|
||||
}
|
||||
return HandlerType::Handler(idx);
|
||||
}
|
||||
}
|
||||
HandlerType::Default
|
||||
@ -89,7 +127,7 @@ impl<S: 'static> HttpApplication<S> {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn run(&mut self, mut req: HttpRequest<S>) -> Reply {
|
||||
pub(crate) fn run(&mut self, mut req: HttpRequest<S>) -> AsyncResult<HttpResponse> {
|
||||
let tp = self.get_handler(&mut req);
|
||||
unsafe { &mut *self.inner.get() }.handle(req, tp)
|
||||
}
|
||||
@ -130,7 +168,7 @@ struct ApplicationParts<S> {
|
||||
settings: ServerSettings,
|
||||
default: ResourceHandler<S>,
|
||||
resources: Vec<(Resource, Option<ResourceHandler<S>>)>,
|
||||
handlers: Vec<(String, Box<RouteHandler<S>>)>,
|
||||
handlers: Vec<PrefixHandlerType<S>>,
|
||||
external: HashMap<String, Resource>,
|
||||
encoding: ContentEncoding,
|
||||
middlewares: Vec<Box<Middleware<S>>>,
|
||||
@ -200,6 +238,12 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Get reference to the application state
|
||||
pub fn state(&self) -> &S {
|
||||
let parts = self.parts.as_ref().expect("Use after finish");
|
||||
&parts.state
|
||||
}
|
||||
|
||||
/// Set application prefix.
|
||||
///
|
||||
/// Only requests that match the application's prefix get
|
||||
@ -274,7 +318,7 @@ where
|
||||
{
|
||||
{
|
||||
let parts: &mut ApplicationParts<S> = unsafe {
|
||||
mem::transmute(self.parts.as_mut().expect("Use after finish"))
|
||||
&mut *(self.parts.as_mut().expect("Use after finish") as *mut _)
|
||||
};
|
||||
|
||||
// get resource handler
|
||||
@ -295,6 +339,56 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
/// Configure scope for common root path.
|
||||
///
|
||||
/// Scopes collect multiple paths under a common path prefix.
|
||||
/// Scope path can contain variable path segments as resources.
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::{http, App, HttpRequest, HttpResponse};
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new()
|
||||
/// .scope("/{project_id}", |scope| {
|
||||
/// scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
|
||||
/// .resource("/path2", |r| r.f(|_| HttpResponse::Ok()))
|
||||
/// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed()))
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// In the above example, three routes get added:
|
||||
/// * /{project_id}/path1
|
||||
/// * /{project_id}/path2
|
||||
/// * /{project_id}/path3
|
||||
///
|
||||
pub fn scope<F>(mut self, path: &str, f: F) -> App<S>
|
||||
where
|
||||
F: FnOnce(Scope<S>) -> Scope<S>,
|
||||
{
|
||||
{
|
||||
let mut scope = Box::new(f(Scope::new()));
|
||||
|
||||
let mut path = path.trim().trim_right_matches('/').to_owned();
|
||||
if !path.is_empty() && !path.starts_with('/') {
|
||||
path.insert(0, '/')
|
||||
}
|
||||
if !path.ends_with('/') {
|
||||
path.push('/');
|
||||
}
|
||||
let parts = self.parts.as_mut().expect("Use after finish");
|
||||
|
||||
let filters = scope.take_filters();
|
||||
parts.handlers.push(PrefixHandlerType::Scope(
|
||||
Resource::prefix("", &path),
|
||||
scope,
|
||||
filters,
|
||||
));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Configure resource for a specific path.
|
||||
///
|
||||
/// Resources may have variable path segments. For example, a
|
||||
@ -447,11 +541,15 @@ where
|
||||
if !path.is_empty() && !path.starts_with('/') {
|
||||
path.insert(0, '/')
|
||||
}
|
||||
if path.len() > 1 && path.ends_with('/') {
|
||||
path.pop();
|
||||
}
|
||||
let parts = self.parts.as_mut().expect("Use after finish");
|
||||
|
||||
parts
|
||||
.handlers
|
||||
.push((path, Box::new(WrapHandler::new(handler))));
|
||||
parts.handlers.push(PrefixHandlerType::Handler(
|
||||
path,
|
||||
Box::new(WrapHandler::new(handler)),
|
||||
));
|
||||
}
|
||||
self
|
||||
}
|
||||
@ -623,24 +721,18 @@ mod tests {
|
||||
|
||||
let req = TestRequest::with_uri("/test").finish();
|
||||
let resp = app.run(req);
|
||||
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
|
||||
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
||||
|
||||
let req = TestRequest::with_uri("/blah").finish();
|
||||
let resp = app.run(req);
|
||||
assert_eq!(
|
||||
resp.as_response().unwrap().status(),
|
||||
StatusCode::NOT_FOUND
|
||||
);
|
||||
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
||||
|
||||
let mut app = App::new()
|
||||
.default_resource(|r| r.f(|_| HttpResponse::MethodNotAllowed()))
|
||||
.finish();
|
||||
let req = TestRequest::with_uri("/blah").finish();
|
||||
let resp = app.run(req);
|
||||
assert_eq!(
|
||||
resp.as_response().unwrap().status(),
|
||||
StatusCode::METHOD_NOT_ALLOWED
|
||||
);
|
||||
assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -660,7 +752,7 @@ mod tests {
|
||||
let req =
|
||||
HttpRequest::default().with_state(Rc::clone(&app.state), app.router.clone());
|
||||
let resp = app.run(req);
|
||||
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
|
||||
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -694,29 +786,23 @@ mod tests {
|
||||
|
||||
let req = TestRequest::with_uri("/test").finish();
|
||||
let resp = app.run(req);
|
||||
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
|
||||
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
||||
|
||||
let req = TestRequest::with_uri("/test/").finish();
|
||||
let resp = app.run(req);
|
||||
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
|
||||
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
||||
|
||||
let req = TestRequest::with_uri("/test/app").finish();
|
||||
let resp = app.run(req);
|
||||
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
|
||||
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
||||
|
||||
let req = TestRequest::with_uri("/testapp").finish();
|
||||
let resp = app.run(req);
|
||||
assert_eq!(
|
||||
resp.as_response().unwrap().status(),
|
||||
StatusCode::NOT_FOUND
|
||||
);
|
||||
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
||||
|
||||
let req = TestRequest::with_uri("/blah").finish();
|
||||
let resp = app.run(req);
|
||||
assert_eq!(
|
||||
resp.as_response().unwrap().status(),
|
||||
StatusCode::NOT_FOUND
|
||||
);
|
||||
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -727,29 +813,23 @@ mod tests {
|
||||
|
||||
let req = TestRequest::with_uri("/test").finish();
|
||||
let resp = app.run(req);
|
||||
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
|
||||
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
||||
|
||||
let req = TestRequest::with_uri("/test/").finish();
|
||||
let resp = app.run(req);
|
||||
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
|
||||
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
||||
|
||||
let req = TestRequest::with_uri("/test/app").finish();
|
||||
let resp = app.run(req);
|
||||
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
|
||||
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
||||
|
||||
let req = TestRequest::with_uri("/testapp").finish();
|
||||
let resp = app.run(req);
|
||||
assert_eq!(
|
||||
resp.as_response().unwrap().status(),
|
||||
StatusCode::NOT_FOUND
|
||||
);
|
||||
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
||||
|
||||
let req = TestRequest::with_uri("/blah").finish();
|
||||
let resp = app.run(req);
|
||||
assert_eq!(
|
||||
resp.as_response().unwrap().status(),
|
||||
StatusCode::NOT_FOUND
|
||||
);
|
||||
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -761,29 +841,23 @@ mod tests {
|
||||
|
||||
let req = TestRequest::with_uri("/prefix/test").finish();
|
||||
let resp = app.run(req);
|
||||
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
|
||||
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
||||
|
||||
let req = TestRequest::with_uri("/prefix/test/").finish();
|
||||
let resp = app.run(req);
|
||||
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
|
||||
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
||||
|
||||
let req = TestRequest::with_uri("/prefix/test/app").finish();
|
||||
let resp = app.run(req);
|
||||
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
|
||||
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
||||
|
||||
let req = TestRequest::with_uri("/prefix/testapp").finish();
|
||||
let resp = app.run(req);
|
||||
assert_eq!(
|
||||
resp.as_response().unwrap().status(),
|
||||
StatusCode::NOT_FOUND
|
||||
);
|
||||
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
||||
|
||||
let req = TestRequest::with_uri("/prefix/blah").finish();
|
||||
let resp = app.run(req);
|
||||
assert_eq!(
|
||||
resp.as_response().unwrap().status(),
|
||||
StatusCode::NOT_FOUND
|
||||
);
|
||||
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -801,25 +875,19 @@ mod tests {
|
||||
.method(Method::GET)
|
||||
.finish();
|
||||
let resp = app.run(req);
|
||||
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
|
||||
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
||||
|
||||
let req = TestRequest::with_uri("/test")
|
||||
.method(Method::POST)
|
||||
.finish();
|
||||
let resp = app.run(req);
|
||||
assert_eq!(
|
||||
resp.as_response().unwrap().status(),
|
||||
StatusCode::CREATED
|
||||
);
|
||||
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
|
||||
|
||||
let req = TestRequest::with_uri("/test")
|
||||
.method(Method::HEAD)
|
||||
.finish();
|
||||
let resp = app.run(req);
|
||||
assert_eq!(
|
||||
resp.as_response().unwrap().status(),
|
||||
StatusCode::NOT_FOUND
|
||||
);
|
||||
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -831,35 +899,27 @@ mod tests {
|
||||
|
||||
let req = TestRequest::with_uri("/test").finish();
|
||||
let resp = app.run(req);
|
||||
assert_eq!(
|
||||
resp.as_response().unwrap().status(),
|
||||
StatusCode::NOT_FOUND
|
||||
);
|
||||
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
||||
|
||||
let req = TestRequest::with_uri("/app/test").finish();
|
||||
let resp = app.run(req);
|
||||
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
|
||||
let resp = app.run(req.clone());
|
||||
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
||||
assert_eq!(req.prefix_len(), 9);
|
||||
|
||||
let req = TestRequest::with_uri("/app/test/").finish();
|
||||
let resp = app.run(req);
|
||||
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
|
||||
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
||||
|
||||
let req = TestRequest::with_uri("/app/test/app").finish();
|
||||
let resp = app.run(req);
|
||||
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
|
||||
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
||||
|
||||
let req = TestRequest::with_uri("/app/testapp").finish();
|
||||
let resp = app.run(req);
|
||||
assert_eq!(
|
||||
resp.as_response().unwrap().status(),
|
||||
StatusCode::NOT_FOUND
|
||||
);
|
||||
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
||||
|
||||
let req = TestRequest::with_uri("/app/blah").finish();
|
||||
let resp = app.run(req);
|
||||
assert_eq!(
|
||||
resp.as_response().unwrap().status(),
|
||||
StatusCode::NOT_FOUND
|
||||
);
|
||||
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
@ -257,8 +257,8 @@ impl Responder for Binary {
|
||||
type Item = HttpResponse;
|
||||
type Error = Error;
|
||||
|
||||
fn respond_to(self, _: HttpRequest) -> Result<HttpResponse, Error> {
|
||||
Ok(HttpResponse::Ok()
|
||||
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
|
||||
Ok(HttpResponse::build_from(req)
|
||||
.content_type("application/octet-stream")
|
||||
.body(self))
|
||||
}
|
||||
|
@ -347,6 +347,7 @@ impl ClientConnector {
|
||||
/// Keep-alive period is the period between connection usage. If
|
||||
/// the delay between repeated usages of the same connection
|
||||
/// exceeds this period, the connection is closed.
|
||||
/// Default keep-alive period is 15 seconds.
|
||||
pub fn conn_keep_alive(mut self, dur: Duration) -> Self {
|
||||
self.conn_keep_alive = dur;
|
||||
self
|
||||
@ -356,6 +357,7 @@ impl ClientConnector {
|
||||
///
|
||||
/// Connection lifetime is max lifetime of any opened connection
|
||||
/// until it is closed regardless of keep-alive period.
|
||||
/// Default lifetime period is 75 seconds.
|
||||
pub fn conn_lifetime(mut self, dur: Duration) -> Self {
|
||||
self.conn_lifetime = dur;
|
||||
self
|
||||
@ -570,7 +572,7 @@ impl ClientConnector {
|
||||
}
|
||||
|
||||
fn wait_for(
|
||||
&mut self, key: Key, wait: Duration, conn_timeout: Duration
|
||||
&mut self, key: Key, wait: Duration, conn_timeout: Duration,
|
||||
) -> oneshot::Receiver<Result<Connection, ClientConnectorError>> {
|
||||
// connection is not available, wait
|
||||
let (tx, rx) = oneshot::channel();
|
||||
@ -810,7 +812,7 @@ impl fut::ActorFuture for Maintenance {
|
||||
type Actor = ClientConnector;
|
||||
|
||||
fn poll(
|
||||
&mut self, act: &mut ClientConnector, ctx: &mut Context<ClientConnector>
|
||||
&mut self, act: &mut ClientConnector, ctx: &mut Context<ClientConnector>,
|
||||
) -> Poll<Self::Item, Self::Error> {
|
||||
// check pause duration
|
||||
let done = if let Some(Some(ref pause)) = act.paused {
|
||||
@ -831,7 +833,7 @@ impl fut::ActorFuture for Maintenance {
|
||||
act.collect_waiters();
|
||||
|
||||
// check waiters
|
||||
let tmp: &mut ClientConnector = unsafe { mem::transmute(act as &mut _) };
|
||||
let tmp: &mut ClientConnector = unsafe { &mut *(act as *mut _) };
|
||||
|
||||
for (key, waiters) in &mut tmp.waiters {
|
||||
while let Some(waiter) = waiters.pop_front() {
|
||||
|
@ -7,18 +7,18 @@ use std::mem;
|
||||
|
||||
use error::{ParseError, PayloadError};
|
||||
|
||||
use server::h1::{chunked, Decoder};
|
||||
use server::h1decoder::EncodingDecoder;
|
||||
use server::{utils, IoStream};
|
||||
|
||||
use super::ClientResponse;
|
||||
use super::response::ClientMessage;
|
||||
use super::ClientResponse;
|
||||
|
||||
const MAX_BUFFER_SIZE: usize = 131_072;
|
||||
const MAX_HEADERS: usize = 96;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct HttpResponseParser {
|
||||
decoder: Option<Decoder>,
|
||||
decoder: Option<EncodingDecoder>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Fail)]
|
||||
@ -32,7 +32,7 @@ pub enum HttpResponseParserError {
|
||||
|
||||
impl HttpResponseParser {
|
||||
pub fn parse<T>(
|
||||
&mut self, io: &mut T, buf: &mut BytesMut
|
||||
&mut self, io: &mut T, buf: &mut BytesMut,
|
||||
) -> Poll<ClientResponse, HttpResponseParserError>
|
||||
where
|
||||
T: IoStream,
|
||||
@ -75,7 +75,7 @@ impl HttpResponseParser {
|
||||
}
|
||||
|
||||
pub fn parse_payload<T>(
|
||||
&mut self, io: &mut T, buf: &mut BytesMut
|
||||
&mut self, io: &mut T, buf: &mut BytesMut,
|
||||
) -> Poll<Option<Bytes>, PayloadError>
|
||||
where
|
||||
T: IoStream,
|
||||
@ -113,8 +113,8 @@ impl HttpResponseParser {
|
||||
}
|
||||
|
||||
fn parse_message(
|
||||
buf: &mut BytesMut
|
||||
) -> Poll<(ClientResponse, Option<Decoder>), ParseError> {
|
||||
buf: &mut BytesMut,
|
||||
) -> Poll<(ClientResponse, Option<EncodingDecoder>), ParseError> {
|
||||
// Parse http message
|
||||
let bytes_ptr = buf.as_ref().as_ptr() as usize;
|
||||
let mut headers: [httparse::Header; MAX_HEADERS] =
|
||||
@ -123,7 +123,7 @@ impl HttpResponseParser {
|
||||
let (len, version, status, headers_len) = {
|
||||
let b = unsafe {
|
||||
let b: &[u8] = buf;
|
||||
mem::transmute(b)
|
||||
&*(b as *const _)
|
||||
};
|
||||
let mut resp = httparse::Response::new(&mut headers);
|
||||
match resp.parse(b)? {
|
||||
@ -160,12 +160,12 @@ impl HttpResponseParser {
|
||||
}
|
||||
|
||||
let decoder = if status == StatusCode::SWITCHING_PROTOCOLS {
|
||||
Some(Decoder::eof())
|
||||
Some(EncodingDecoder::eof())
|
||||
} else if let Some(len) = hdrs.get(header::CONTENT_LENGTH) {
|
||||
// Content-Length
|
||||
if let Ok(s) = len.to_str() {
|
||||
if let Ok(len) = s.parse::<u64>() {
|
||||
Some(Decoder::length(len))
|
||||
Some(EncodingDecoder::length(len))
|
||||
} else {
|
||||
debug!("illegal Content-Length: {:?}", len);
|
||||
return Err(ParseError::Header);
|
||||
@ -176,7 +176,7 @@ impl HttpResponseParser {
|
||||
}
|
||||
} else if chunked(&hdrs)? {
|
||||
// Chunked encoding
|
||||
Some(Decoder::chunked())
|
||||
Some(EncodingDecoder::chunked())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@ -204,3 +204,16 @@ impl HttpResponseParser {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if request has chunked transfer encoding
|
||||
pub fn chunked(headers: &HeaderMap) -> Result<bool, ParseError> {
|
||||
if let Some(encodings) = headers.get(header::TRANSFER_ENCODING) {
|
||||
if let Ok(s) = encodings.to_str() {
|
||||
Ok(s.to_lowercase().contains("chunked"))
|
||||
} else {
|
||||
Err(ParseError::Header)
|
||||
}
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
@ -18,9 +18,9 @@ use error::Error;
|
||||
use error::PayloadError;
|
||||
use header::ContentEncoding;
|
||||
use httpmessage::HttpMessage;
|
||||
use server::WriterState;
|
||||
use server::encoding::PayloadStream;
|
||||
use server::shared::SharedBytes;
|
||||
use server::WriterState;
|
||||
|
||||
/// A set of errors that can occur during request sending and response reading
|
||||
#[derive(Fail, Debug)]
|
||||
@ -80,7 +80,7 @@ impl SendRequest {
|
||||
}
|
||||
|
||||
pub(crate) fn with_connector(
|
||||
req: ClientRequest, conn: Addr<Unsync, ClientConnector>
|
||||
req: ClientRequest, conn: Addr<Unsync, ClientConnector>,
|
||||
) -> SendRequest {
|
||||
SendRequest {
|
||||
req,
|
||||
@ -305,7 +305,7 @@ impl Pipeline {
|
||||
return Ok(Async::Ready(None));
|
||||
}
|
||||
let conn: &mut Connection =
|
||||
unsafe { mem::transmute(self.conn.as_mut().unwrap()) };
|
||||
unsafe { &mut *(self.conn.as_mut().unwrap() as *mut _) };
|
||||
|
||||
let mut need_run = false;
|
||||
|
||||
|
@ -682,7 +682,7 @@ impl ClientRequestBuilder {
|
||||
|
||||
#[inline]
|
||||
fn parts<'a>(
|
||||
parts: &'a mut Option<ClientRequest>, err: &Option<HttpError>
|
||||
parts: &'a mut Option<ClientRequest>, err: &Option<HttpError>,
|
||||
) -> Option<&'a mut ClientRequest> {
|
||||
if err.is_some() {
|
||||
return None;
|
||||
|
@ -7,8 +7,10 @@ use std::io::{self, Write};
|
||||
#[cfg(feature = "brotli")]
|
||||
use brotli2::write::BrotliEncoder;
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use flate2::Compression;
|
||||
#[cfg(feature = "flate2")]
|
||||
use flate2::write::{DeflateEncoder, GzEncoder};
|
||||
#[cfg(feature = "flate2")]
|
||||
use flate2::Compression;
|
||||
use futures::{Async, Poll};
|
||||
use http::header::{HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE,
|
||||
TRANSFER_ENCODING};
|
||||
@ -18,9 +20,9 @@ use tokio_io::AsyncWrite;
|
||||
|
||||
use body::{Binary, Body};
|
||||
use header::ContentEncoding;
|
||||
use server::WriterState;
|
||||
use server::encoding::{ContentEncoder, TransferEncoding};
|
||||
use server::shared::SharedBytes;
|
||||
use server::WriterState;
|
||||
|
||||
use client::ClientRequest;
|
||||
|
||||
@ -70,7 +72,7 @@ impl HttpClientWriter {
|
||||
// !self.flags.contains(Flags::UPGRADE) }
|
||||
|
||||
fn write_to_stream<T: AsyncWrite>(
|
||||
&mut self, stream: &mut T
|
||||
&mut self, stream: &mut T,
|
||||
) -> io::Result<WriterState> {
|
||||
while !self.buffer.is_empty() {
|
||||
match stream.write(self.buffer.as_ref()) {
|
||||
@ -191,7 +193,7 @@ impl HttpClientWriter {
|
||||
|
||||
#[inline]
|
||||
pub fn poll_completed<T: AsyncWrite>(
|
||||
&mut self, stream: &mut T, shutdown: bool
|
||||
&mut self, stream: &mut T, shutdown: bool,
|
||||
) -> Poll<(), io::Error> {
|
||||
match self.write_to_stream(stream) {
|
||||
Ok(WriterState::Done) => {
|
||||
@ -222,9 +224,11 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder
|
||||
let tmp = SharedBytes::default();
|
||||
let transfer = TransferEncoding::eof(tmp.clone());
|
||||
let mut enc = match encoding {
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Deflate => ContentEncoder::Deflate(
|
||||
DeflateEncoder::new(transfer, Compression::default()),
|
||||
),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new(
|
||||
transfer,
|
||||
Compression::default(),
|
||||
@ -283,10 +287,12 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder
|
||||
|
||||
req.replace_body(body);
|
||||
match encoding {
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Deflate => ContentEncoder::Deflate(DeflateEncoder::new(
|
||||
transfer,
|
||||
Compression::default(),
|
||||
)),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Gzip => {
|
||||
ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::default()))
|
||||
}
|
||||
@ -299,7 +305,7 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder
|
||||
}
|
||||
|
||||
fn streaming_encoding(
|
||||
buf: SharedBytes, version: Version, req: &mut ClientRequest
|
||||
buf: SharedBytes, version: Version, req: &mut ClientRequest,
|
||||
) -> TransferEncoding {
|
||||
if req.chunked() {
|
||||
// Enable transfer encoding
|
||||
|
@ -3,7 +3,6 @@ use futures::unsync::oneshot;
|
||||
use futures::{Async, Future, Poll};
|
||||
use smallvec::SmallVec;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem;
|
||||
|
||||
use actix::dev::{ContextImpl, SyncEnvelope, ToEnvelope};
|
||||
use actix::fut::ActorFuture;
|
||||
@ -174,7 +173,9 @@ where
|
||||
if self.stream.is_none() {
|
||||
self.stream = Some(SmallVec::new());
|
||||
}
|
||||
self.stream.as_mut().map(|s| s.push(frame));
|
||||
if let Some(s) = self.stream.as_mut() {
|
||||
s.push(frame)
|
||||
}
|
||||
self.inner.modify();
|
||||
}
|
||||
|
||||
@ -199,7 +200,7 @@ where
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<SmallVec<[Frame; 4]>>, Error> {
|
||||
let ctx: &mut HttpContext<A, S> =
|
||||
unsafe { mem::transmute(self as &mut HttpContext<A, S>) };
|
||||
unsafe { &mut *(self as &mut HttpContext<A, S> as *mut _) };
|
||||
|
||||
if self.inner.alive() {
|
||||
match self.inner.poll(ctx) {
|
||||
@ -261,7 +262,7 @@ impl<A: Actor> ActorFuture for Drain<A> {
|
||||
|
||||
#[inline]
|
||||
fn poll(
|
||||
&mut self, _: &mut A, _: &mut <Self::Actor as Actor>::Context
|
||||
&mut self, _: &mut A, _: &mut <Self::Actor as Actor>::Context,
|
||||
) -> Poll<Self::Item, Self::Error> {
|
||||
self.fut.poll().map_err(|_| ())
|
||||
}
|
||||
|
24
src/de.rs
24
src/de.rs
@ -59,7 +59,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
|
||||
}
|
||||
|
||||
fn deserialize_struct<V>(
|
||||
self, _: &'static str, _: &'static [&'static str], visitor: V
|
||||
self, _: &'static str, _: &'static [&'static str], visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
@ -75,7 +75,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
|
||||
}
|
||||
|
||||
fn deserialize_unit_struct<V>(
|
||||
self, _: &'static str, visitor: V
|
||||
self, _: &'static str, visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
@ -84,7 +84,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
|
||||
}
|
||||
|
||||
fn deserialize_newtype_struct<V>(
|
||||
self, _: &'static str, visitor: V
|
||||
self, _: &'static str, visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
@ -93,7 +93,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
|
||||
}
|
||||
|
||||
fn deserialize_tuple<V>(
|
||||
self, len: usize, visitor: V
|
||||
self, len: usize, visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
@ -114,7 +114,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
|
||||
}
|
||||
|
||||
fn deserialize_tuple_struct<V>(
|
||||
self, _: &'static str, len: usize, visitor: V
|
||||
self, _: &'static str, len: usize, visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
@ -135,7 +135,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
|
||||
}
|
||||
|
||||
fn deserialize_enum<V>(
|
||||
self, _: &'static str, _: &'static [&'static str], _: V
|
||||
self, _: &'static str, _: &'static [&'static str], _: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
@ -301,7 +301,7 @@ impl<'de> Deserializer<'de> for Value<'de> {
|
||||
}
|
||||
|
||||
fn deserialize_unit_struct<V>(
|
||||
self, _: &'static str, visitor: V
|
||||
self, _: &'static str, visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
@ -331,7 +331,7 @@ impl<'de> Deserializer<'de> for Value<'de> {
|
||||
}
|
||||
|
||||
fn deserialize_enum<V>(
|
||||
self, _: &'static str, _: &'static [&'static str], visitor: V
|
||||
self, _: &'static str, _: &'static [&'static str], visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
@ -342,7 +342,7 @@ impl<'de> Deserializer<'de> for Value<'de> {
|
||||
}
|
||||
|
||||
fn deserialize_newtype_struct<V>(
|
||||
self, _: &'static str, visitor: V
|
||||
self, _: &'static str, visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
@ -358,7 +358,7 @@ impl<'de> Deserializer<'de> for Value<'de> {
|
||||
}
|
||||
|
||||
fn deserialize_struct<V>(
|
||||
self, _: &'static str, _: &'static [&'static str], _: V
|
||||
self, _: &'static str, _: &'static [&'static str], _: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
@ -367,7 +367,7 @@ impl<'de> Deserializer<'de> for Value<'de> {
|
||||
}
|
||||
|
||||
fn deserialize_tuple_struct<V>(
|
||||
self, _: &'static str, _: usize, _: V
|
||||
self, _: &'static str, _: usize, _: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
@ -446,7 +446,7 @@ impl<'de> de::VariantAccess<'de> for UnitVariant {
|
||||
}
|
||||
|
||||
fn struct_variant<V>(
|
||||
self, _: &'static [&'static str], _: V
|
||||
self, _: &'static [&'static str], _: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
|
24
src/error.rs
24
src/error.rs
@ -45,6 +45,16 @@ impl Error {
|
||||
pub fn cause(&self) -> &ResponseError {
|
||||
self.cause.as_ref()
|
||||
}
|
||||
|
||||
/// Returns a reference to the Backtrace carried by this error, if it
|
||||
/// carries one.
|
||||
pub fn backtrace(&self) -> &Backtrace {
|
||||
if let Some(bt) = self.cause.backtrace() {
|
||||
bt
|
||||
} else {
|
||||
self.backtrace.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Error that can be converted to `HttpResponse`
|
||||
@ -298,7 +308,9 @@ pub enum HttpRangeError {
|
||||
/// Returned if first-byte-pos of all of the byte-range-spec
|
||||
/// values is greater than the content size.
|
||||
/// See `https://github.com/golang/go/commit/aa9b3d7`
|
||||
#[fail(display = "First-byte-pos of all of the byte-range-spec values is greater than the content size")]
|
||||
#[fail(
|
||||
display = "First-byte-pos of all of the byte-range-spec values is greater than the content size"
|
||||
)]
|
||||
NoOverlap,
|
||||
}
|
||||
|
||||
@ -530,7 +542,7 @@ impl From<UrlParseError> for UrlGenerationError {
|
||||
/// Helper type that can wrap any error and generate custom response.
|
||||
///
|
||||
/// In following example any `io::Error` will be converted into "BAD REQUEST"
|
||||
/// response as opposite to *INNTERNAL SERVER ERROR* which is defined by
|
||||
/// response as opposite to *INTERNAL SERVER ERROR* which is defined by
|
||||
/// default.
|
||||
///
|
||||
/// ```rust
|
||||
@ -630,7 +642,7 @@ where
|
||||
type Item = HttpResponse;
|
||||
type Error = Error;
|
||||
|
||||
fn respond_to(self, _: HttpRequest) -> Result<HttpResponse, Error> {
|
||||
fn respond_to<S>(self, _: &HttpRequest<S>) -> Result<HttpResponse, Error> {
|
||||
Err(self.into())
|
||||
}
|
||||
}
|
||||
@ -791,6 +803,12 @@ mod tests {
|
||||
assert_eq!(format!("{}", e.cause().unwrap()), desc);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_backtrace() {
|
||||
let e = ErrorBadRequest("err");
|
||||
let _ = e.backtrace();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_cause() {
|
||||
let orig = io::Error::new(io::ErrorKind::Other, "other");
|
||||
|
297
src/extractor.rs
297
src/extractor.rs
@ -1,17 +1,18 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::str;
|
||||
|
||||
use bytes::Bytes;
|
||||
use encoding::all::UTF_8;
|
||||
use encoding::types::{DecoderTrap, Encoding};
|
||||
use futures::future::{result, Future, FutureResult};
|
||||
use futures::{Async, Future, Poll};
|
||||
use mime::Mime;
|
||||
use serde::de::{self, DeserializeOwned};
|
||||
use serde_urlencoded;
|
||||
|
||||
use de::PathDeserializer;
|
||||
use error::{Error, ErrorBadRequest};
|
||||
use handler::{Either, FromRequest};
|
||||
use handler::{AsyncResult, FromRequest};
|
||||
use httpmessage::{HttpMessage, MessageBody, UrlEncoded};
|
||||
use httprequest::HttpRequest;
|
||||
|
||||
@ -99,19 +100,16 @@ impl<T> Path<T> {
|
||||
impl<T, S> FromRequest<S> for Path<T>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
S: 'static,
|
||||
{
|
||||
type Config = ();
|
||||
type Result = FutureResult<Self, Error>;
|
||||
type Result = Result<Self, Error>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
|
||||
let req = req.clone();
|
||||
result(
|
||||
de::Deserialize::deserialize(PathDeserializer::new(&req))
|
||||
.map_err(|e| e.into())
|
||||
.map(|inner| Path { inner }),
|
||||
)
|
||||
de::Deserialize::deserialize(PathDeserializer::new(&req))
|
||||
.map_err(|e| e.into())
|
||||
.map(|inner| Path { inner })
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,19 +167,16 @@ impl<T> Query<T> {
|
||||
impl<T, S> FromRequest<S> for Query<T>
|
||||
where
|
||||
T: de::DeserializeOwned,
|
||||
S: 'static,
|
||||
{
|
||||
type Config = ();
|
||||
type Result = FutureResult<Self, Error>;
|
||||
type Result = Result<Self, Error>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
|
||||
let req = req.clone();
|
||||
result(
|
||||
serde_urlencoded::from_str::<T>(req.query_string())
|
||||
.map_err(|e| e.into())
|
||||
.map(Query),
|
||||
)
|
||||
serde_urlencoded::from_str::<T>(req.query_string())
|
||||
.map_err(|e| e.into())
|
||||
.map(Query)
|
||||
}
|
||||
}
|
||||
|
||||
@ -327,17 +322,14 @@ impl Default for FormConfig {
|
||||
/// ```
|
||||
impl<S: 'static> FromRequest<S> for Bytes {
|
||||
type Config = PayloadConfig;
|
||||
type Result =
|
||||
Either<FutureResult<Self, Error>, Box<Future<Item = Self, Error = Error>>>;
|
||||
type Result = Result<Box<Future<Item = Self, Error = Error>>, Error>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
|
||||
// check content-type
|
||||
if let Err(e) = cfg.check_mimetype(req) {
|
||||
return Either::A(result(Err(e)));
|
||||
}
|
||||
cfg.check_mimetype(req)?;
|
||||
|
||||
Either::B(Box::new(
|
||||
Ok(Box::new(
|
||||
MessageBody::new(req.clone())
|
||||
.limit(cfg.limit)
|
||||
.from_err(),
|
||||
@ -374,27 +366,17 @@ impl<S: 'static> FromRequest<S> for Bytes {
|
||||
/// ```
|
||||
impl<S: 'static> FromRequest<S> for String {
|
||||
type Config = PayloadConfig;
|
||||
type Result =
|
||||
Either<FutureResult<String, Error>, Box<Future<Item = String, Error = Error>>>;
|
||||
type Result = Result<Box<Future<Item = String, Error = Error>>, Error>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
|
||||
// check content-type
|
||||
if let Err(e) = cfg.check_mimetype(req) {
|
||||
return Either::A(result(Err(e)));
|
||||
}
|
||||
cfg.check_mimetype(req)?;
|
||||
|
||||
// check charset
|
||||
let encoding = match req.encoding() {
|
||||
Err(_) => {
|
||||
return Either::A(result(Err(ErrorBadRequest(
|
||||
"Unknown request charset",
|
||||
))))
|
||||
}
|
||||
Ok(encoding) => encoding,
|
||||
};
|
||||
let encoding = req.encoding()?;
|
||||
|
||||
Either::B(Box::new(
|
||||
Ok(Box::new(
|
||||
MessageBody::new(req.clone())
|
||||
.limit(cfg.limit)
|
||||
.from_err()
|
||||
@ -464,6 +446,123 @@ impl Default for PayloadConfig {
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => {
|
||||
|
||||
/// FromRequest implementation for tuple
|
||||
impl<S, $($T: FromRequest<S> + 'static),+> FromRequest<S> for ($($T,)+)
|
||||
where
|
||||
S: 'static,
|
||||
{
|
||||
type Config = ($($T::Config,)+);
|
||||
type Result = Box<Future<Item = ($($T,)+), Error = Error>>;
|
||||
|
||||
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
|
||||
Box::new($fut_type {
|
||||
s: PhantomData,
|
||||
items: <($(Option<$T>,)+)>::default(),
|
||||
futs: ($(Some($T::from_request(req, &cfg.$n).into()),)+),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct $fut_type<S, $($T: FromRequest<S>),+>
|
||||
where
|
||||
S: 'static,
|
||||
{
|
||||
s: PhantomData<S>,
|
||||
items: ($(Option<$T>,)+),
|
||||
futs: ($(Option<AsyncResult<$T>>,)+),
|
||||
}
|
||||
|
||||
impl<S, $($T: FromRequest<S>),+> Future for $fut_type<S, $($T),+>
|
||||
where
|
||||
S: 'static,
|
||||
{
|
||||
type Item = ($($T,)+);
|
||||
type Error = Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
let mut ready = true;
|
||||
|
||||
$(
|
||||
if self.futs.$n.is_some() {
|
||||
match self.futs.$n.as_mut().unwrap().poll() {
|
||||
Ok(Async::Ready(item)) => {
|
||||
self.items.$n = Some(item);
|
||||
self.futs.$n.take();
|
||||
}
|
||||
Ok(Async::NotReady) => ready = false,
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
)+
|
||||
|
||||
if ready {
|
||||
Ok(Async::Ready(
|
||||
($(self.items.$n.take().unwrap(),)+)
|
||||
))
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tuple_from_req!(TupleFromRequest1, (0, A));
|
||||
tuple_from_req!(TupleFromRequest2, (0, A), (1, B));
|
||||
tuple_from_req!(TupleFromRequest3, (0, A), (1, B), (2, C));
|
||||
tuple_from_req!(TupleFromRequest4, (0, A), (1, B), (2, C), (3, D));
|
||||
tuple_from_req!(
|
||||
TupleFromRequest5,
|
||||
(0, A),
|
||||
(1, B),
|
||||
(2, C),
|
||||
(3, D),
|
||||
(4, E)
|
||||
);
|
||||
tuple_from_req!(
|
||||
TupleFromRequest6,
|
||||
(0, A),
|
||||
(1, B),
|
||||
(2, C),
|
||||
(3, D),
|
||||
(4, E),
|
||||
(5, F)
|
||||
);
|
||||
tuple_from_req!(
|
||||
TupleFromRequest7,
|
||||
(0, A),
|
||||
(1, B),
|
||||
(2, C),
|
||||
(3, D),
|
||||
(4, E),
|
||||
(5, F),
|
||||
(6, G)
|
||||
);
|
||||
tuple_from_req!(
|
||||
TupleFromRequest8,
|
||||
(0, A),
|
||||
(1, B),
|
||||
(2, C),
|
||||
(3, D),
|
||||
(4, E),
|
||||
(5, F),
|
||||
(6, G),
|
||||
(7, H)
|
||||
);
|
||||
tuple_from_req!(
|
||||
TupleFromRequest9,
|
||||
(0, A),
|
||||
(1, B),
|
||||
(2, C),
|
||||
(3, D),
|
||||
(4, E),
|
||||
(5, F),
|
||||
(6, G),
|
||||
(7, H),
|
||||
(8, I)
|
||||
);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@ -488,7 +587,11 @@ mod tests {
|
||||
req.payload_mut()
|
||||
.unread_data(Bytes::from_static(b"hello=world"));
|
||||
|
||||
match Bytes::from_request(&req, &cfg).poll().unwrap() {
|
||||
match Bytes::from_request(&req, &cfg)
|
||||
.unwrap()
|
||||
.poll()
|
||||
.unwrap()
|
||||
{
|
||||
Async::Ready(s) => {
|
||||
assert_eq!(s, Bytes::from_static(b"hello=world"));
|
||||
}
|
||||
@ -503,7 +606,11 @@ mod tests {
|
||||
req.payload_mut()
|
||||
.unread_data(Bytes::from_static(b"hello=world"));
|
||||
|
||||
match String::from_request(&req, &cfg).poll().unwrap() {
|
||||
match String::from_request(&req, &cfg)
|
||||
.unwrap()
|
||||
.poll()
|
||||
.unwrap()
|
||||
{
|
||||
Async::Ready(s) => {
|
||||
assert_eq!(s, "hello=world");
|
||||
}
|
||||
@ -580,73 +687,35 @@ mod tests {
|
||||
let (router, _) = Router::new("", ServerSettings::default(), routes);
|
||||
assert!(router.recognize(&mut req).is_some());
|
||||
|
||||
match Path::<MyStruct>::from_request(&req, &())
|
||||
.poll()
|
||||
.unwrap()
|
||||
{
|
||||
Async::Ready(s) => {
|
||||
assert_eq!(s.key, "name");
|
||||
assert_eq!(s.value, "user1");
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
let s = Path::<MyStruct>::from_request(&req, &()).unwrap();
|
||||
assert_eq!(s.key, "name");
|
||||
assert_eq!(s.value, "user1");
|
||||
|
||||
match Path::<(String, String)>::from_request(&req, &())
|
||||
.poll()
|
||||
.unwrap()
|
||||
{
|
||||
Async::Ready(s) => {
|
||||
assert_eq!(s.0, "name");
|
||||
assert_eq!(s.1, "user1");
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
let s = Path::<(String, String)>::from_request(&req, &()).unwrap();
|
||||
assert_eq!(s.0, "name");
|
||||
assert_eq!(s.1, "user1");
|
||||
|
||||
match Query::<Id>::from_request(&req, &()).poll().unwrap() {
|
||||
Async::Ready(s) => {
|
||||
assert_eq!(s.id, "test");
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
let s = Query::<Id>::from_request(&req, &()).unwrap();
|
||||
assert_eq!(s.id, "test");
|
||||
|
||||
let mut req = TestRequest::with_uri("/name/32/").finish();
|
||||
assert!(router.recognize(&mut req).is_some());
|
||||
|
||||
match Path::<Test2>::from_request(&req, &()).poll().unwrap() {
|
||||
Async::Ready(s) => {
|
||||
assert_eq!(s.as_ref().key, "name");
|
||||
assert_eq!(s.value, 32);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
let s = Path::<Test2>::from_request(&req, &()).unwrap();
|
||||
assert_eq!(s.as_ref().key, "name");
|
||||
assert_eq!(s.value, 32);
|
||||
|
||||
match Path::<(String, u8)>::from_request(&req, &())
|
||||
.poll()
|
||||
.unwrap()
|
||||
{
|
||||
Async::Ready(s) => {
|
||||
assert_eq!(s.0, "name");
|
||||
assert_eq!(s.1, 32);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
let s = Path::<(String, u8)>::from_request(&req, &()).unwrap();
|
||||
assert_eq!(s.0, "name");
|
||||
assert_eq!(s.1, 32);
|
||||
|
||||
match Path::<Vec<String>>::from_request(&req, &())
|
||||
.poll()
|
||||
.unwrap()
|
||||
{
|
||||
Async::Ready(s) => {
|
||||
assert_eq!(
|
||||
s.into_inner(),
|
||||
vec!["name".to_owned(), "32".to_owned()]
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
let res = Path::<Vec<String>>::extract(&req).unwrap();
|
||||
assert_eq!(res[0], "name".to_owned());
|
||||
assert_eq!(res[1], "32".to_owned());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_path_signle() {
|
||||
fn test_extract_path_single() {
|
||||
let mut resource = ResourceHandler::<()>::default();
|
||||
resource.name("index");
|
||||
let mut routes = Vec::new();
|
||||
@ -656,11 +725,39 @@ mod tests {
|
||||
let mut req = TestRequest::with_uri("/32/").finish();
|
||||
assert!(router.recognize(&mut req).is_some());
|
||||
|
||||
match Path::<i8>::from_request(&req, &()).poll().unwrap() {
|
||||
Async::Ready(s) => {
|
||||
assert_eq!(s.into_inner(), 32);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
assert_eq!(*Path::<i8>::from_request(&mut req, &()).unwrap(), 32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tuple_extract() {
|
||||
let mut req = TestRequest::with_uri("/name/user1/?id=test").finish();
|
||||
|
||||
let mut resource = ResourceHandler::<()>::default();
|
||||
resource.name("index");
|
||||
let mut routes = Vec::new();
|
||||
routes.push((
|
||||
Resource::new("index", "/{key}/{value}/"),
|
||||
Some(resource),
|
||||
));
|
||||
let (router, _) = Router::new("", ServerSettings::default(), routes);
|
||||
assert!(router.recognize(&mut req).is_some());
|
||||
|
||||
let res = match <(Path<(String, String)>,)>::extract(&req).poll() {
|
||||
Ok(Async::Ready(res)) => res,
|
||||
_ => panic!("error"),
|
||||
};
|
||||
assert_eq!((res.0).0, "name");
|
||||
assert_eq!((res.0).1, "user1");
|
||||
|
||||
let res = match <(Path<(String, String)>, Path<(String, String)>)>::extract(&req)
|
||||
.poll()
|
||||
{
|
||||
Ok(Async::Ready(res)) => res,
|
||||
_ => panic!("error"),
|
||||
};
|
||||
assert_eq!((res.0).0, "name");
|
||||
assert_eq!((res.0).1, "user1");
|
||||
assert_eq!((res.1).0, "name");
|
||||
assert_eq!((res.1).1, "user1");
|
||||
}
|
||||
}
|
||||
|
280
src/fs.rs
280
src/fs.rs
@ -14,10 +14,11 @@ use std::os::unix::fs::MetadataExt;
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use futures::{Async, Future, Poll, Stream};
|
||||
use futures_cpupool::{CpuFuture, CpuPool};
|
||||
use mime_guess::get_mime_type;
|
||||
use mime;
|
||||
use mime_guess::{get_mime_type, guess_mime_type};
|
||||
|
||||
use error::Error;
|
||||
use handler::{Handler, Reply, Responder, RouteHandler, WrapHandler};
|
||||
use handler::{AsyncResult, Handler, Responder, RouteHandler, WrapHandler};
|
||||
use header;
|
||||
use http::{Method, StatusCode};
|
||||
use httpmessage::HttpMessage;
|
||||
@ -160,7 +161,7 @@ impl DerefMut for NamedFile {
|
||||
}
|
||||
|
||||
/// Returns true if `req` has no `If-Match` header or one which matches `etag`.
|
||||
fn any_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool {
|
||||
fn any_match<S>(etag: Option<&header::EntityTag>, req: &HttpRequest<S>) -> bool {
|
||||
match req.get_header::<header::IfMatch>() {
|
||||
None | Some(header::IfMatch::Any) => true,
|
||||
Some(header::IfMatch::Items(ref items)) => {
|
||||
@ -177,7 +178,7 @@ fn any_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool {
|
||||
}
|
||||
|
||||
/// Returns true if `req` doesn't have an `If-None-Match` header matching `req`.
|
||||
fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool {
|
||||
fn none_match<S>(etag: Option<&header::EntityTag>, req: &HttpRequest<S>) -> bool {
|
||||
match req.get_header::<header::IfNoneMatch>() {
|
||||
Some(header::IfNoneMatch::Any) => false,
|
||||
Some(header::IfNoneMatch::Items(ref items)) => {
|
||||
@ -198,14 +199,28 @@ impl Responder for NamedFile {
|
||||
type Item = HttpResponse;
|
||||
type Error = io::Error;
|
||||
|
||||
fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, io::Error> {
|
||||
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, io::Error> {
|
||||
if self.status_code != StatusCode::OK {
|
||||
let mut resp = HttpResponse::build(self.status_code);
|
||||
resp.if_some(self.path().extension(), |ext, resp| {
|
||||
resp.set(header::ContentType(get_mime_type(
|
||||
&ext.to_string_lossy(),
|
||||
)));
|
||||
});
|
||||
}).if_some(self.path().file_name(), |file_name, resp| {
|
||||
let mime_type = guess_mime_type(self.path());
|
||||
let inline_or_attachment = match mime_type.type_() {
|
||||
mime::IMAGE | mime::TEXT => "inline",
|
||||
_ => "attachment",
|
||||
};
|
||||
resp.header(
|
||||
"Content-Disposition",
|
||||
format!(
|
||||
"{inline_or_attachment}; filename={filename}",
|
||||
inline_or_attachment = inline_or_attachment,
|
||||
filename = file_name.to_string_lossy()
|
||||
),
|
||||
);
|
||||
});
|
||||
let reader = ChunkedReadFile {
|
||||
size: self.md.len(),
|
||||
offset: 0,
|
||||
@ -229,7 +244,7 @@ impl Responder for NamedFile {
|
||||
let last_modified = self.last_modified();
|
||||
|
||||
// check preconditions
|
||||
let precondition_failed = if !any_match(etag.as_ref(), &req) {
|
||||
let precondition_failed = if !any_match(etag.as_ref(), req) {
|
||||
true
|
||||
} else if let (Some(ref m), Some(header::IfUnmodifiedSince(ref since))) =
|
||||
(last_modified, req.get_header())
|
||||
@ -240,7 +255,7 @@ impl Responder for NamedFile {
|
||||
};
|
||||
|
||||
// check last modified
|
||||
let not_modified = if !none_match(etag.as_ref(), &req) {
|
||||
let not_modified = if !none_match(etag.as_ref(), req) {
|
||||
true
|
||||
} else if let (Some(ref m), Some(header::IfModifiedSince(ref since))) =
|
||||
(last_modified, req.get_header())
|
||||
@ -256,7 +271,22 @@ impl Responder for NamedFile {
|
||||
resp.set(header::ContentType(get_mime_type(
|
||||
&ext.to_string_lossy(),
|
||||
)));
|
||||
}).if_some(last_modified, |lm, resp| {
|
||||
}).if_some(self.path().file_name(), |file_name, resp| {
|
||||
let mime_type = guess_mime_type(self.path());
|
||||
let inline_or_attachment = match mime_type.type_() {
|
||||
mime::IMAGE | mime::TEXT => "inline",
|
||||
_ => "attachment",
|
||||
};
|
||||
resp.header(
|
||||
"Content-Disposition",
|
||||
format!(
|
||||
"{inline_or_attachment}; filename={filename}",
|
||||
inline_or_attachment = inline_or_attachment,
|
||||
filename = file_name.to_string_lossy()
|
||||
),
|
||||
);
|
||||
})
|
||||
.if_some(last_modified, |lm, resp| {
|
||||
resp.set(header::LastModified(lm));
|
||||
})
|
||||
.if_some(etag, |etag, resp| {
|
||||
@ -335,11 +365,14 @@ impl Stream for ChunkedReadFile {
|
||||
}
|
||||
}
|
||||
|
||||
type DirectoryRenderer<S> =
|
||||
Fn(&Directory, &HttpRequest<S>) -> Result<HttpResponse, io::Error>;
|
||||
|
||||
/// A directory; responds with the generated directory listing.
|
||||
#[derive(Debug)]
|
||||
pub struct Directory {
|
||||
base: PathBuf,
|
||||
path: PathBuf,
|
||||
pub base: PathBuf,
|
||||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
impl Directory {
|
||||
@ -347,7 +380,7 @@ impl Directory {
|
||||
Directory { base, path }
|
||||
}
|
||||
|
||||
fn can_list(&self, entry: &io::Result<DirEntry>) -> bool {
|
||||
pub fn is_visible(&self, entry: &io::Result<DirEntry>) -> bool {
|
||||
if let Ok(ref entry) = *entry {
|
||||
if let Some(name) = entry.file_name().to_str() {
|
||||
if name.starts_with('.') {
|
||||
@ -363,61 +396,58 @@ impl Directory {
|
||||
}
|
||||
}
|
||||
|
||||
impl Responder for Directory {
|
||||
type Item = HttpResponse;
|
||||
type Error = io::Error;
|
||||
fn directory_listing<S>(
|
||||
dir: &Directory, req: &HttpRequest<S>,
|
||||
) -> Result<HttpResponse, io::Error> {
|
||||
let index_of = format!("Index of {}", req.path());
|
||||
let mut body = String::new();
|
||||
let base = Path::new(req.path());
|
||||
|
||||
fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, io::Error> {
|
||||
let index_of = format!("Index of {}", req.path());
|
||||
let mut body = String::new();
|
||||
let base = Path::new(req.path());
|
||||
for entry in dir.path.read_dir()? {
|
||||
if dir.is_visible(&entry) {
|
||||
let entry = entry.unwrap();
|
||||
let p = match entry.path().strip_prefix(&dir.path) {
|
||||
Ok(p) => base.join(p),
|
||||
Err(_) => continue,
|
||||
};
|
||||
// show file url as relative to static path
|
||||
let file_url = format!("{}", p.to_string_lossy());
|
||||
|
||||
for entry in self.path.read_dir()? {
|
||||
if self.can_list(&entry) {
|
||||
let entry = entry.unwrap();
|
||||
let p = match entry.path().strip_prefix(&self.path) {
|
||||
Ok(p) => base.join(p),
|
||||
Err(_) => continue,
|
||||
};
|
||||
// show file url as relative to static path
|
||||
let file_url = format!("{}", p.to_string_lossy());
|
||||
|
||||
// if file is a directory, add '/' to the end of the name
|
||||
if let Ok(metadata) = entry.metadata() {
|
||||
if metadata.is_dir() {
|
||||
let _ = write!(
|
||||
body,
|
||||
"<li><a href=\"{}\">{}/</a></li>",
|
||||
file_url,
|
||||
entry.file_name().to_string_lossy()
|
||||
);
|
||||
} else {
|
||||
let _ = write!(
|
||||
body,
|
||||
"<li><a href=\"{}\">{}</a></li>",
|
||||
file_url,
|
||||
entry.file_name().to_string_lossy()
|
||||
);
|
||||
}
|
||||
// if file is a directory, add '/' to the end of the name
|
||||
if let Ok(metadata) = entry.metadata() {
|
||||
if metadata.is_dir() {
|
||||
let _ = write!(
|
||||
body,
|
||||
"<li><a href=\"{}\">{}/</a></li>",
|
||||
file_url,
|
||||
entry.file_name().to_string_lossy()
|
||||
);
|
||||
} else {
|
||||
continue;
|
||||
let _ = write!(
|
||||
body,
|
||||
"<li><a href=\"{}\">{}</a></li>",
|
||||
file_url,
|
||||
entry.file_name().to_string_lossy()
|
||||
);
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let html = format!(
|
||||
"<html>\
|
||||
<head><title>{}</title></head>\
|
||||
<body><h1>{}</h1>\
|
||||
<ul>\
|
||||
{}\
|
||||
</ul></body>\n</html>",
|
||||
index_of, index_of, body
|
||||
);
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_type("text/html; charset=utf-8")
|
||||
.body(html))
|
||||
}
|
||||
|
||||
let html = format!(
|
||||
"<html>\
|
||||
<head><title>{}</title></head>\
|
||||
<body><h1>{}</h1>\
|
||||
<ul>\
|
||||
{}\
|
||||
</ul></body>\n</html>",
|
||||
index_of, index_of, body
|
||||
);
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_type("text/html; charset=utf-8")
|
||||
.body(html))
|
||||
}
|
||||
|
||||
/// Static files handling
|
||||
@ -442,6 +472,7 @@ pub struct StaticFiles<S> {
|
||||
show_index: bool,
|
||||
cpu_pool: CpuPool,
|
||||
default: Box<RouteHandler<S>>,
|
||||
renderer: Box<DirectoryRenderer<S>>,
|
||||
_chunk_size: usize,
|
||||
_follow_symlinks: bool,
|
||||
}
|
||||
@ -505,6 +536,7 @@ impl<S: 'static> StaticFiles<S> {
|
||||
default: Box::new(WrapHandler::new(|_| {
|
||||
HttpResponse::new(StatusCode::NOT_FOUND)
|
||||
})),
|
||||
renderer: Box::new(directory_listing),
|
||||
_chunk_size: 0,
|
||||
_follow_symlinks: false,
|
||||
}
|
||||
@ -518,6 +550,17 @@ impl<S: 'static> StaticFiles<S> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set custom directory renderer
|
||||
pub fn files_listing_renderer<F>(mut self, f: F) -> Self
|
||||
where
|
||||
for<'r, 's> F: Fn(&'r Directory, &'s HttpRequest<S>)
|
||||
-> Result<HttpResponse, io::Error>
|
||||
+ 'static,
|
||||
{
|
||||
self.renderer = Box::new(f);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set index file
|
||||
///
|
||||
/// Redirects to specific index file for directory "/" instead of
|
||||
@ -535,7 +578,7 @@ impl<S: 'static> StaticFiles<S> {
|
||||
}
|
||||
|
||||
impl<S: 'static> Handler<S> for StaticFiles<S> {
|
||||
type Result = Result<Reply, Error>;
|
||||
type Result = Result<AsyncResult<HttpResponse>, Error>;
|
||||
|
||||
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
|
||||
if !self.accessible {
|
||||
@ -543,7 +586,7 @@ impl<S: 'static> Handler<S> for StaticFiles<S> {
|
||||
} else {
|
||||
let relpath = match req.match_info()
|
||||
.get("tail")
|
||||
.map(|tail| PathBuf::from_param(tail))
|
||||
.map(|tail| PathBuf::from_param(tail.trim_left_matches('/')))
|
||||
{
|
||||
Some(Ok(path)) => path,
|
||||
_ => return Ok(self.default.handle(req)),
|
||||
@ -569,19 +612,18 @@ impl<S: 'static> Handler<S> for StaticFiles<S> {
|
||||
HttpResponse::Found()
|
||||
.header(header::LOCATION, new_path.as_str())
|
||||
.finish()
|
||||
.respond_to(req.drop_state())
|
||||
.respond_to(&req)
|
||||
} else if self.show_index {
|
||||
Directory::new(self.directory.clone(), path)
|
||||
.respond_to(req.drop_state())?
|
||||
.respond_to(req.drop_state())
|
||||
let dir = Directory::new(self.directory.clone(), path);
|
||||
Ok((*self.renderer)(&dir, &req)?.into())
|
||||
} else {
|
||||
Ok(self.default.handle(req))
|
||||
}
|
||||
} else {
|
||||
NamedFile::open(path)?
|
||||
.set_cpu_pool(self.cpu_pool.clone())
|
||||
.respond_to(req.drop_state())?
|
||||
.respond_to(req.drop_state())
|
||||
.respond_to(&req)?
|
||||
.respond_to(&req)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -595,7 +637,7 @@ mod tests {
|
||||
use test::{self, TestRequest};
|
||||
|
||||
#[test]
|
||||
fn test_named_file() {
|
||||
fn test_named_file_text() {
|
||||
assert!(NamedFile::open("test--").is_err());
|
||||
let mut file = NamedFile::open("Cargo.toml")
|
||||
.unwrap()
|
||||
@ -608,15 +650,73 @@ mod tests {
|
||||
let _f: &mut File = &mut file;
|
||||
}
|
||||
|
||||
let resp = file.respond_to(HttpRequest::default()).unwrap();
|
||||
let resp = file.respond_to(&HttpRequest::default()).unwrap();
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
"text/x-toml"
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
resp.headers()
|
||||
.get(header::CONTENT_DISPOSITION)
|
||||
.unwrap(),
|
||||
"inline; filename=Cargo.toml"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_named_file_status_code() {
|
||||
fn test_named_file_image() {
|
||||
let mut file = NamedFile::open("tests/test.png")
|
||||
.unwrap()
|
||||
.set_cpu_pool(CpuPool::new(1));
|
||||
{
|
||||
file.file();
|
||||
let _f: &File = &file;
|
||||
}
|
||||
{
|
||||
let _f: &mut File = &mut file;
|
||||
}
|
||||
|
||||
let resp = file.respond_to(&HttpRequest::default()).unwrap();
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
"image/png"
|
||||
);
|
||||
assert_eq!(
|
||||
resp.headers()
|
||||
.get(header::CONTENT_DISPOSITION)
|
||||
.unwrap(),
|
||||
"inline; filename=test.png"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_named_file_binary() {
|
||||
let mut file = NamedFile::open("tests/test.binary")
|
||||
.unwrap()
|
||||
.set_cpu_pool(CpuPool::new(1));
|
||||
{
|
||||
file.file();
|
||||
let _f: &File = &file;
|
||||
}
|
||||
{
|
||||
let _f: &mut File = &mut file;
|
||||
}
|
||||
|
||||
let resp = file.respond_to(&HttpRequest::default()).unwrap();
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
"application/octet-stream"
|
||||
);
|
||||
assert_eq!(
|
||||
resp.headers()
|
||||
.get(header::CONTENT_DISPOSITION)
|
||||
.unwrap(),
|
||||
"attachment; filename=test.binary"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_named_file_status_code_text() {
|
||||
let mut file = NamedFile::open("Cargo.toml")
|
||||
.unwrap()
|
||||
.set_status_code(StatusCode::NOT_FOUND)
|
||||
@ -629,11 +729,17 @@ mod tests {
|
||||
let _f: &mut File = &mut file;
|
||||
}
|
||||
|
||||
let resp = file.respond_to(HttpRequest::default()).unwrap();
|
||||
let resp = file.respond_to(&HttpRequest::default()).unwrap();
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
"text/x-toml"
|
||||
);
|
||||
assert_eq!(
|
||||
resp.headers()
|
||||
.get(header::CONTENT_DISPOSITION)
|
||||
.unwrap(),
|
||||
"inline; filename=Cargo.toml"
|
||||
);
|
||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
@ -642,7 +748,7 @@ mod tests {
|
||||
let req = TestRequest::default().method(Method::POST).finish();
|
||||
let file = NamedFile::open("Cargo.toml").unwrap();
|
||||
|
||||
let resp = file.only_get().respond_to(req).unwrap();
|
||||
let resp = file.only_get().respond_to(&req).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
|
||||
}
|
||||
|
||||
@ -650,7 +756,7 @@ mod tests {
|
||||
fn test_named_file_any_method() {
|
||||
let req = TestRequest::default().method(Method::POST).finish();
|
||||
let file = NamedFile::open("Cargo.toml").unwrap();
|
||||
let resp = file.respond_to(req).unwrap();
|
||||
let resp = file.respond_to(&req).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
@ -659,17 +765,17 @@ mod tests {
|
||||
let mut st = StaticFiles::new(".").show_files_listing();
|
||||
st.accessible = false;
|
||||
let resp = st.handle(HttpRequest::default())
|
||||
.respond_to(HttpRequest::default())
|
||||
.respond_to(&HttpRequest::default())
|
||||
.unwrap();
|
||||
let resp = resp.as_response().expect("HTTP Response");
|
||||
let resp = resp.as_msg();
|
||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||
|
||||
st.accessible = true;
|
||||
st.show_index = false;
|
||||
let resp = st.handle(HttpRequest::default())
|
||||
.respond_to(HttpRequest::default())
|
||||
.respond_to(&HttpRequest::default())
|
||||
.unwrap();
|
||||
let resp = resp.as_response().expect("HTTP Response");
|
||||
let resp = resp.as_msg();
|
||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||
|
||||
let mut req = HttpRequest::default();
|
||||
@ -677,9 +783,9 @@ mod tests {
|
||||
|
||||
st.show_index = true;
|
||||
let resp = st.handle(req)
|
||||
.respond_to(HttpRequest::default())
|
||||
.respond_to(&HttpRequest::default())
|
||||
.unwrap();
|
||||
let resp = resp.as_response().expect("HTTP Response");
|
||||
let resp = resp.as_msg();
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
"text/html; charset=utf-8"
|
||||
@ -695,9 +801,9 @@ mod tests {
|
||||
req.match_info_mut().add("tail", "tests");
|
||||
|
||||
let resp = st.handle(req)
|
||||
.respond_to(HttpRequest::default())
|
||||
.respond_to(&HttpRequest::default())
|
||||
.unwrap();
|
||||
let resp = resp.as_response().expect("HTTP Response");
|
||||
let resp = resp.as_msg();
|
||||
assert_eq!(resp.status(), StatusCode::FOUND);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::LOCATION).unwrap(),
|
||||
@ -708,9 +814,9 @@ mod tests {
|
||||
req.match_info_mut().add("tail", "tests/");
|
||||
|
||||
let resp = st.handle(req)
|
||||
.respond_to(HttpRequest::default())
|
||||
.respond_to(&HttpRequest::default())
|
||||
.unwrap();
|
||||
let resp = resp.as_response().expect("HTTP Response");
|
||||
let resp = resp.as_msg();
|
||||
assert_eq!(resp.status(), StatusCode::FOUND);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::LOCATION).unwrap(),
|
||||
@ -725,9 +831,9 @@ mod tests {
|
||||
req.match_info_mut().add("tail", "tools/wsload");
|
||||
|
||||
let resp = st.handle(req)
|
||||
.respond_to(HttpRequest::default())
|
||||
.respond_to(&HttpRequest::default())
|
||||
.unwrap();
|
||||
let resp = resp.as_response().expect("HTTP Response");
|
||||
let resp = resp.as_msg();
|
||||
assert_eq!(resp.status(), StatusCode::FOUND);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::LOCATION).unwrap(),
|
||||
|
217
src/handler.rs
217
src/handler.rs
@ -1,8 +1,9 @@
|
||||
use futures::Poll;
|
||||
use futures::future::{err, ok, Future, FutureResult};
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::Deref;
|
||||
|
||||
use futures::future::{err, ok, Future};
|
||||
use futures::{Async, Poll};
|
||||
|
||||
use error::Error;
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
@ -22,30 +23,36 @@ pub trait Handler<S>: 'static {
|
||||
/// Types that implement this trait can be used as the return type of a handler.
|
||||
pub trait Responder {
|
||||
/// The associated item which can be returned.
|
||||
type Item: Into<Reply>;
|
||||
type Item: Into<AsyncResult<HttpResponse>>;
|
||||
|
||||
/// The associated error which can be returned.
|
||||
type Error: Into<Error>;
|
||||
|
||||
/// Convert itself to `Reply` or `Error`.
|
||||
fn respond_to(self, req: HttpRequest) -> Result<Self::Item, Self::Error>;
|
||||
/// Convert itself to `AsyncResult` or `Error`.
|
||||
fn respond_to<S: 'static>(
|
||||
self, req: &HttpRequest<S>,
|
||||
) -> Result<Self::Item, Self::Error>;
|
||||
}
|
||||
|
||||
/// Trait implemented by types that can be extracted from request.
|
||||
///
|
||||
/// Types that implement this trait can be used with `Route::with()` method.
|
||||
pub trait FromRequest<S>: Sized
|
||||
where
|
||||
S: 'static,
|
||||
{
|
||||
pub trait FromRequest<S>: Sized {
|
||||
/// Configuration for conversion process
|
||||
type Config: Default;
|
||||
|
||||
/// Future that resolves to a Self
|
||||
type Result: Future<Item = Self, Error = Error>;
|
||||
type Result: Into<AsyncResult<Self>>;
|
||||
|
||||
/// Convert request to a Self
|
||||
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result;
|
||||
|
||||
/// Convert request to a Self
|
||||
///
|
||||
/// This method uses default extractor configuration
|
||||
fn extract(req: &HttpRequest<S>) -> Self::Result {
|
||||
Self::from_request(req, &Self::Config::default())
|
||||
}
|
||||
}
|
||||
|
||||
/// Combines two different responder types into a single type
|
||||
@ -88,10 +95,12 @@ where
|
||||
A: Responder,
|
||||
B: Responder,
|
||||
{
|
||||
type Item = Reply;
|
||||
type Item = AsyncResult<HttpResponse>;
|
||||
type Error = Error;
|
||||
|
||||
fn respond_to(self, req: HttpRequest) -> Result<Reply, Error> {
|
||||
fn respond_to<S: 'static>(
|
||||
self, req: &HttpRequest<S>,
|
||||
) -> Result<AsyncResult<HttpResponse>, Error> {
|
||||
match self {
|
||||
Either::A(a) => match a.respond_to(req) {
|
||||
Ok(val) => Ok(val.into()),
|
||||
@ -177,67 +186,109 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents response process.
|
||||
pub struct Reply(ReplyItem);
|
||||
/// Represents async result
|
||||
///
|
||||
/// Result could be in tree different forms.
|
||||
/// * Ok(T) - ready item
|
||||
/// * Err(E) - error happen during reply process
|
||||
/// * Future<T, E> - reply process completes in the future
|
||||
pub struct AsyncResult<I, E = Error>(Option<AsyncResultItem<I, E>>);
|
||||
|
||||
pub(crate) enum ReplyItem {
|
||||
Message(HttpResponse),
|
||||
Future(Box<Future<Item = HttpResponse, Error = Error>>),
|
||||
impl<I, E> Future for AsyncResult<I, E> {
|
||||
type Item = I;
|
||||
type Error = E;
|
||||
|
||||
fn poll(&mut self) -> Poll<I, E> {
|
||||
let res = self.0.take().expect("use after resolve");
|
||||
match res {
|
||||
AsyncResultItem::Ok(msg) => Ok(Async::Ready(msg)),
|
||||
AsyncResultItem::Err(err) => Err(err),
|
||||
AsyncResultItem::Future(mut fut) => match fut.poll() {
|
||||
Ok(Async::NotReady) => {
|
||||
self.0 = Some(AsyncResultItem::Future(fut));
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
Ok(Async::Ready(msg)) => Ok(Async::Ready(msg)),
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Reply {
|
||||
pub(crate) enum AsyncResultItem<I, E> {
|
||||
Ok(I),
|
||||
Err(E),
|
||||
Future(Box<Future<Item = I, Error = E>>),
|
||||
}
|
||||
|
||||
impl<I, E> AsyncResult<I, E> {
|
||||
/// Create async response
|
||||
#[inline]
|
||||
pub fn async<F>(fut: F) -> Reply
|
||||
where
|
||||
F: Future<Item = HttpResponse, Error = Error> + 'static,
|
||||
{
|
||||
Reply(ReplyItem::Future(Box::new(fut)))
|
||||
pub fn async(fut: Box<Future<Item = I, Error = E>>) -> AsyncResult<I, E> {
|
||||
AsyncResult(Some(AsyncResultItem::Future(fut)))
|
||||
}
|
||||
|
||||
/// Send response
|
||||
#[inline]
|
||||
pub fn response<R: Into<HttpResponse>>(response: R) -> Reply {
|
||||
Reply(ReplyItem::Message(response.into()))
|
||||
pub fn ok<R: Into<I>>(ok: R) -> AsyncResult<I, E> {
|
||||
AsyncResult(Some(AsyncResultItem::Ok(ok.into())))
|
||||
}
|
||||
|
||||
/// Send error
|
||||
#[inline]
|
||||
pub fn err<R: Into<E>>(err: R) -> AsyncResult<I, E> {
|
||||
AsyncResult(Some(AsyncResultItem::Err(err.into())))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn into(self) -> ReplyItem {
|
||||
self.0
|
||||
pub(crate) fn into(self) -> AsyncResultItem<I, E> {
|
||||
self.0.expect("use after resolve")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn as_response(&self) -> Option<&HttpResponse> {
|
||||
match self.0 {
|
||||
ReplyItem::Message(ref resp) => Some(resp),
|
||||
pub(crate) fn as_msg(&self) -> &I {
|
||||
match self.0.as_ref().unwrap() {
|
||||
&AsyncResultItem::Ok(ref resp) => resp,
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn as_err(&self) -> Option<&E> {
|
||||
match self.0.as_ref().unwrap() {
|
||||
&AsyncResultItem::Err(ref err) => Some(err),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Responder for Reply {
|
||||
type Item = Reply;
|
||||
impl Responder for AsyncResult<HttpResponse> {
|
||||
type Item = AsyncResult<HttpResponse>;
|
||||
type Error = Error;
|
||||
|
||||
fn respond_to(self, _: HttpRequest) -> Result<Reply, Error> {
|
||||
fn respond_to<S>(
|
||||
self, _: &HttpRequest<S>,
|
||||
) -> Result<AsyncResult<HttpResponse>, Error> {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Responder for HttpResponse {
|
||||
type Item = Reply;
|
||||
type Item = AsyncResult<HttpResponse>;
|
||||
type Error = Error;
|
||||
|
||||
#[inline]
|
||||
fn respond_to(self, _: HttpRequest) -> Result<Reply, Error> {
|
||||
Ok(Reply(ReplyItem::Message(self)))
|
||||
fn respond_to<S>(
|
||||
self, _: &HttpRequest<S>,
|
||||
) -> Result<AsyncResult<HttpResponse>, Error> {
|
||||
Ok(AsyncResult(Some(AsyncResultItem::Ok(self))))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HttpResponse> for Reply {
|
||||
impl<T> From<T> for AsyncResult<T> {
|
||||
#[inline]
|
||||
fn from(resp: HttpResponse) -> Reply {
|
||||
Reply(ReplyItem::Message(resp))
|
||||
fn from(resp: T) -> AsyncResult<T> {
|
||||
AsyncResult(Some(AsyncResultItem::Ok(resp)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -245,7 +296,7 @@ impl<T: Responder, E: Into<Error>> Responder for Result<T, E> {
|
||||
type Item = <T as Responder>::Item;
|
||||
type Error = Error;
|
||||
|
||||
fn respond_to(self, req: HttpRequest) -> Result<Self::Item, Self::Error> {
|
||||
fn respond_to<S: 'static>(self, req: &HttpRequest<S>) -> Result<Self::Item, Error> {
|
||||
match self {
|
||||
Ok(val) => match val.respond_to(req) {
|
||||
Ok(val) => Ok(val),
|
||||
@ -256,30 +307,42 @@ impl<T: Responder, E: Into<Error>> Responder for Result<T, E> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Into<Error>> From<Result<Reply, E>> for Reply {
|
||||
impl<T, E: Into<Error>> From<Result<AsyncResult<T>, E>> for AsyncResult<T> {
|
||||
#[inline]
|
||||
fn from(res: Result<Reply, E>) -> Self {
|
||||
fn from(res: Result<AsyncResult<T>, E>) -> Self {
|
||||
match res {
|
||||
Ok(val) => val,
|
||||
Err(err) => Reply(ReplyItem::Message(err.into().into())),
|
||||
Err(err) => AsyncResult(Some(AsyncResultItem::Err(err.into()))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Into<Error>> From<Result<HttpResponse, E>> for Reply {
|
||||
impl<T, E: Into<Error>> From<Result<T, E>> for AsyncResult<T> {
|
||||
#[inline]
|
||||
fn from(res: Result<HttpResponse, E>) -> Self {
|
||||
fn from(res: Result<T, E>) -> Self {
|
||||
match res {
|
||||
Ok(val) => Reply(ReplyItem::Message(val)),
|
||||
Err(err) => Reply(ReplyItem::Message(err.into().into())),
|
||||
Ok(val) => AsyncResult(Some(AsyncResultItem::Ok(val))),
|
||||
Err(err) => AsyncResult(Some(AsyncResultItem::Err(err.into()))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<Future<Item = HttpResponse, Error = Error>>> for Reply {
|
||||
impl<T, E: Into<Error>> From<Result<Box<Future<Item = T, Error = Error>>, E>>
|
||||
for AsyncResult<T>
|
||||
{
|
||||
#[inline]
|
||||
fn from(fut: Box<Future<Item = HttpResponse, Error = Error>>) -> Reply {
|
||||
Reply(ReplyItem::Future(fut))
|
||||
fn from(res: Result<Box<Future<Item = T, Error = Error>>, E>) -> Self {
|
||||
match res {
|
||||
Ok(fut) => AsyncResult(Some(AsyncResultItem::Future(fut))),
|
||||
Err(err) => AsyncResult(Some(AsyncResultItem::Err(err.into()))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Box<Future<Item = T, Error = Error>>> for AsyncResult<T> {
|
||||
#[inline]
|
||||
fn from(fut: Box<Future<Item = T, Error = Error>>) -> AsyncResult<T> {
|
||||
AsyncResult(Some(AsyncResultItem::Future(fut)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -291,26 +354,29 @@ where
|
||||
I: Responder + 'static,
|
||||
E: Into<Error> + 'static,
|
||||
{
|
||||
type Item = Reply;
|
||||
type Item = AsyncResult<HttpResponse>;
|
||||
type Error = Error;
|
||||
|
||||
#[inline]
|
||||
fn respond_to(self, req: HttpRequest) -> Result<Reply, Error> {
|
||||
fn respond_to<S: 'static>(
|
||||
self, req: &HttpRequest<S>,
|
||||
) -> Result<AsyncResult<HttpResponse>, Error> {
|
||||
let req = req.clone();
|
||||
let fut = self.map_err(|e| e.into())
|
||||
.then(move |r| match r.respond_to(req) {
|
||||
Ok(reply) => match reply.into().0 {
|
||||
ReplyItem::Message(resp) => ok(resp),
|
||||
.then(move |r| match r.respond_to(&req) {
|
||||
Ok(reply) => match reply.into().into() {
|
||||
AsyncResultItem::Ok(resp) => ok(resp),
|
||||
_ => panic!("Nested async replies are not supported"),
|
||||
},
|
||||
Err(e) => err(e),
|
||||
});
|
||||
Ok(Reply::async(fut))
|
||||
Ok(AsyncResult::async(Box::new(fut)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait defines object that could be registered as resource route
|
||||
// /// Trait defines object that could be registered as resource route
|
||||
pub(crate) trait RouteHandler<S>: 'static {
|
||||
fn handle(&mut self, req: HttpRequest<S>) -> Reply;
|
||||
fn handle(&mut self, req: HttpRequest<S>) -> AsyncResult<HttpResponse>;
|
||||
}
|
||||
|
||||
/// Route handler wrapper for Handler
|
||||
@ -344,11 +410,10 @@ where
|
||||
R: Responder + 'static,
|
||||
S: 'static,
|
||||
{
|
||||
fn handle(&mut self, req: HttpRequest<S>) -> Reply {
|
||||
let req2 = req.drop_state();
|
||||
match self.h.handle(req).respond_to(req2) {
|
||||
fn handle(&mut self, req: HttpRequest<S>) -> AsyncResult<HttpResponse> {
|
||||
match self.h.handle(req.clone()).respond_to(&req) {
|
||||
Ok(reply) => reply.into(),
|
||||
Err(err) => Reply::response(err.into()),
|
||||
Err(err) => AsyncResult::err(err.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -390,18 +455,18 @@ where
|
||||
E: Into<Error> + 'static,
|
||||
S: 'static,
|
||||
{
|
||||
fn handle(&mut self, req: HttpRequest<S>) -> Reply {
|
||||
let req2 = req.drop_state();
|
||||
let fut = (self.h)(req).map_err(|e| e.into()).then(move |r| {
|
||||
match r.respond_to(req2) {
|
||||
Ok(reply) => match reply.into().0 {
|
||||
ReplyItem::Message(resp) => ok(resp),
|
||||
_ => panic!("Nested async replies are not supported"),
|
||||
fn handle(&mut self, req: HttpRequest<S>) -> AsyncResult<HttpResponse> {
|
||||
let fut = (self.h)(req.clone())
|
||||
.map_err(|e| e.into())
|
||||
.then(move |r| match r.respond_to(&req) {
|
||||
Ok(reply) => match reply.into().into() {
|
||||
AsyncResultItem::Ok(resp) => Either::A(ok(resp)),
|
||||
AsyncResultItem::Err(e) => Either::A(err(e)),
|
||||
AsyncResultItem::Future(fut) => Either::B(fut),
|
||||
},
|
||||
Err(e) => err(e),
|
||||
}
|
||||
});
|
||||
Reply::async(fut)
|
||||
Err(e) => Either::A(err(e)),
|
||||
});
|
||||
AsyncResult::async(Box::new(fut))
|
||||
}
|
||||
}
|
||||
|
||||
@ -447,12 +512,12 @@ impl<S> Deref for State<S> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static> FromRequest<S> for State<S> {
|
||||
impl<S> FromRequest<S> for State<S> {
|
||||
type Config = ();
|
||||
type Result = FutureResult<Self, Error>;
|
||||
type Result = State<S>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
|
||||
ok(State(req.clone()))
|
||||
State(req.clone())
|
||||
}
|
||||
}
|
||||
|
@ -6,8 +6,8 @@ use std::str::FromStr;
|
||||
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use mime::Mime;
|
||||
use modhttp::Error as HttpError;
|
||||
use modhttp::header::GetAll;
|
||||
use modhttp::Error as HttpError;
|
||||
|
||||
pub use modhttp::header::*;
|
||||
|
||||
@ -116,8 +116,10 @@ pub enum ContentEncoding {
|
||||
#[cfg(feature = "brotli")]
|
||||
Br,
|
||||
/// A format using the zlib structure with deflate algorithm
|
||||
#[cfg(feature = "flate2")]
|
||||
Deflate,
|
||||
/// Gzip algorithm
|
||||
#[cfg(feature = "flate2")]
|
||||
Gzip,
|
||||
/// Indicates the identity function (i.e. no compression, nor modification)
|
||||
Identity,
|
||||
@ -137,7 +139,9 @@ impl ContentEncoding {
|
||||
match *self {
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoding::Br => "br",
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Gzip => "gzip",
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Deflate => "deflate",
|
||||
ContentEncoding::Identity | ContentEncoding::Auto => "identity",
|
||||
}
|
||||
@ -149,7 +153,9 @@ impl ContentEncoding {
|
||||
match *self {
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoding::Br => 1.1,
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Gzip => 1.0,
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Deflate => 0.9,
|
||||
ContentEncoding::Identity | ContentEncoding::Auto => 0.1,
|
||||
}
|
||||
@ -159,10 +165,12 @@ impl ContentEncoding {
|
||||
// TODO: remove memory allocation
|
||||
impl<'a> From<&'a str> for ContentEncoding {
|
||||
fn from(s: &'a str) -> ContentEncoding {
|
||||
match s.trim().to_lowercase().as_ref() {
|
||||
match AsRef::<str>::as_ref(&s.trim().to_lowercase()) {
|
||||
#[cfg(feature = "brotli")]
|
||||
"br" => ContentEncoding::Br,
|
||||
#[cfg(feature = "flate2")]
|
||||
"gzip" => ContentEncoding::Gzip,
|
||||
#[cfg(feature = "flate2")]
|
||||
"deflate" => ContentEncoding::Deflate,
|
||||
_ => ContentEncoding::Identity,
|
||||
}
|
||||
@ -202,7 +210,7 @@ impl fmt::Write for Writer {
|
||||
#[doc(hidden)]
|
||||
/// Reads a comma-delimited raw header into a Vec.
|
||||
pub fn from_comma_delimited<T: FromStr>(
|
||||
all: GetAll<HeaderValue>
|
||||
all: GetAll<HeaderValue>,
|
||||
) -> Result<Vec<T>, ParseError> {
|
||||
let mut result = Vec::new();
|
||||
for h in all {
|
||||
|
@ -7,7 +7,7 @@ use self::Charset::*;
|
||||
|
||||
/// A Mime charset.
|
||||
///
|
||||
/// The string representation is normalised to upper case.
|
||||
/// The string representation is normalized to upper case.
|
||||
///
|
||||
/// See [http://www.iana.org/assignments/character-sets/character-sets.xhtml][url].
|
||||
///
|
||||
|
@ -217,7 +217,7 @@ mod tests {
|
||||
for (path, target, code) in params {
|
||||
let req = app.prepare_request(TestRequest::with_uri(path).finish());
|
||||
let resp = app.run(req);
|
||||
let r = resp.as_response().unwrap();
|
||||
let r = resp.as_msg();
|
||||
assert_eq!(r.status(), code);
|
||||
if !target.is_empty() {
|
||||
assert_eq!(
|
||||
@ -260,7 +260,7 @@ mod tests {
|
||||
for (path, code) in params {
|
||||
let req = app.prepare_request(TestRequest::with_uri(path).finish());
|
||||
let resp = app.run(req);
|
||||
let r = resp.as_response().unwrap();
|
||||
let r = resp.as_msg();
|
||||
assert_eq!(r.status(), code);
|
||||
}
|
||||
}
|
||||
@ -351,7 +351,7 @@ mod tests {
|
||||
for (path, target, code) in params {
|
||||
let req = app.prepare_request(TestRequest::with_uri(path).finish());
|
||||
let resp = app.run(req);
|
||||
let r = resp.as_response().unwrap();
|
||||
let r = resp.as_msg();
|
||||
assert_eq!(r.status(), code);
|
||||
if !target.is_empty() {
|
||||
assert_eq!(
|
||||
@ -535,7 +535,7 @@ mod tests {
|
||||
for (path, target, code) in params {
|
||||
let req = app.prepare_request(TestRequest::with_uri(path).finish());
|
||||
let resp = app.run(req);
|
||||
let r = resp.as_response().unwrap();
|
||||
let r = resp.as_msg();
|
||||
assert_eq!(r.status(), code);
|
||||
if !target.is_empty() {
|
||||
assert_eq!(
|
||||
|
234
src/httpcodes.rs
234
src/httpcodes.rs
@ -1,213 +1,8 @@
|
||||
//! Basic http responses
|
||||
#![allow(non_upper_case_globals, deprecated)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
use http::StatusCode;
|
||||
|
||||
use body::Body;
|
||||
use error::Error;
|
||||
use handler::{Handler, Reply, Responder, RouteHandler};
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::{HttpResponse, HttpResponseBuilder};
|
||||
|
||||
#[deprecated(since = "0.5.0", note = "please use `HttpResponse::Ok()` instead")]
|
||||
pub const HttpOk: StaticResponse = StaticResponse(StatusCode::OK);
|
||||
#[deprecated(since = "0.5.0", note = "please use `HttpResponse::Created()` instead")]
|
||||
pub const HttpCreated: StaticResponse = StaticResponse(StatusCode::CREATED);
|
||||
#[deprecated(since = "0.5.0", note = "please use `HttpResponse::Accepted()` instead")]
|
||||
pub const HttpAccepted: StaticResponse = StaticResponse(StatusCode::ACCEPTED);
|
||||
#[deprecated(since = "0.5.0",
|
||||
note = "please use `HttpResponse::pNonAuthoritativeInformation()` instead")]
|
||||
pub const HttpNonAuthoritativeInformation: StaticResponse =
|
||||
StaticResponse(StatusCode::NON_AUTHORITATIVE_INFORMATION);
|
||||
#[deprecated(since = "0.5.0", note = "please use `HttpResponse::NoContent()` instead")]
|
||||
pub const HttpNoContent: StaticResponse = StaticResponse(StatusCode::NO_CONTENT);
|
||||
#[deprecated(since = "0.5.0",
|
||||
note = "please use `HttpResponse::ResetContent()` instead")]
|
||||
pub const HttpResetContent: StaticResponse = StaticResponse(StatusCode::RESET_CONTENT);
|
||||
#[deprecated(since = "0.5.0",
|
||||
note = "please use `HttpResponse::PartialContent()` instead")]
|
||||
pub const HttpPartialContent: StaticResponse =
|
||||
StaticResponse(StatusCode::PARTIAL_CONTENT);
|
||||
#[deprecated(since = "0.5.0", note = "please use `HttpResponse::MultiStatus()` instead")]
|
||||
pub const HttpMultiStatus: StaticResponse = StaticResponse(StatusCode::MULTI_STATUS);
|
||||
#[deprecated(since = "0.5.0",
|
||||
note = "please use `HttpResponse::AlreadyReported()` instead")]
|
||||
pub const HttpAlreadyReported: StaticResponse =
|
||||
StaticResponse(StatusCode::ALREADY_REPORTED);
|
||||
|
||||
#[deprecated(since = "0.5.0",
|
||||
note = "please use `HttpResponse::MultipleChoices()` instead")]
|
||||
pub const HttpMultipleChoices: StaticResponse =
|
||||
StaticResponse(StatusCode::MULTIPLE_CHOICES);
|
||||
#[deprecated(since = "0.5.0",
|
||||
note = "please use `HttpResponse::MovedPermanently()` instead")]
|
||||
pub const HttpMovedPermanently: StaticResponse =
|
||||
StaticResponse(StatusCode::MOVED_PERMANENTLY);
|
||||
#[deprecated(since = "0.5.0", note = "please use `HttpResponse::Found()` instead")]
|
||||
pub const HttpFound: StaticResponse = StaticResponse(StatusCode::FOUND);
|
||||
#[deprecated(since = "0.5.0", note = "please use `HttpResponse::SeeOther()` instead")]
|
||||
pub const HttpSeeOther: StaticResponse = StaticResponse(StatusCode::SEE_OTHER);
|
||||
#[deprecated(since = "0.5.0", note = "please use `HttpResponse::NotModified()` instead")]
|
||||
pub const HttpNotModified: StaticResponse = StaticResponse(StatusCode::NOT_MODIFIED);
|
||||
#[deprecated(since = "0.5.0", note = "please use `HttpResponse::UseProxy()` instead")]
|
||||
pub const HttpUseProxy: StaticResponse = StaticResponse(StatusCode::USE_PROXY);
|
||||
#[deprecated(since = "0.5.0",
|
||||
note = "please use `HttpResponse::TemporaryRedirect()` instead")]
|
||||
pub const HttpTemporaryRedirect: StaticResponse =
|
||||
StaticResponse(StatusCode::TEMPORARY_REDIRECT);
|
||||
#[deprecated(since = "0.5.0",
|
||||
note = "please use `HttpResponse::PermanentRedirect()` instead")]
|
||||
pub const HttpPermanentRedirect: StaticResponse =
|
||||
StaticResponse(StatusCode::PERMANENT_REDIRECT);
|
||||
|
||||
#[deprecated(since = "0.5.0", note = "please use `HttpResponse::BadRequest()` instead")]
|
||||
pub const HttpBadRequest: StaticResponse = StaticResponse(StatusCode::BAD_REQUEST);
|
||||
#[deprecated(since = "0.5.0",
|
||||
note = "please use `HttpResponse::Unauthorized()` instead")]
|
||||
pub const HttpUnauthorized: StaticResponse = StaticResponse(StatusCode::UNAUTHORIZED);
|
||||
#[deprecated(since = "0.5.0",
|
||||
note = "please use `HttpResponse::PaymentRequired()` instead")]
|
||||
pub const HttpPaymentRequired: StaticResponse =
|
||||
StaticResponse(StatusCode::PAYMENT_REQUIRED);
|
||||
#[deprecated(since = "0.5.0", note = "please use `HttpResponse::Forbidden()` instead")]
|
||||
pub const HttpForbidden: StaticResponse = StaticResponse(StatusCode::FORBIDDEN);
|
||||
#[deprecated(since = "0.5.0", note = "please use `HttpResponse::NotFound()` instead")]
|
||||
pub const HttpNotFound: StaticResponse = StaticResponse(StatusCode::NOT_FOUND);
|
||||
#[deprecated(since = "0.5.0",
|
||||
note = "please use `HttpResponse::MethodNotAllowed()` instead")]
|
||||
pub const HttpMethodNotAllowed: StaticResponse =
|
||||
StaticResponse(StatusCode::METHOD_NOT_ALLOWED);
|
||||
#[deprecated(since = "0.5.0",
|
||||
note = "please use `HttpResponse::NotAcceptable()` instead")]
|
||||
pub const HttpNotAcceptable: StaticResponse = StaticResponse(StatusCode::NOT_ACCEPTABLE);
|
||||
#[deprecated(since = "0.5.0",
|
||||
note = "please use `HttpResponse::ProxyAuthenticationRequired()` instead")]
|
||||
pub const HttpProxyAuthenticationRequired: StaticResponse =
|
||||
StaticResponse(StatusCode::PROXY_AUTHENTICATION_REQUIRED);
|
||||
#[deprecated(since = "0.5.0",
|
||||
note = "please use `HttpResponse::RequestTimeout()` instead")]
|
||||
pub const HttpRequestTimeout: StaticResponse =
|
||||
StaticResponse(StatusCode::REQUEST_TIMEOUT);
|
||||
#[deprecated(since = "0.5.0", note = "please use `HttpResponse::Conflict()` instead")]
|
||||
pub const HttpConflict: StaticResponse = StaticResponse(StatusCode::CONFLICT);
|
||||
#[deprecated(since = "0.5.0", note = "please use `HttpResponse::Gone()` instead")]
|
||||
pub const HttpGone: StaticResponse = StaticResponse(StatusCode::GONE);
|
||||
#[deprecated(since = "0.5.0",
|
||||
note = "please use `HttpResponse::LengthRequired()` instead")]
|
||||
pub const HttpLengthRequired: StaticResponse =
|
||||
StaticResponse(StatusCode::LENGTH_REQUIRED);
|
||||
#[deprecated(since = "0.5.0",
|
||||
note = "please use `HttpResponse::PreconditionFailed()` instead")]
|
||||
pub const HttpPreconditionFailed: StaticResponse =
|
||||
StaticResponse(StatusCode::PRECONDITION_FAILED);
|
||||
#[deprecated(since = "0.5.0",
|
||||
note = "please use `HttpResponse::PayloadTooLarge()` instead")]
|
||||
pub const HttpPayloadTooLarge: StaticResponse =
|
||||
StaticResponse(StatusCode::PAYLOAD_TOO_LARGE);
|
||||
#[deprecated(since = "0.5.0", note = "please use `HttpResponse::UriTooLong()` instead")]
|
||||
pub const HttpUriTooLong: StaticResponse = StaticResponse(StatusCode::URI_TOO_LONG);
|
||||
#[deprecated(since = "0.5.0",
|
||||
note = "please use `HttpResponse::UnsupportedMediaType()` instead")]
|
||||
pub const HttpUnsupportedMediaType: StaticResponse =
|
||||
StaticResponse(StatusCode::UNSUPPORTED_MEDIA_TYPE);
|
||||
#[deprecated(since = "0.5.0",
|
||||
note = "please use `HttpResponse::RangeNotSatisfiable()` instead")]
|
||||
pub const HttpRangeNotSatisfiable: StaticResponse =
|
||||
StaticResponse(StatusCode::RANGE_NOT_SATISFIABLE);
|
||||
#[deprecated(since = "0.5.0",
|
||||
note = "please use `HttpResponse::ExpectationFailed()` instead")]
|
||||
pub const HttpExpectationFailed: StaticResponse =
|
||||
StaticResponse(StatusCode::EXPECTATION_FAILED);
|
||||
|
||||
#[deprecated(since = "0.5.0",
|
||||
note = "please use `HttpResponse::InternalServerError()` instead")]
|
||||
pub const HttpInternalServerError: StaticResponse =
|
||||
StaticResponse(StatusCode::INTERNAL_SERVER_ERROR);
|
||||
#[deprecated(since = "0.5.0",
|
||||
note = "please use `HttpResponse::NotImplemented()` instead")]
|
||||
pub const HttpNotImplemented: StaticResponse =
|
||||
StaticResponse(StatusCode::NOT_IMPLEMENTED);
|
||||
#[deprecated(since = "0.5.0", note = "please use `HttpResponse::BadGateway()` instead")]
|
||||
pub const HttpBadGateway: StaticResponse = StaticResponse(StatusCode::BAD_GATEWAY);
|
||||
#[deprecated(since = "0.5.0",
|
||||
note = "please use `HttpResponse::ServiceUnavailable()` instead")]
|
||||
pub const HttpServiceUnavailable: StaticResponse =
|
||||
StaticResponse(StatusCode::SERVICE_UNAVAILABLE);
|
||||
#[deprecated(since = "0.5.0",
|
||||
note = "please use `HttpResponse::GatewayTimeout()` instead")]
|
||||
pub const HttpGatewayTimeout: StaticResponse =
|
||||
StaticResponse(StatusCode::GATEWAY_TIMEOUT);
|
||||
#[deprecated(since = "0.5.0",
|
||||
note = "please use `HttpResponse::VersionNotSupported()` instead")]
|
||||
pub const HttpVersionNotSupported: StaticResponse =
|
||||
StaticResponse(StatusCode::HTTP_VERSION_NOT_SUPPORTED);
|
||||
#[deprecated(since = "0.5.0",
|
||||
note = "please use `HttpResponse::VariantAlsoNegotiates()` instead")]
|
||||
pub const HttpVariantAlsoNegotiates: StaticResponse =
|
||||
StaticResponse(StatusCode::VARIANT_ALSO_NEGOTIATES);
|
||||
#[deprecated(since = "0.5.0",
|
||||
note = "please use `HttpResponse::InsufficientStorage()` instead")]
|
||||
pub const HttpInsufficientStorage: StaticResponse =
|
||||
StaticResponse(StatusCode::INSUFFICIENT_STORAGE);
|
||||
#[deprecated(since = "0.5.0",
|
||||
note = "please use `HttpResponse::LoopDetected()` instead")]
|
||||
pub const HttpLoopDetected: StaticResponse = StaticResponse(StatusCode::LOOP_DETECTED);
|
||||
|
||||
#[deprecated(since = "0.5.0", note = "please use `HttpResponse` instead")]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct StaticResponse(StatusCode);
|
||||
|
||||
impl StaticResponse {
|
||||
pub fn build(&self) -> HttpResponseBuilder {
|
||||
HttpResponse::build(self.0)
|
||||
}
|
||||
pub fn build_from<S>(&self, req: &HttpRequest<S>) -> HttpResponseBuilder {
|
||||
req.build_response(self.0)
|
||||
}
|
||||
pub fn with_reason(self, reason: &'static str) -> HttpResponse {
|
||||
let mut resp = HttpResponse::new(self.0);
|
||||
resp.set_reason(reason);
|
||||
resp
|
||||
}
|
||||
pub fn with_body<B: Into<Body>>(self, body: B) -> HttpResponse {
|
||||
HttpResponse::with_body(self.0, body.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Handler<S> for StaticResponse {
|
||||
type Result = HttpResponse;
|
||||
|
||||
fn handle(&mut self, _: HttpRequest<S>) -> HttpResponse {
|
||||
HttpResponse::new(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> RouteHandler<S> for StaticResponse {
|
||||
fn handle(&mut self, _: HttpRequest<S>) -> Reply {
|
||||
Reply::response(HttpResponse::new(self.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Responder for StaticResponse {
|
||||
type Item = HttpResponse;
|
||||
type Error = Error;
|
||||
|
||||
fn respond_to(self, _: HttpRequest) -> Result<HttpResponse, Error> {
|
||||
Ok(self.build().finish())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StaticResponse> for HttpResponse {
|
||||
fn from(st: StaticResponse) -> Self {
|
||||
HttpResponse::new(st.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StaticResponse> for Reply {
|
||||
fn from(st: StaticResponse) -> Self {
|
||||
HttpResponse::new(st.0).into()
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! STATIC_RESP {
|
||||
($name:ident, $status:expr) => {
|
||||
#[allow(non_snake_case)]
|
||||
@ -286,34 +81,13 @@ impl HttpResponse {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Body, HttpBadRequest, HttpOk, HttpResponse};
|
||||
use body::Body;
|
||||
use http::StatusCode;
|
||||
use httpresponse::HttpResponse;
|
||||
|
||||
#[test]
|
||||
fn test_build() {
|
||||
let resp = HttpOk.build().body(Body::Empty);
|
||||
let resp = HttpResponse::Ok().body(Body::Empty);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_response() {
|
||||
let resp: HttpResponse = HttpOk.into();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from() {
|
||||
let resp: HttpResponse = HttpOk.into();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_reason() {
|
||||
let resp: HttpResponse = HttpOk.into();
|
||||
assert_eq!(resp.reason(), "OK");
|
||||
|
||||
let resp = HttpBadRequest.with_reason("test");
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
assert_eq!(resp.reason(), "test");
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use encoding::EncodingRef;
|
||||
use encoding::all::UTF_8;
|
||||
use encoding::label::encoding_from_whatwg_label;
|
||||
use encoding::types::{DecoderTrap, Encoding};
|
||||
use encoding::EncodingRef;
|
||||
use futures::{Future, Poll, Stream};
|
||||
use http::{header, HeaderMap};
|
||||
use http_range::HttpRange;
|
||||
@ -425,8 +425,8 @@ where
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use encoding::Encoding;
|
||||
use encoding::all::ISO_8859_2;
|
||||
use encoding::Encoding;
|
||||
use futures::Async;
|
||||
use http::{Method, Uri, Version};
|
||||
use httprequest::HttpRequest;
|
||||
|
@ -1,19 +1,20 @@
|
||||
//! HTTP Request message related code.
|
||||
use bytes::Bytes;
|
||||
use cookie::Cookie;
|
||||
use failure;
|
||||
use futures::future::{result, FutureResult};
|
||||
use futures::{Async, Poll, Stream};
|
||||
use futures_cpupool::CpuPool;
|
||||
use http::{header, Extensions, HeaderMap, Method, StatusCode, Uri, Version};
|
||||
#![cfg_attr(feature = "cargo-clippy", allow(transmute_ptr_to_ptr))]
|
||||
use std::net::SocketAddr;
|
||||
use std::rc::Rc;
|
||||
use std::{cmp, fmt, io, mem, str};
|
||||
|
||||
use bytes::Bytes;
|
||||
use cookie::Cookie;
|
||||
use failure;
|
||||
use futures::{Async, Poll, Stream};
|
||||
use futures_cpupool::CpuPool;
|
||||
use http::{header, Extensions, HeaderMap, Method, StatusCode, Uri, Version};
|
||||
use tokio_io::AsyncRead;
|
||||
use url::{form_urlencoded, Url};
|
||||
|
||||
use body::Body;
|
||||
use error::{CookieParseError, Error, PayloadError, UrlGenerationError};
|
||||
use error::{CookieParseError, PayloadError, UrlGenerationError};
|
||||
use handler::FromRequest;
|
||||
use httpmessage::HttpMessage;
|
||||
use httpresponse::{HttpResponse, HttpResponseBuilder};
|
||||
@ -24,19 +25,27 @@ use router::{Resource, Router};
|
||||
use server::helpers::SharedHttpInnerMessage;
|
||||
use uri::Url as InnerUrl;
|
||||
|
||||
bitflags! {
|
||||
pub(crate) struct MessageFlags: u8 {
|
||||
const QUERY = 0b0000_0001;
|
||||
const KEEPALIVE = 0b0000_0010;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HttpInnerMessage {
|
||||
pub version: Version,
|
||||
pub method: Method,
|
||||
pub(crate) url: InnerUrl,
|
||||
pub(crate) flags: MessageFlags,
|
||||
pub headers: HeaderMap,
|
||||
pub extensions: Extensions,
|
||||
pub params: Params<'static>,
|
||||
pub cookies: Option<Vec<Cookie<'static>>>,
|
||||
pub query: Params<'static>,
|
||||
pub query_loaded: bool,
|
||||
pub addr: Option<SocketAddr>,
|
||||
pub payload: Option<Payload>,
|
||||
pub info: Option<ConnectionInfo<'static>>,
|
||||
pub prefix: u16,
|
||||
resource: RouterResource,
|
||||
}
|
||||
|
||||
@ -53,14 +62,15 @@ impl Default for HttpInnerMessage {
|
||||
url: InnerUrl::default(),
|
||||
version: Version::HTTP_11,
|
||||
headers: HeaderMap::with_capacity(16),
|
||||
flags: MessageFlags::empty(),
|
||||
params: Params::new(),
|
||||
query: Params::new(),
|
||||
query_loaded: false,
|
||||
cookies: None,
|
||||
addr: None,
|
||||
cookies: None,
|
||||
payload: None,
|
||||
extensions: Extensions::new(),
|
||||
info: None,
|
||||
prefix: 0,
|
||||
resource: RouterResource::Notset,
|
||||
}
|
||||
}
|
||||
@ -70,20 +80,7 @@ impl HttpInnerMessage {
|
||||
/// Checks if a connection should be kept alive.
|
||||
#[inline]
|
||||
pub fn keep_alive(&self) -> bool {
|
||||
if let Some(conn) = self.headers.get(header::CONNECTION) {
|
||||
if let Ok(conn) = conn.to_str() {
|
||||
if self.version == Version::HTTP_10 && conn.contains("keep-alive") {
|
||||
true
|
||||
} else {
|
||||
self.version == Version::HTTP_11
|
||||
&& !(conn.contains("close") || conn.contains("upgrade"))
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
self.version != Version::HTTP_10
|
||||
}
|
||||
self.flags.contains(MessageFlags::KEEPALIVE)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@ -91,12 +88,12 @@ impl HttpInnerMessage {
|
||||
self.headers.clear();
|
||||
self.extensions.clear();
|
||||
self.params.clear();
|
||||
self.query.clear();
|
||||
self.query_loaded = false;
|
||||
self.cookies = None;
|
||||
self.addr = None;
|
||||
self.info = None;
|
||||
self.flags = MessageFlags::empty();
|
||||
self.cookies = None;
|
||||
self.payload = None;
|
||||
self.prefix = 0;
|
||||
self.resource = RouterResource::Notset;
|
||||
}
|
||||
}
|
||||
@ -125,11 +122,12 @@ impl HttpRequest<()> {
|
||||
payload,
|
||||
params: Params::new(),
|
||||
query: Params::new(),
|
||||
query_loaded: false,
|
||||
extensions: Extensions::new(),
|
||||
cookies: None,
|
||||
addr: None,
|
||||
extensions: Extensions::new(),
|
||||
info: None,
|
||||
prefix: 0,
|
||||
flags: MessageFlags::empty(),
|
||||
resource: RouterResource::Notset,
|
||||
}),
|
||||
None,
|
||||
@ -197,18 +195,11 @@ impl<S> HttpRequest<S> {
|
||||
|
||||
/// Request extensions
|
||||
#[inline]
|
||||
pub fn extensions(&mut self) -> &mut Extensions {
|
||||
&mut self.as_mut().extensions
|
||||
}
|
||||
|
||||
/// Request extensions
|
||||
#[inline]
|
||||
#[doc(hidden)]
|
||||
pub fn extensions_ro(&self) -> &Extensions {
|
||||
pub fn extensions(&self) -> &Extensions {
|
||||
&self.as_ref().extensions
|
||||
}
|
||||
|
||||
/// Mutable refernece to a the request's extensions
|
||||
/// Mutable reference to a the request's extensions
|
||||
#[inline]
|
||||
pub fn extensions_mut(&mut self) -> &mut Extensions {
|
||||
&mut self.as_mut().extensions
|
||||
@ -243,12 +234,13 @@ impl<S> HttpRequest<S> {
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn prefix_len(&self) -> usize {
|
||||
if let Some(router) = self.router() {
|
||||
router.prefix().len()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
pub fn prefix_len(&self) -> u16 {
|
||||
self.as_ref().prefix as u16
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn set_prefix_len(&mut self, len: u16) {
|
||||
self.as_mut().prefix = len;
|
||||
}
|
||||
|
||||
/// Read the Request Uri.
|
||||
@ -257,16 +249,6 @@ impl<S> HttpRequest<S> {
|
||||
self.as_ref().url.uri()
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[deprecated(since = "0.5.3")]
|
||||
/// Returns mutable the Request Uri.
|
||||
///
|
||||
/// This might be useful for middlewares, e.g. path normalization.
|
||||
#[inline]
|
||||
pub fn uri_mut(&mut self) -> &mut Uri {
|
||||
self.as_mut().url.uri_mut()
|
||||
}
|
||||
|
||||
/// Read the Request method.
|
||||
#[inline]
|
||||
pub fn method(&self) -> &Method {
|
||||
@ -324,7 +306,7 @@ impl<S> HttpRequest<S> {
|
||||
/// }
|
||||
/// ```
|
||||
pub fn url_for<U, I>(
|
||||
&self, name: &str, elements: U
|
||||
&self, name: &str, elements: U,
|
||||
) -> Result<Url, UrlGenerationError>
|
||||
where
|
||||
U: IntoIterator<Item = I>,
|
||||
@ -377,22 +359,25 @@ impl<S> HttpRequest<S> {
|
||||
/// To get client connection information `connection_info()` method should
|
||||
/// be used.
|
||||
#[inline]
|
||||
pub fn peer_addr(&self) -> Option<&SocketAddr> {
|
||||
self.as_ref().addr.as_ref()
|
||||
pub fn peer_addr(&self) -> Option<SocketAddr> {
|
||||
self.as_ref().addr
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn set_peer_addr(&mut self, addr: Option<SocketAddr>) {
|
||||
self.as_mut().addr = addr
|
||||
self.as_mut().addr = addr;
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[deprecated(since = "0.6.0", note = "please use `Query<T>` extractor")]
|
||||
/// Get a reference to the Params object.
|
||||
/// Params is a container for url query parameters.
|
||||
pub fn query(&self) -> &Params {
|
||||
if !self.as_ref().query_loaded {
|
||||
if !self.as_ref().flags.contains(MessageFlags::QUERY) {
|
||||
self.as_mut().flags.insert(MessageFlags::QUERY);
|
||||
let params: &mut Params =
|
||||
unsafe { mem::transmute(&mut self.as_mut().query) };
|
||||
self.as_mut().query_loaded = true;
|
||||
params.clear();
|
||||
for (key, val) in form_urlencoded::parse(self.query_string().as_ref()) {
|
||||
params.add(key, val);
|
||||
}
|
||||
@ -425,9 +410,9 @@ impl<S> HttpRequest<S> {
|
||||
}
|
||||
}
|
||||
}
|
||||
msg.cookies = Some(cookies)
|
||||
msg.cookies = Some(cookies);
|
||||
}
|
||||
Ok(self.as_ref().cookies.as_ref().unwrap())
|
||||
Ok(&self.as_ref().cookies.as_ref().unwrap())
|
||||
}
|
||||
|
||||
/// Return request cookie.
|
||||
@ -461,7 +446,7 @@ impl<S> HttpRequest<S> {
|
||||
|
||||
/// Checks if a connection should be kept alive.
|
||||
pub fn keep_alive(&self) -> bool {
|
||||
self.as_ref().keep_alive()
|
||||
self.as_ref().flags.contains(MessageFlags::KEEPALIVE)
|
||||
}
|
||||
|
||||
/// Check if request requires connection upgrade
|
||||
@ -515,13 +500,13 @@ impl<S> Clone for HttpRequest<S> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static> FromRequest<S> for HttpRequest<S> {
|
||||
impl<S> FromRequest<S> for HttpRequest<S> {
|
||||
type Config = ();
|
||||
type Result = FutureResult<Self, Error>;
|
||||
type Result = Self;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
|
||||
result(Ok(req.clone()))
|
||||
req.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@ -603,10 +588,7 @@ impl<S> fmt::Debug for HttpRequest<S> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(deprecated)]
|
||||
|
||||
use super::*;
|
||||
use http::{HttpTryFrom, Uri};
|
||||
use resource::ResourceHandler;
|
||||
use router::Resource;
|
||||
use server::ServerSettings;
|
||||
@ -619,14 +601,6 @@ mod tests {
|
||||
assert!(dbg.contains("HttpRequest"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_uri_mut() {
|
||||
let mut req = HttpRequest::default();
|
||||
assert_eq!(req.path(), "/");
|
||||
*req.uri_mut() = Uri::try_from("/test").unwrap();
|
||||
assert_eq!(req.path(), "/test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_request_cookies() {
|
||||
let req = HttpRequest::default();
|
||||
@ -690,12 +664,10 @@ mod tests {
|
||||
|
||||
let mut resource = ResourceHandler::<()>::default();
|
||||
resource.name("index");
|
||||
let routes = vec![
|
||||
(
|
||||
Resource::new("index", "/user/{name}.{ext}"),
|
||||
Some(resource),
|
||||
),
|
||||
];
|
||||
let routes = vec![(
|
||||
Resource::new("index", "/user/{name}.{ext}"),
|
||||
Some(resource),
|
||||
)];
|
||||
let (router, _) = Router::new("/", ServerSettings::default(), routes);
|
||||
assert!(router.has_route("/user/test.html"));
|
||||
assert!(!router.has_route("/test/unknown"));
|
||||
@ -724,12 +696,10 @@ mod tests {
|
||||
|
||||
let mut resource = ResourceHandler::<()>::default();
|
||||
resource.name("index");
|
||||
let routes = vec![
|
||||
(
|
||||
Resource::new("index", "/user/{name}.{ext}"),
|
||||
Some(resource),
|
||||
),
|
||||
];
|
||||
let routes = vec![(
|
||||
Resource::new("index", "/user/{name}.{ext}"),
|
||||
Some(resource),
|
||||
)];
|
||||
let (router, _) = Router::new("/prefix/", ServerSettings::default(), routes);
|
||||
assert!(router.has_route("/user/test.html"));
|
||||
assert!(!router.has_route("/prefix/user/test.html"));
|
||||
@ -748,12 +718,10 @@ mod tests {
|
||||
|
||||
let mut resource = ResourceHandler::<()>::default();
|
||||
resource.name("index");
|
||||
let routes = vec![
|
||||
(
|
||||
Resource::external("youtube", "https://youtube.com/watch/{video_id}"),
|
||||
None,
|
||||
),
|
||||
];
|
||||
let routes = vec![(
|
||||
Resource::external("youtube", "https://youtube.com/watch/{video_id}"),
|
||||
None,
|
||||
)];
|
||||
let (router, _) = Router::new::<()>("", ServerSettings::default(), routes);
|
||||
assert!(!router.has_route("https://youtube.com/watch/unknown"));
|
||||
|
||||
|
@ -607,7 +607,7 @@ impl HttpResponseBuilder {
|
||||
#[inline]
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(borrowed_box))]
|
||||
fn parts<'a>(
|
||||
parts: &'a mut Option<Box<InnerHttpResponse>>, err: &Option<HttpError>
|
||||
parts: &'a mut Option<Box<InnerHttpResponse>>, err: &Option<HttpError>,
|
||||
) -> Option<&'a mut Box<InnerHttpResponse>> {
|
||||
if err.is_some() {
|
||||
return None;
|
||||
@ -636,7 +636,7 @@ impl Responder for HttpResponseBuilder {
|
||||
type Error = Error;
|
||||
|
||||
#[inline]
|
||||
fn respond_to(mut self, _: HttpRequest) -> Result<HttpResponse, Error> {
|
||||
fn respond_to<S>(mut self, _: &HttpRequest<S>) -> Result<HttpResponse, Error> {
|
||||
Ok(self.finish())
|
||||
}
|
||||
}
|
||||
@ -653,7 +653,7 @@ impl Responder for &'static str {
|
||||
type Item = HttpResponse;
|
||||
type Error = Error;
|
||||
|
||||
fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, Error> {
|
||||
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
|
||||
Ok(req.build_response(StatusCode::OK)
|
||||
.content_type("text/plain; charset=utf-8")
|
||||
.body(self))
|
||||
@ -672,7 +672,7 @@ impl Responder for &'static [u8] {
|
||||
type Item = HttpResponse;
|
||||
type Error = Error;
|
||||
|
||||
fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, Error> {
|
||||
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
|
||||
Ok(req.build_response(StatusCode::OK)
|
||||
.content_type("application/octet-stream")
|
||||
.body(self))
|
||||
@ -691,7 +691,7 @@ impl Responder for String {
|
||||
type Item = HttpResponse;
|
||||
type Error = Error;
|
||||
|
||||
fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, Error> {
|
||||
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
|
||||
Ok(req.build_response(StatusCode::OK)
|
||||
.content_type("text/plain; charset=utf-8")
|
||||
.body(self))
|
||||
@ -710,7 +710,7 @@ impl<'a> Responder for &'a String {
|
||||
type Item = HttpResponse;
|
||||
type Error = Error;
|
||||
|
||||
fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, Error> {
|
||||
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
|
||||
Ok(req.build_response(StatusCode::OK)
|
||||
.content_type("text/plain; charset=utf-8")
|
||||
.body(self))
|
||||
@ -729,7 +729,7 @@ impl Responder for Bytes {
|
||||
type Item = HttpResponse;
|
||||
type Error = Error;
|
||||
|
||||
fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, Error> {
|
||||
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
|
||||
Ok(req.build_response(StatusCode::OK)
|
||||
.content_type("application/octet-stream")
|
||||
.body(self))
|
||||
@ -748,7 +748,7 @@ impl Responder for BytesMut {
|
||||
type Item = HttpResponse;
|
||||
type Error = Error;
|
||||
|
||||
fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, Error> {
|
||||
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
|
||||
Ok(req.build_response(StatusCode::OK)
|
||||
.content_type("application/octet-stream")
|
||||
.body(self))
|
||||
@ -829,7 +829,7 @@ impl HttpResponsePool {
|
||||
|
||||
#[inline]
|
||||
pub fn get_builder(
|
||||
pool: &Rc<UnsafeCell<HttpResponsePool>>, status: StatusCode
|
||||
pool: &Rc<UnsafeCell<HttpResponsePool>>, status: StatusCode,
|
||||
) -> HttpResponseBuilder {
|
||||
let p = unsafe { &mut *pool.as_ref().get() };
|
||||
if let Some(mut msg) = p.0.pop_front() {
|
||||
@ -853,7 +853,7 @@ impl HttpResponsePool {
|
||||
|
||||
#[inline]
|
||||
pub fn get_response(
|
||||
pool: &Rc<UnsafeCell<HttpResponsePool>>, status: StatusCode, body: Body
|
||||
pool: &Rc<UnsafeCell<HttpResponsePool>>, status: StatusCode, body: Body,
|
||||
) -> HttpResponse {
|
||||
let p = unsafe { &mut *pool.as_ref().get() };
|
||||
if let Some(mut msg) = p.0.pop_front() {
|
||||
@ -879,7 +879,7 @@ impl HttpResponsePool {
|
||||
#[inline(always)]
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(boxed_local, inline_always))]
|
||||
fn release(
|
||||
pool: &Rc<UnsafeCell<HttpResponsePool>>, mut inner: Box<InnerHttpResponse>
|
||||
pool: &Rc<UnsafeCell<HttpResponsePool>>, mut inner: Box<InnerHttpResponse>,
|
||||
) {
|
||||
let pool = unsafe { &mut *pool.as_ref().get() };
|
||||
if pool.0.len() < 128 {
|
||||
@ -1057,7 +1057,7 @@ mod tests {
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(resp.body().binary().unwrap(), &Binary::from("test"));
|
||||
|
||||
let resp: HttpResponse = "test".respond_to(req.clone()).ok().unwrap();
|
||||
let resp: HttpResponse = "test".respond_to(&req).ok().unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
@ -1078,7 +1078,7 @@ mod tests {
|
||||
&Binary::from(b"test".as_ref())
|
||||
);
|
||||
|
||||
let resp: HttpResponse = b"test".as_ref().respond_to(req.clone()).ok().unwrap();
|
||||
let resp: HttpResponse = b"test".as_ref().respond_to(&req).ok().unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
@ -1102,11 +1102,7 @@ mod tests {
|
||||
&Binary::from("test".to_owned())
|
||||
);
|
||||
|
||||
let resp: HttpResponse = "test"
|
||||
.to_owned()
|
||||
.respond_to(req.clone())
|
||||
.ok()
|
||||
.unwrap();
|
||||
let resp: HttpResponse = "test".to_owned().respond_to(&req).ok().unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
@ -1130,10 +1126,7 @@ mod tests {
|
||||
&Binary::from(&"test".to_owned())
|
||||
);
|
||||
|
||||
let resp: HttpResponse = (&"test".to_owned())
|
||||
.respond_to(req.clone())
|
||||
.ok()
|
||||
.unwrap();
|
||||
let resp: HttpResponse = (&"test".to_owned()).respond_to(&req).ok().unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
@ -1159,7 +1152,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let b = Bytes::from_static(b"test");
|
||||
let resp: HttpResponse = b.respond_to(req.clone()).ok().unwrap();
|
||||
let resp: HttpResponse = b.respond_to(&req).ok().unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
@ -1185,7 +1178,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let b = BytesMut::from("test");
|
||||
let resp: HttpResponse = b.respond_to(req.clone()).ok().unwrap();
|
||||
let resp: HttpResponse = b.respond_to(&req).ok().unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
|
22
src/json.rs
22
src/json.rs
@ -6,8 +6,8 @@ use std::ops::{Deref, DerefMut};
|
||||
use std::rc::Rc;
|
||||
|
||||
use mime;
|
||||
use serde::Serialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use serde_json;
|
||||
|
||||
use error::{Error, JsonPayloadError, PayloadError};
|
||||
@ -118,7 +118,7 @@ impl<T: Serialize> Responder for Json<T> {
|
||||
type Item = HttpResponse;
|
||||
type Error = Error;
|
||||
|
||||
fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, Error> {
|
||||
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
|
||||
let body = serde_json::to_string(&self.0)?;
|
||||
|
||||
Ok(req.build_response(StatusCode::OK)
|
||||
@ -351,7 +351,7 @@ mod tests {
|
||||
let json = Json(MyObject {
|
||||
name: "test".to_owned(),
|
||||
});
|
||||
let resp = json.respond_to(HttpRequest::default()).unwrap();
|
||||
let resp = json.respond_to(&HttpRequest::default()).unwrap();
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
"application/json"
|
||||
@ -417,13 +417,7 @@ mod tests {
|
||||
let mut handler = With::new(|data: Json<MyObject>| data, cfg);
|
||||
|
||||
let req = HttpRequest::default();
|
||||
let err = handler
|
||||
.handle(req)
|
||||
.as_response()
|
||||
.unwrap()
|
||||
.error()
|
||||
.is_some();
|
||||
assert!(err);
|
||||
assert!(handler.handle(req).as_err().is_some());
|
||||
|
||||
let mut req = HttpRequest::default();
|
||||
req.headers_mut().insert(
|
||||
@ -436,12 +430,6 @@ mod tests {
|
||||
);
|
||||
req.payload_mut()
|
||||
.unread_data(Bytes::from_static(b"{\"name\": \"test\"}"));
|
||||
let ok = handler
|
||||
.handle(req)
|
||||
.as_response()
|
||||
.unwrap()
|
||||
.error()
|
||||
.is_none();
|
||||
assert!(ok)
|
||||
assert!(handler.handle(req).as_err().is_none())
|
||||
}
|
||||
}
|
||||
|
25
src/lib.rs
25
src/lib.rs
@ -44,7 +44,7 @@
|
||||
//! * [HttpRequest](struct.HttpRequest.html) and
|
||||
//! [HttpResponse](struct.HttpResponse.html): These structs
|
||||
//! represent HTTP requests and responses and expose various methods
|
||||
//! for inspecting, creating and otherwise utilising them.
|
||||
//! for inspecting, creating and otherwise utilizing them.
|
||||
//!
|
||||
//! ## Features
|
||||
//!
|
||||
@ -59,13 +59,15 @@
|
||||
//! * SSL support with OpenSSL or `native-tls`
|
||||
//! * Middlewares (`Logger`, `Session`, `CORS`, `CSRF`, `DefaultHeaders`)
|
||||
//! * Built on top of [Actix actor framework](https://github.com/actix/actix)
|
||||
//! * Supported Rust version: 1.21 or later
|
||||
//! * Supported Rust version: 1.24 or later
|
||||
|
||||
#![cfg_attr(actix_nightly, feature(
|
||||
specialization, // for impl ErrorResponse for std::error::Error
|
||||
))]
|
||||
#![cfg_attr(feature = "cargo-clippy",
|
||||
allow(decimal_literal_representation, suspicious_arithmetic_impl))]
|
||||
#![cfg_attr(
|
||||
feature = "cargo-clippy",
|
||||
allow(decimal_literal_representation, suspicious_arithmetic_impl)
|
||||
)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
@ -95,6 +97,7 @@ extern crate mime_guess;
|
||||
extern crate mio;
|
||||
extern crate net2;
|
||||
extern crate rand;
|
||||
extern crate slab;
|
||||
extern crate tokio_core;
|
||||
extern crate tokio_io;
|
||||
extern crate url;
|
||||
@ -103,6 +106,7 @@ extern crate serde;
|
||||
#[cfg(feature = "brotli")]
|
||||
extern crate brotli2;
|
||||
extern crate encoding;
|
||||
#[cfg(feature = "flate2")]
|
||||
extern crate flate2;
|
||||
extern crate h2 as http2;
|
||||
extern crate num_cpus;
|
||||
@ -135,6 +139,7 @@ mod extractor;
|
||||
mod handler;
|
||||
mod header;
|
||||
mod helpers;
|
||||
mod httpcodes;
|
||||
mod httpmessage;
|
||||
mod httprequest;
|
||||
mod httpresponse;
|
||||
@ -146,6 +151,7 @@ mod pipeline;
|
||||
mod resource;
|
||||
mod route;
|
||||
mod router;
|
||||
mod scope;
|
||||
mod uri;
|
||||
mod with;
|
||||
|
||||
@ -168,13 +174,8 @@ pub use httpmessage::HttpMessage;
|
||||
pub use httprequest::HttpRequest;
|
||||
pub use httpresponse::HttpResponse;
|
||||
pub use json::Json;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod httpcodes;
|
||||
|
||||
#[doc(hidden)]
|
||||
#[allow(deprecated)]
|
||||
pub use application::Application;
|
||||
pub use scope::Scope;
|
||||
pub use ws::WsWriter;
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
pub(crate) const HAS_OPENSSL: bool = true;
|
||||
@ -200,7 +201,7 @@ pub mod dev {
|
||||
pub use body::BodyStream;
|
||||
pub use context::Drain;
|
||||
pub use extractor::{FormConfig, PayloadConfig};
|
||||
pub use handler::{Handler, Reply};
|
||||
pub use handler::{AsyncResult, Handler};
|
||||
pub use httpmessage::{MessageBody, UrlEncoded};
|
||||
pub use httpresponse::HttpResponseBuilder;
|
||||
pub use info::ConnectionInfo;
|
||||
|
@ -64,26 +64,36 @@ use resource::ResourceHandler;
|
||||
#[derive(Debug, Fail)]
|
||||
pub enum CorsError {
|
||||
/// The HTTP request header `Origin` is required but was not provided
|
||||
#[fail(display = "The HTTP request header `Origin` is required but was not provided")]
|
||||
#[fail(
|
||||
display = "The HTTP request header `Origin` is required but was not provided"
|
||||
)]
|
||||
MissingOrigin,
|
||||
/// The HTTP request header `Origin` could not be parsed correctly.
|
||||
#[fail(display = "The HTTP request header `Origin` could not be parsed correctly.")]
|
||||
BadOrigin,
|
||||
/// The request header `Access-Control-Request-Method` is required but is
|
||||
/// missing
|
||||
#[fail(display = "The request header `Access-Control-Request-Method` is required but is missing")]
|
||||
#[fail(
|
||||
display = "The request header `Access-Control-Request-Method` is required but is missing"
|
||||
)]
|
||||
MissingRequestMethod,
|
||||
/// The request header `Access-Control-Request-Method` has an invalid value
|
||||
#[fail(display = "The request header `Access-Control-Request-Method` has an invalid value")]
|
||||
#[fail(
|
||||
display = "The request header `Access-Control-Request-Method` has an invalid value"
|
||||
)]
|
||||
BadRequestMethod,
|
||||
/// The request header `Access-Control-Request-Headers` has an invalid
|
||||
/// value
|
||||
#[fail(display = "The request header `Access-Control-Request-Headers` has an invalid value")]
|
||||
#[fail(
|
||||
display = "The request header `Access-Control-Request-Headers` has an invalid value"
|
||||
)]
|
||||
BadRequestHeaders,
|
||||
/// The request header `Access-Control-Request-Headers` is required but is
|
||||
/// missing.
|
||||
#[fail(display = "The request header `Access-Control-Request-Headers` is required but is
|
||||
missing")]
|
||||
#[fail(
|
||||
display = "The request header `Access-Control-Request-Headers` is required but is
|
||||
missing"
|
||||
)]
|
||||
MissingRequestHeaders,
|
||||
/// Origin is not allowed to make this request
|
||||
#[fail(display = "Origin is not allowed to make this request")]
|
||||
@ -292,7 +302,7 @@ impl Cors {
|
||||
}
|
||||
|
||||
fn validate_allowed_method<S>(
|
||||
&self, req: &mut HttpRequest<S>
|
||||
&self, req: &mut HttpRequest<S>,
|
||||
) -> Result<(), CorsError> {
|
||||
if let Some(hdr) = req.headers()
|
||||
.get(header::ACCESS_CONTROL_REQUEST_METHOD)
|
||||
@ -313,7 +323,7 @@ impl Cors {
|
||||
}
|
||||
|
||||
fn validate_allowed_headers<S>(
|
||||
&self, req: &mut HttpRequest<S>
|
||||
&self, req: &mut HttpRequest<S>,
|
||||
) -> Result<(), CorsError> {
|
||||
match self.inner.headers {
|
||||
AllOrSome::All => Ok(()),
|
||||
@ -419,7 +429,7 @@ impl<S> Middleware<S> for Cors {
|
||||
}
|
||||
|
||||
fn response(
|
||||
&self, req: &mut HttpRequest<S>, mut resp: HttpResponse
|
||||
&self, req: &mut HttpRequest<S>, mut resp: HttpResponse,
|
||||
) -> Result<Response> {
|
||||
match self.inner.origins {
|
||||
AllOrSome::All => {
|
||||
@ -506,7 +516,7 @@ pub struct CorsBuilder<S = ()> {
|
||||
}
|
||||
|
||||
fn cors<'a>(
|
||||
parts: &'a mut Option<Inner>, err: &Option<http::Error>
|
||||
parts: &'a mut Option<Inner>, err: &Option<http::Error>,
|
||||
) -> Option<&'a mut Inner> {
|
||||
if err.is_some() {
|
||||
return None;
|
||||
@ -815,7 +825,7 @@ impl<S: 'static> CorsBuilder<S> {
|
||||
if let AllOrSome::Some(ref origins) = cors.origins {
|
||||
let s = origins
|
||||
.iter()
|
||||
.fold(String::new(), |s, v| s + &format!("{}", v));
|
||||
.fold(String::new(), |s, v| s + &v.to_string());
|
||||
cors.origins_str = Some(HeaderValue::try_from(s.as_str()).unwrap());
|
||||
}
|
||||
|
||||
|
@ -150,8 +150,8 @@ impl CsrfFilter {
|
||||
|
||||
/// 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) -> CsrfFilter {
|
||||
self.origins.insert(origin.to_owned());
|
||||
pub fn allowed_origin<T: Into<String>>(mut self, origin: T) -> CsrfFilter {
|
||||
self.origins.insert(origin.into());
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -76,7 +76,7 @@ impl DefaultHeaders {
|
||||
|
||||
impl<S> Middleware<S> for DefaultHeaders {
|
||||
fn response(
|
||||
&self, _: &mut HttpRequest<S>, mut resp: HttpResponse
|
||||
&self, _: &mut HttpRequest<S>, mut resp: HttpResponse,
|
||||
) -> Result<Response> {
|
||||
for (key, value) in self.headers.iter() {
|
||||
if !resp.headers().contains_key(key) {
|
||||
|
@ -12,7 +12,7 @@ type ErrorHandler<S> = Fn(&mut HttpRequest<S>, HttpResponse) -> Result<Response>
|
||||
///
|
||||
/// You can use `ErrorHandlers::handler()` method to register a custom error
|
||||
/// handler for specific status code. You can modify existing response or
|
||||
/// create completly new one.
|
||||
/// create completely new one.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
@ -69,7 +69,7 @@ impl<S> ErrorHandlers<S> {
|
||||
|
||||
impl<S: 'static> Middleware<S> for ErrorHandlers<S> {
|
||||
fn response(
|
||||
&self, req: &mut HttpRequest<S>, resp: HttpResponse
|
||||
&self, req: &mut HttpRequest<S>, resp: HttpResponse,
|
||||
) -> Result<Response> {
|
||||
if let Some(handler) = self.handlers.get(&resp.status()) {
|
||||
handler(req, resp)
|
||||
@ -82,8 +82,8 @@ impl<S: 'static> Middleware<S> for ErrorHandlers<S> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use http::StatusCode;
|
||||
use http::header::CONTENT_TYPE;
|
||||
use http::StatusCode;
|
||||
|
||||
fn render_500<S>(_: &mut HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
|
||||
let mut builder = resp.into_builder();
|
||||
|
@ -49,8 +49,8 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use cookie::{Cookie, CookieJar, Key};
|
||||
use futures::future::{err as FutErr, ok as FutOk, FutureResult};
|
||||
use futures::Future;
|
||||
use futures::future::{FutureResult, err as FutErr, ok as FutOk};
|
||||
use time::Duration;
|
||||
|
||||
use error::{Error, Result};
|
||||
@ -100,7 +100,7 @@ pub trait RequestIdentity {
|
||||
|
||||
impl<S> RequestIdentity for HttpRequest<S> {
|
||||
fn identity(&self) -> Option<&str> {
|
||||
if let Some(id) = self.extensions_ro().get::<IdentityBox>() {
|
||||
if let Some(id) = self.extensions().get::<IdentityBox>() {
|
||||
return id.0.identity();
|
||||
}
|
||||
None
|
||||
@ -183,7 +183,7 @@ impl<S: 'static, T: IdentityPolicy<S>> Middleware<S> for IdentityService<T> {
|
||||
.from_request(&mut req)
|
||||
.then(move |res| match res {
|
||||
Ok(id) => {
|
||||
req.extensions().insert(IdentityBox(Box::new(id)));
|
||||
req.extensions_mut().insert(IdentityBox(Box::new(id)));
|
||||
FutOk(None)
|
||||
}
|
||||
Err(err) => FutErr(err),
|
||||
@ -192,9 +192,9 @@ impl<S: 'static, T: IdentityPolicy<S>> Middleware<S> for IdentityService<T> {
|
||||
}
|
||||
|
||||
fn response(
|
||||
&self, req: &mut HttpRequest<S>, resp: HttpResponse
|
||||
&self, req: &mut HttpRequest<S>, resp: HttpResponse,
|
||||
) -> Result<Response> {
|
||||
if let Some(mut id) = req.extensions().remove::<IdentityBox>() {
|
||||
if let Some(mut id) = req.extensions_mut().remove::<IdentityBox>() {
|
||||
id.0.write(resp)
|
||||
} else {
|
||||
Ok(Response::Done(resp))
|
||||
|
@ -116,7 +116,7 @@ impl Logger {
|
||||
|
||||
impl<S> Middleware<S> for Logger {
|
||||
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
|
||||
req.extensions().insert(StartTime(time::now()));
|
||||
req.extensions_mut().insert(StartTime(time::now()));
|
||||
Ok(Started::Done)
|
||||
}
|
||||
|
||||
|
@ -19,16 +19,9 @@ pub use self::defaultheaders::DefaultHeaders;
|
||||
pub use self::errhandlers::ErrorHandlers;
|
||||
pub use self::logger::Logger;
|
||||
|
||||
#[cfg(feature = "session")]
|
||||
#[doc(hidden)]
|
||||
#[deprecated(since = "0.5.4",
|
||||
note = "please use `actix_web::middleware::session` instead")]
|
||||
pub use self::session::{CookieSessionBackend, CookieSessionError, RequestSession,
|
||||
Session, SessionBackend, SessionImpl, SessionStorage};
|
||||
|
||||
/// Middleware start result
|
||||
pub enum Started {
|
||||
/// Execution completed
|
||||
/// Middleware is completed, continue to next middleware
|
||||
Done,
|
||||
/// New http response got generated. If middleware generates response
|
||||
/// handler execution halts.
|
||||
@ -65,7 +58,7 @@ pub trait Middleware<S>: 'static {
|
||||
/// Method is called when handler returns response,
|
||||
/// but before sending http message to peer.
|
||||
fn response(
|
||||
&self, req: &mut HttpRequest<S>, resp: HttpResponse
|
||||
&self, req: &mut HttpRequest<S>, resp: HttpResponse,
|
||||
) -> Result<Response> {
|
||||
Ok(Response::Done(resp))
|
||||
}
|
||||
|
@ -35,9 +35,9 @@
|
||||
//! # extern crate actix;
|
||||
//! # extern crate actix_web;
|
||||
//! use actix_web::{server, App, HttpRequest, Result};
|
||||
//! use actix_web::middleware::{RequestSession, SessionStorage, CookieSessionBackend};
|
||||
//! use actix_web::middleware::session::{RequestSession, SessionStorage, CookieSessionBackend};
|
||||
//!
|
||||
//! fn index(mut req: HttpRequest) -> Result<&'static str> {
|
||||
//! fn index(req: HttpRequest) -> Result<&'static str> {
|
||||
//! // access session data
|
||||
//! if let Some(count) = req.session().get::<i32>("counter")? {
|
||||
//! println!("SESSION value: {}", count);
|
||||
@ -63,16 +63,18 @@
|
||||
//! let _ = sys.run();
|
||||
//! }
|
||||
//! ```
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::marker::PhantomData;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use cookie::{Cookie, CookieJar, Key};
|
||||
use futures::future::{err as FutErr, ok as FutOk, FutureResult};
|
||||
use futures::Future;
|
||||
use futures::future::{FutureResult, err as FutErr, ok as FutOk};
|
||||
use http::header::{self, HeaderValue};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use serde_json;
|
||||
use serde_json::error::Error as JsonError;
|
||||
use time::Duration;
|
||||
@ -86,7 +88,7 @@ use middleware::{Middleware, Response, Started};
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::*;
|
||||
/// use actix_web::middleware::RequestSession;
|
||||
/// use actix_web::middleware::session::RequestSession;
|
||||
///
|
||||
/// fn index(mut req: HttpRequest) -> Result<&'static str> {
|
||||
/// // access session data
|
||||
@ -101,17 +103,15 @@ use middleware::{Middleware, Response, Started};
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
pub trait RequestSession {
|
||||
fn session(&mut self) -> Session;
|
||||
fn session(&self) -> Session;
|
||||
}
|
||||
|
||||
impl<S> RequestSession for HttpRequest<S> {
|
||||
fn session(&mut self) -> Session {
|
||||
if let Some(s_impl) = self.extensions().get_mut::<Arc<SessionImplBox>>() {
|
||||
if let Some(s) = Arc::get_mut(s_impl) {
|
||||
return Session(s.0.as_mut());
|
||||
}
|
||||
fn session(&self) -> Session {
|
||||
if let Some(s_impl) = self.extensions().get::<Arc<SessionImplCell>>() {
|
||||
return Session(SessionInner::Session(Arc::clone(&s_impl)));
|
||||
}
|
||||
Session(unsafe { &mut DUMMY })
|
||||
Session(SessionInner::None)
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,7 +123,7 @@ impl<S> RequestSession for HttpRequest<S> {
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::*;
|
||||
/// use actix_web::middleware::RequestSession;
|
||||
/// use actix_web::middleware::session::RequestSession;
|
||||
///
|
||||
/// fn index(mut req: HttpRequest) -> Result<&'static str> {
|
||||
/// // access session data
|
||||
@ -137,41 +137,65 @@ impl<S> RequestSession for HttpRequest<S> {
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
pub struct Session<'a>(&'a mut SessionImpl);
|
||||
pub struct Session(SessionInner);
|
||||
|
||||
impl<'a> Session<'a> {
|
||||
enum SessionInner {
|
||||
Session(Arc<SessionImplCell>),
|
||||
None,
|
||||
}
|
||||
|
||||
impl Session {
|
||||
/// Get a `value` from the session.
|
||||
pub fn get<T: Deserialize<'a>>(&'a self, key: &str) -> Result<Option<T>> {
|
||||
if let Some(s) = self.0.get(key) {
|
||||
Ok(Some(serde_json::from_str(s)?))
|
||||
} else {
|
||||
Ok(None)
|
||||
pub fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>> {
|
||||
match self.0 {
|
||||
SessionInner::Session(ref sess) => {
|
||||
if let Some(s) = sess.as_ref().0.borrow().get(key) {
|
||||
Ok(Some(serde_json::from_str(s)?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
SessionInner::None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set a `value` from the session.
|
||||
pub fn set<T: Serialize>(&mut self, key: &str, value: T) -> Result<()> {
|
||||
self.0.set(key, serde_json::to_string(&value)?);
|
||||
Ok(())
|
||||
pub fn set<T: Serialize>(&self, key: &str, value: T) -> Result<()> {
|
||||
match self.0 {
|
||||
SessionInner::Session(ref sess) => {
|
||||
sess.as_ref()
|
||||
.0
|
||||
.borrow_mut()
|
||||
.set(key, serde_json::to_string(&value)?);
|
||||
Ok(())
|
||||
}
|
||||
SessionInner::None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove value from the session.
|
||||
pub fn remove(&'a mut self, key: &str) {
|
||||
self.0.remove(key)
|
||||
pub fn remove(&self, key: &str) {
|
||||
match self.0 {
|
||||
SessionInner::Session(ref sess) => sess.as_ref().0.borrow_mut().remove(key),
|
||||
SessionInner::None => (),
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear the session.
|
||||
pub fn clear(&'a mut self) {
|
||||
self.0.clear()
|
||||
pub fn clear(&self) {
|
||||
match self.0 {
|
||||
SessionInner::Session(ref sess) => sess.as_ref().0.borrow_mut().clear(),
|
||||
SessionInner::None => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SessionImplBox(Box<SessionImpl>);
|
||||
struct SessionImplCell(RefCell<Box<SessionImpl>>);
|
||||
|
||||
#[doc(hidden)]
|
||||
unsafe impl Send for SessionImplBox {}
|
||||
unsafe impl Send for SessionImplCell {}
|
||||
#[doc(hidden)]
|
||||
unsafe impl Sync for SessionImplBox {}
|
||||
unsafe impl Sync for SessionImplCell {}
|
||||
|
||||
/// Session storage middleware
|
||||
///
|
||||
@ -179,7 +203,7 @@ unsafe impl Sync for SessionImplBox {}
|
||||
/// # extern crate actix;
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::App;
|
||||
/// use actix_web::middleware::{SessionStorage, CookieSessionBackend};
|
||||
/// use actix_web::middleware::session::{SessionStorage, CookieSessionBackend};
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().middleware(
|
||||
@ -206,8 +230,9 @@ impl<S: 'static, T: SessionBackend<S>> Middleware<S> for SessionStorage<T, S> {
|
||||
.from_request(&mut req)
|
||||
.then(move |res| match res {
|
||||
Ok(sess) => {
|
||||
req.extensions()
|
||||
.insert(Arc::new(SessionImplBox(Box::new(sess))));
|
||||
req.extensions_mut().insert(Arc::new(SessionImplCell(
|
||||
RefCell::new(Box::new(sess)),
|
||||
)));
|
||||
FutOk(None)
|
||||
}
|
||||
Err(err) => FutErr(err),
|
||||
@ -216,10 +241,10 @@ impl<S: 'static, T: SessionBackend<S>> Middleware<S> for SessionStorage<T, S> {
|
||||
}
|
||||
|
||||
fn response(
|
||||
&self, req: &mut HttpRequest<S>, resp: HttpResponse
|
||||
&self, req: &mut HttpRequest<S>, resp: HttpResponse,
|
||||
) -> Result<Response> {
|
||||
if let Some(s_box) = req.extensions().remove::<Arc<SessionImplBox>>() {
|
||||
s_box.0.write(resp)
|
||||
if let Some(s_box) = req.extensions_mut().remove::<Arc<SessionImplCell>>() {
|
||||
s_box.0.borrow_mut().write(resp)
|
||||
} else {
|
||||
Ok(Response::Done(resp))
|
||||
}
|
||||
@ -251,23 +276,6 @@ pub trait SessionBackend<S>: Sized + 'static {
|
||||
fn from_request(&self, request: &mut HttpRequest<S>) -> Self::ReadFuture;
|
||||
}
|
||||
|
||||
/// Dummy session impl, does not do anything
|
||||
struct DummySessionImpl;
|
||||
|
||||
static mut DUMMY: DummySessionImpl = DummySessionImpl;
|
||||
|
||||
impl SessionImpl for DummySessionImpl {
|
||||
fn get(&self, _: &str) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
fn set(&mut self, _: &str, _: String) {}
|
||||
fn remove(&mut self, _: &str) {}
|
||||
fn clear(&mut self) {}
|
||||
fn write(&self, resp: HttpResponse) -> Result<Response> {
|
||||
Ok(Response::Done(resp))
|
||||
}
|
||||
}
|
||||
|
||||
/// Session that uses signed cookies as session storage
|
||||
pub struct CookieSession {
|
||||
changed: bool,
|
||||
@ -349,7 +357,7 @@ impl CookieSessionInner {
|
||||
}
|
||||
|
||||
fn set_cookie(
|
||||
&self, resp: &mut HttpResponse, state: &HashMap<String, String>
|
||||
&self, resp: &mut HttpResponse, state: &HashMap<String, String>,
|
||||
) -> Result<()> {
|
||||
let value =
|
||||
serde_json::to_string(&state).map_err(CookieSessionError::Serialize)?;
|
||||
@ -437,7 +445,7 @@ impl CookieSessionInner {
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::middleware::CookieSessionBackend;
|
||||
/// use actix_web::middleware::session::CookieSessionBackend;
|
||||
///
|
||||
/// # fn main() {
|
||||
/// let backend: CookieSessionBackend = CookieSessionBackend::signed(&[0; 32])
|
||||
@ -517,3 +525,30 @@ impl<S> SessionBackend<S> for CookieSessionBackend {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use application::App;
|
||||
use test;
|
||||
|
||||
#[test]
|
||||
fn cookie_session() {
|
||||
let mut srv = test::TestServer::with_factory(|| {
|
||||
App::new()
|
||||
.middleware(SessionStorage::new(
|
||||
CookieSessionBackend::signed(&[0; 32]).secure(false),
|
||||
))
|
||||
.resource("/", |r| {
|
||||
r.f(|req| {
|
||||
let _ = req.session().set("counter", 100);
|
||||
"test"
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
let request = srv.get().uri(srv.url("/")).finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.cookie("actix-session").is_some());
|
||||
}
|
||||
}
|
||||
|
@ -7,8 +7,8 @@ use std::{cmp, fmt};
|
||||
use bytes::Bytes;
|
||||
use futures::task::{current as current_task, Task};
|
||||
use futures::{Async, Poll, Stream};
|
||||
use http::HttpTryFrom;
|
||||
use http::header::{self, HeaderMap, HeaderName, HeaderValue};
|
||||
use http::HttpTryFrom;
|
||||
use httparse;
|
||||
use mime;
|
||||
|
||||
@ -168,7 +168,7 @@ where
|
||||
}
|
||||
|
||||
fn read_boundary(
|
||||
payload: &mut PayloadHelper<S>, boundary: &str
|
||||
payload: &mut PayloadHelper<S>, boundary: &str,
|
||||
) -> Poll<bool, MultipartError> {
|
||||
// TODO: need to read epilogue
|
||||
match payload.readline()? {
|
||||
@ -192,7 +192,7 @@ where
|
||||
}
|
||||
|
||||
fn skip_until_boundary(
|
||||
payload: &mut PayloadHelper<S>, boundary: &str
|
||||
payload: &mut PayloadHelper<S>, boundary: &str,
|
||||
) -> Poll<bool, MultipartError> {
|
||||
let mut eof = false;
|
||||
loop {
|
||||
@ -230,7 +230,7 @@ where
|
||||
}
|
||||
|
||||
fn poll(
|
||||
&mut self, safety: &Safety
|
||||
&mut self, safety: &Safety,
|
||||
) -> Poll<Option<MultipartItem<S>>, MultipartError> {
|
||||
if self.state == InnerState::Eof {
|
||||
Ok(Async::Ready(None))
|
||||
@ -450,7 +450,7 @@ where
|
||||
S: Stream<Item = Bytes, Error = PayloadError>,
|
||||
{
|
||||
fn new(
|
||||
payload: PayloadRef<S>, boundary: String, headers: &HeaderMap
|
||||
payload: PayloadRef<S>, boundary: String, headers: &HeaderMap,
|
||||
) -> Result<InnerField<S>, PayloadError> {
|
||||
let len = if let Some(len) = headers.get(header::CONTENT_LENGTH) {
|
||||
if let Ok(s) = len.to_str() {
|
||||
@ -477,7 +477,7 @@ where
|
||||
/// Reads body part content chunk of the specified size.
|
||||
/// The body part must has `Content-Length` header with proper value.
|
||||
fn read_len(
|
||||
payload: &mut PayloadHelper<S>, size: &mut u64
|
||||
payload: &mut PayloadHelper<S>, size: &mut u64,
|
||||
) -> Poll<Option<Bytes>, MultipartError> {
|
||||
if *size == 0 {
|
||||
Ok(Async::Ready(None))
|
||||
@ -502,7 +502,7 @@ where
|
||||
/// Reads content chunk of body part with unknown length.
|
||||
/// The `Content-Length` header for body part is not necessary.
|
||||
fn read_stream(
|
||||
payload: &mut PayloadHelper<S>, boundary: &str
|
||||
payload: &mut PayloadHelper<S>, boundary: &str,
|
||||
) -> Poll<Option<Bytes>, MultipartError> {
|
||||
match payload.read_until(b"\r")? {
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
|
16
src/param.rs
16
src/param.rs
@ -42,6 +42,22 @@ impl<'a> Params<'a> {
|
||||
self.0.push((name.into(), value.into()));
|
||||
}
|
||||
|
||||
pub(crate) fn set<N, V>(&mut self, name: N, value: V)
|
||||
where
|
||||
N: Into<Cow<'a, str>>,
|
||||
V: Into<Cow<'a, str>>,
|
||||
{
|
||||
let name = name.into();
|
||||
let value = value.into();
|
||||
for item in &mut self.0 {
|
||||
if item.0 == name {
|
||||
item.1 = value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.0.push((name, value));
|
||||
}
|
||||
|
||||
/// Check if there are any matched patterns
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
|
@ -11,7 +11,7 @@ use application::Inner;
|
||||
use body::{Body, BodyStream};
|
||||
use context::{ActorHttpContext, Frame};
|
||||
use error::Error;
|
||||
use handler::{Reply, ReplyItem};
|
||||
use handler::{AsyncResult, AsyncResultItem};
|
||||
use header::ContentEncoding;
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
@ -28,7 +28,9 @@ pub(crate) enum HandlerType {
|
||||
pub(crate) trait PipelineHandler<S> {
|
||||
fn encoding(&self) -> ContentEncoding;
|
||||
|
||||
fn handle(&mut self, req: HttpRequest<S>, htype: HandlerType) -> Reply;
|
||||
fn handle(
|
||||
&mut self, req: HttpRequest<S>, htype: HandlerType,
|
||||
) -> AsyncResult<HttpResponse>;
|
||||
}
|
||||
|
||||
pub(crate) struct Pipeline<S, H>(PipelineInfo<S>, PipelineState<S, H>);
|
||||
@ -67,7 +69,7 @@ impl<S: 'static, H: PipelineHandler<S>> PipelineState<S, H> {
|
||||
}
|
||||
|
||||
struct PipelineInfo<S> {
|
||||
req: HttpRequest<S>,
|
||||
req: UnsafeCell<HttpRequest<S>>,
|
||||
count: u16,
|
||||
mws: Rc<Vec<Box<Middleware<S>>>>,
|
||||
context: Option<Box<ActorHttpContext>>,
|
||||
@ -79,7 +81,7 @@ struct PipelineInfo<S> {
|
||||
impl<S> PipelineInfo<S> {
|
||||
fn new(req: HttpRequest<S>) -> PipelineInfo<S> {
|
||||
PipelineInfo {
|
||||
req,
|
||||
req: UnsafeCell::new(req),
|
||||
count: 0,
|
||||
mws: Rc::new(Vec::new()),
|
||||
error: None,
|
||||
@ -89,11 +91,17 @@ impl<S> PipelineInfo<S> {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn req(&self) -> &HttpRequest<S> {
|
||||
unsafe { &*self.req.get() }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref))]
|
||||
fn req_mut(&self) -> &mut HttpRequest<S> {
|
||||
#[allow(mutable_transmutes)]
|
||||
unsafe {
|
||||
mem::transmute(&self.req)
|
||||
&mut *self.req.get()
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,8 +124,8 @@ impl<S: 'static, H: PipelineHandler<S>> Pipeline<S, H> {
|
||||
handler: Rc<UnsafeCell<H>>, htype: HandlerType,
|
||||
) -> Pipeline<S, H> {
|
||||
let mut info = PipelineInfo {
|
||||
req,
|
||||
mws,
|
||||
req: UnsafeCell::new(req),
|
||||
count: 0,
|
||||
error: None,
|
||||
context: None,
|
||||
@ -159,7 +167,7 @@ impl<S: 'static, H: PipelineHandler<S>> HttpHandlerTask for Pipeline<S, H> {
|
||||
}
|
||||
|
||||
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error> {
|
||||
let info: &mut PipelineInfo<_> = unsafe { mem::transmute(&mut self.0) };
|
||||
let info: &mut PipelineInfo<_> = unsafe { &mut *(&mut self.0 as *mut _) };
|
||||
|
||||
loop {
|
||||
if self.1.is_response() {
|
||||
@ -197,7 +205,7 @@ impl<S: 'static, H: PipelineHandler<S>> HttpHandlerTask for Pipeline<S, H> {
|
||||
}
|
||||
|
||||
fn poll(&mut self) -> Poll<(), Error> {
|
||||
let info: &mut PipelineInfo<_> = unsafe { mem::transmute(&mut self.0) };
|
||||
let info: &mut PipelineInfo<_> = unsafe { &mut *(&mut self.0 as *mut _) };
|
||||
|
||||
loop {
|
||||
match self.1 {
|
||||
@ -228,38 +236,29 @@ struct StartMiddlewares<S, H> {
|
||||
|
||||
impl<S: 'static, H: PipelineHandler<S>> StartMiddlewares<S, H> {
|
||||
fn init(
|
||||
info: &mut PipelineInfo<S>, hnd: Rc<UnsafeCell<H>>, htype: HandlerType
|
||||
info: &mut PipelineInfo<S>, hnd: Rc<UnsafeCell<H>>, htype: HandlerType,
|
||||
) -> PipelineState<S, H> {
|
||||
// execute middlewares, we need this stage because middlewares could be
|
||||
// non-async and we can move to next state immediately
|
||||
let len = info.mws.len() as u16;
|
||||
loop {
|
||||
if info.count == len {
|
||||
let reply = unsafe { &mut *hnd.get() }.handle(info.req.clone(), htype);
|
||||
let reply = unsafe { &mut *hnd.get() }.handle(info.req().clone(), htype);
|
||||
return WaitingResponse::init(info, reply);
|
||||
} else {
|
||||
match info.mws[info.count as usize].start(&mut info.req) {
|
||||
match info.mws[info.count as usize].start(info.req_mut()) {
|
||||
Ok(Started::Done) => info.count += 1,
|
||||
Ok(Started::Response(resp)) => {
|
||||
return RunMiddlewares::init(info, resp)
|
||||
}
|
||||
Ok(Started::Future(mut fut)) => match fut.poll() {
|
||||
Ok(Async::NotReady) => {
|
||||
return PipelineState::Starting(StartMiddlewares {
|
||||
hnd,
|
||||
htype,
|
||||
fut: Some(fut),
|
||||
_s: PhantomData,
|
||||
})
|
||||
}
|
||||
Ok(Async::Ready(resp)) => {
|
||||
if let Some(resp) = resp {
|
||||
return RunMiddlewares::init(info, resp);
|
||||
}
|
||||
info.count += 1;
|
||||
}
|
||||
Err(err) => return ProcessResponse::init(err.into()),
|
||||
},
|
||||
Ok(Started::Future(fut)) => {
|
||||
return PipelineState::Starting(StartMiddlewares {
|
||||
hnd,
|
||||
htype,
|
||||
fut: Some(fut),
|
||||
_s: PhantomData,
|
||||
})
|
||||
}
|
||||
Err(err) => return ProcessResponse::init(err.into()),
|
||||
}
|
||||
}
|
||||
@ -278,7 +277,7 @@ impl<S: 'static, H: PipelineHandler<S>> StartMiddlewares<S, H> {
|
||||
}
|
||||
if info.count == len {
|
||||
let reply = unsafe { &mut *self.hnd.get() }
|
||||
.handle(info.req.clone(), self.htype);
|
||||
.handle(info.req().clone(), self.htype);
|
||||
return Some(WaitingResponse::init(info, reply));
|
||||
} else {
|
||||
loop {
|
||||
@ -313,10 +312,13 @@ struct WaitingResponse<S, H> {
|
||||
|
||||
impl<S: 'static, H> WaitingResponse<S, H> {
|
||||
#[inline]
|
||||
fn init(info: &mut PipelineInfo<S>, reply: Reply) -> PipelineState<S, H> {
|
||||
fn init(
|
||||
info: &mut PipelineInfo<S>, reply: AsyncResult<HttpResponse>,
|
||||
) -> PipelineState<S, H> {
|
||||
match reply.into() {
|
||||
ReplyItem::Message(resp) => RunMiddlewares::init(info, resp),
|
||||
ReplyItem::Future(fut) => PipelineState::Handler(WaitingResponse {
|
||||
AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()),
|
||||
AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, resp),
|
||||
AsyncResultItem::Future(fut) => PipelineState::Handler(WaitingResponse {
|
||||
fut,
|
||||
_s: PhantomData,
|
||||
_h: PhantomData,
|
||||
@ -462,7 +464,7 @@ impl<S: 'static, H> ProcessResponse<S, H> {
|
||||
}
|
||||
|
||||
fn poll_io(
|
||||
mut self, io: &mut Writer, info: &mut PipelineInfo<S>
|
||||
mut self, io: &mut Writer, info: &mut PipelineInfo<S>,
|
||||
) -> Result<PipelineState<S, H>, PipelineState<S, H>> {
|
||||
loop {
|
||||
if self.drain.is_none() && self.running != RunningState::Paused {
|
||||
@ -482,8 +484,7 @@ impl<S: 'static, H> ProcessResponse<S, H> {
|
||||
Err(err) => {
|
||||
info.error = Some(err.into());
|
||||
return Ok(FinishingMiddlewares::init(
|
||||
info,
|
||||
self.resp,
|
||||
info, self.resp,
|
||||
));
|
||||
}
|
||||
};
|
||||
@ -525,8 +526,7 @@ impl<S: 'static, H> ProcessResponse<S, H> {
|
||||
if let Err(err) = io.write_eof() {
|
||||
info.error = Some(err.into());
|
||||
return Ok(FinishingMiddlewares::init(
|
||||
info,
|
||||
self.resp,
|
||||
info, self.resp,
|
||||
));
|
||||
}
|
||||
break;
|
||||
@ -537,8 +537,7 @@ impl<S: 'static, H> ProcessResponse<S, H> {
|
||||
Err(err) => {
|
||||
info.error = Some(err.into());
|
||||
return Ok(FinishingMiddlewares::init(
|
||||
info,
|
||||
self.resp,
|
||||
info, self.resp,
|
||||
));
|
||||
}
|
||||
Ok(result) => result,
|
||||
@ -572,8 +571,7 @@ impl<S: 'static, H> ProcessResponse<S, H> {
|
||||
info.error = Some(err.into());
|
||||
return Ok(
|
||||
FinishingMiddlewares::init(
|
||||
info,
|
||||
self.resp,
|
||||
info, self.resp,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -585,8 +583,7 @@ impl<S: 'static, H> ProcessResponse<S, H> {
|
||||
info.error = Some(err.into());
|
||||
return Ok(
|
||||
FinishingMiddlewares::init(
|
||||
info,
|
||||
self.resp,
|
||||
info, self.resp,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -611,8 +608,7 @@ impl<S: 'static, H> ProcessResponse<S, H> {
|
||||
Err(err) => {
|
||||
info.error = Some(err);
|
||||
return Ok(FinishingMiddlewares::init(
|
||||
info,
|
||||
self.resp,
|
||||
info, self.resp,
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -717,8 +713,11 @@ impl<S: 'static, H> FinishingMiddlewares<S, H> {
|
||||
return None;
|
||||
}
|
||||
self.fut = None;
|
||||
info.count -= 1;
|
||||
if info.count == 0 {
|
||||
return Some(Completed::init(info));
|
||||
}
|
||||
|
||||
info.count -= 1;
|
||||
match info.mws[info.count as usize].finish(info.req_mut(), &self.resp) {
|
||||
Finished::Done => {
|
||||
if info.count == 0 {
|
||||
|
@ -171,7 +171,7 @@ pub fn Method<S: 'static>(method: http::Method) -> MethodPredicate<S> {
|
||||
/// Return predicate that matches if request contains specified header and
|
||||
/// value.
|
||||
pub fn Header<S: 'static>(
|
||||
name: &'static str, value: &'static str
|
||||
name: &'static str, value: &'static str,
|
||||
) -> HeaderPredicate<S> {
|
||||
HeaderPredicate(
|
||||
header::HeaderName::try_from(name).unwrap(),
|
||||
|
@ -4,7 +4,7 @@ use std::rc::Rc;
|
||||
use http::{Method, StatusCode};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use handler::{FromRequest, Handler, Reply, Responder};
|
||||
use handler::{AsyncResult, FromRequest, Handler, Responder};
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
use middleware::Middleware;
|
||||
@ -187,6 +187,9 @@ impl<S: 'static> ResourceHandler<S> {
|
||||
///
|
||||
/// This is similar to `App's` middlewares, but
|
||||
/// middlewares get invoked on resource level.
|
||||
///
|
||||
/// *Note* `Middleware::finish()` fires right after response get
|
||||
/// prepared. It does not wait until body get sent to peer.
|
||||
pub fn middleware<M: Middleware<S>>(&mut self, mw: M) {
|
||||
Rc::get_mut(&mut self.middlewares)
|
||||
.unwrap()
|
||||
@ -194,8 +197,8 @@ impl<S: 'static> ResourceHandler<S> {
|
||||
}
|
||||
|
||||
pub(crate) fn handle(
|
||||
&mut self, mut req: HttpRequest<S>, default: Option<&mut ResourceHandler<S>>
|
||||
) -> Reply {
|
||||
&mut self, mut req: HttpRequest<S>, default: Option<&mut ResourceHandler<S>>,
|
||||
) -> AsyncResult<HttpResponse> {
|
||||
for route in &mut self.routes {
|
||||
if route.check(&mut req) {
|
||||
return if self.middlewares.is_empty() {
|
||||
@ -208,7 +211,7 @@ impl<S: 'static> ResourceHandler<S> {
|
||||
if let Some(resource) = default {
|
||||
resource.handle(req, None)
|
||||
} else {
|
||||
Reply::response(HttpResponse::new(StatusCode::NOT_FOUND))
|
||||
AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
180
src/route.rs
180
src/route.rs
@ -1,16 +1,17 @@
|
||||
use futures::{Async, Future, Poll};
|
||||
use std::cell::UnsafeCell;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem;
|
||||
use std::rc::Rc;
|
||||
|
||||
use futures::{Async, Future, Poll};
|
||||
|
||||
use error::Error;
|
||||
use handler::{AsyncHandler, FromRequest, Handler, Reply, ReplyItem, Responder,
|
||||
RouteHandler, WrapHandler};
|
||||
use handler::{AsyncHandler, AsyncResult, AsyncResultItem, FromRequest, Handler,
|
||||
Responder, RouteHandler, WrapHandler};
|
||||
use http::StatusCode;
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
use middleware::{Middleware, Response as MiddlewareResponse,
|
||||
Started as MiddlewareStarted};
|
||||
use middleware::{Finished as MiddlewareFinished, Middleware,
|
||||
Response as MiddlewareResponse, Started as MiddlewareStarted};
|
||||
use pred::Predicate;
|
||||
use with::{ExtractorConfig, With, With2, With3};
|
||||
|
||||
@ -44,15 +45,15 @@ impl<S: 'static> Route<S> {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn handle(&mut self, req: HttpRequest<S>) -> Reply {
|
||||
pub(crate) fn handle(&mut self, req: HttpRequest<S>) -> AsyncResult<HttpResponse> {
|
||||
self.handler.handle(req)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn compose(
|
||||
&mut self, req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>
|
||||
) -> Reply {
|
||||
Reply::async(Compose::new(req, mws, self.handler.clone()))
|
||||
&mut self, req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>,
|
||||
) -> AsyncResult<HttpResponse> {
|
||||
AsyncResult::async(Box::new(Compose::new(req, mws, self.handler.clone())))
|
||||
}
|
||||
|
||||
/// Add match predicate to route.
|
||||
@ -103,7 +104,7 @@ impl<S: 'static> Route<S> {
|
||||
self.handler = InnerHandler::async(handler);
|
||||
}
|
||||
|
||||
/// Set handler function, use request extractor for paramters.
|
||||
/// Set handler function, use request extractor for parameters.
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate bytes;
|
||||
@ -139,7 +140,7 @@ impl<S: 'static> Route<S> {
|
||||
cfg
|
||||
}
|
||||
|
||||
/// Set handler function, use request extractor for both paramters.
|
||||
/// Set handler function, use request extractor for both parameters.
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate bytes;
|
||||
@ -170,7 +171,7 @@ impl<S: 'static> Route<S> {
|
||||
/// }
|
||||
/// ```
|
||||
pub fn with2<T1, T2, F, R>(
|
||||
&mut self, handler: F
|
||||
&mut self, handler: F,
|
||||
) -> (ExtractorConfig<S, T1>, ExtractorConfig<S, T2>)
|
||||
where
|
||||
F: Fn(T1, T2) -> R + 'static,
|
||||
@ -188,9 +189,9 @@ impl<S: 'static> Route<S> {
|
||||
(cfg1, cfg2)
|
||||
}
|
||||
|
||||
/// Set handler function, use request extractor for all paramters.
|
||||
/// Set handler function, use request extractor for all parameters.
|
||||
pub fn with3<T1, T2, T3, F, R>(
|
||||
&mut self, handler: F
|
||||
&mut self, handler: F,
|
||||
) -> (
|
||||
ExtractorConfig<S, T1>,
|
||||
ExtractorConfig<S, T2>,
|
||||
@ -218,12 +219,14 @@ impl<S: 'static> Route<S> {
|
||||
|
||||
/// `RouteHandler` wrapper. This struct is required because it needs to be
|
||||
/// shared for resource level middlewares.
|
||||
struct InnerHandler<S>(Rc<Box<RouteHandler<S>>>);
|
||||
struct InnerHandler<S>(Rc<UnsafeCell<Box<RouteHandler<S>>>>);
|
||||
|
||||
impl<S: 'static> InnerHandler<S> {
|
||||
#[inline]
|
||||
fn new<H: Handler<S>>(h: H) -> Self {
|
||||
InnerHandler(Rc::new(Box::new(WrapHandler::new(h))))
|
||||
InnerHandler(Rc::new(UnsafeCell::new(Box::new(WrapHandler::new(
|
||||
h,
|
||||
)))))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@ -234,16 +237,15 @@ impl<S: 'static> InnerHandler<S> {
|
||||
R: Responder + 'static,
|
||||
E: Into<Error> + 'static,
|
||||
{
|
||||
InnerHandler(Rc::new(Box::new(AsyncHandler::new(h))))
|
||||
InnerHandler(Rc::new(UnsafeCell::new(Box::new(AsyncHandler::new(
|
||||
h,
|
||||
)))))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn handle(&self, req: HttpRequest<S>) -> Reply {
|
||||
// reason: handler is unique per thread,
|
||||
// handler get called from async code only
|
||||
#[allow(mutable_transmutes)]
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(borrowed_box))]
|
||||
let h: &mut Box<RouteHandler<S>> = unsafe { mem::transmute(self.0.as_ref()) };
|
||||
pub fn handle(&self, req: HttpRequest<S>) -> AsyncResult<HttpResponse> {
|
||||
// reason: handler is unique per thread, handler get called from async code only
|
||||
let h = unsafe { &mut *self.0.as_ref().get() };
|
||||
h.handle(req)
|
||||
}
|
||||
}
|
||||
@ -272,7 +274,8 @@ enum ComposeState<S: 'static> {
|
||||
Starting(StartMiddlewares<S>),
|
||||
Handler(WaitingResponse<S>),
|
||||
RunMiddlewares(RunMiddlewares<S>),
|
||||
Response(Response<S>),
|
||||
Finishing(FinishingMiddlewares<S>),
|
||||
Completed(Response<S>),
|
||||
}
|
||||
|
||||
impl<S: 'static> ComposeState<S> {
|
||||
@ -281,14 +284,15 @@ impl<S: 'static> ComposeState<S> {
|
||||
ComposeState::Starting(ref mut state) => state.poll(info),
|
||||
ComposeState::Handler(ref mut state) => state.poll(info),
|
||||
ComposeState::RunMiddlewares(ref mut state) => state.poll(info),
|
||||
ComposeState::Response(_) => None,
|
||||
ComposeState::Finishing(ref mut state) => state.poll(info),
|
||||
ComposeState::Completed(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static> Compose<S> {
|
||||
fn new(
|
||||
req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>, handler: InnerHandler<S>
|
||||
req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>, handler: InnerHandler<S>,
|
||||
) -> Self {
|
||||
let mut info = ComposeInfo {
|
||||
count: 0,
|
||||
@ -308,7 +312,7 @@ impl<S> Future for Compose<S> {
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
loop {
|
||||
if let ComposeState::Response(ref mut resp) = self.state {
|
||||
if let ComposeState::Completed(ref mut resp) = self.state {
|
||||
let resp = resp.resp.take().unwrap();
|
||||
return Ok(Async::Ready(resp));
|
||||
}
|
||||
@ -342,22 +346,13 @@ impl<S: 'static> StartMiddlewares<S> {
|
||||
Ok(MiddlewareStarted::Response(resp)) => {
|
||||
return RunMiddlewares::init(info, resp)
|
||||
}
|
||||
Ok(MiddlewareStarted::Future(mut fut)) => match fut.poll() {
|
||||
Ok(Async::NotReady) => {
|
||||
return ComposeState::Starting(StartMiddlewares {
|
||||
fut: Some(fut),
|
||||
_s: PhantomData,
|
||||
})
|
||||
}
|
||||
Ok(Async::Ready(resp)) => {
|
||||
if let Some(resp) = resp {
|
||||
return RunMiddlewares::init(info, resp);
|
||||
}
|
||||
info.count += 1;
|
||||
}
|
||||
Err(err) => return Response::init(err.into()),
|
||||
},
|
||||
Err(err) => return Response::init(err.into()),
|
||||
Ok(MiddlewareStarted::Future(fut)) => {
|
||||
return ComposeState::Starting(StartMiddlewares {
|
||||
fut: Some(fut),
|
||||
_s: PhantomData,
|
||||
})
|
||||
}
|
||||
Err(err) => return FinishingMiddlewares::init(info, err.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -387,12 +382,17 @@ impl<S: 'static> StartMiddlewares<S> {
|
||||
self.fut = Some(fut);
|
||||
continue 'outer;
|
||||
}
|
||||
Err(err) => return Some(Response::init(err.into())),
|
||||
Err(err) => {
|
||||
return Some(FinishingMiddlewares::init(
|
||||
info,
|
||||
err.into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => return Some(Response::init(err.into())),
|
||||
Err(err) => return Some(FinishingMiddlewares::init(info, err.into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -406,10 +406,13 @@ struct WaitingResponse<S> {
|
||||
|
||||
impl<S: 'static> WaitingResponse<S> {
|
||||
#[inline]
|
||||
fn init(info: &mut ComposeInfo<S>, reply: Reply) -> ComposeState<S> {
|
||||
fn init(
|
||||
info: &mut ComposeInfo<S>, reply: AsyncResult<HttpResponse>,
|
||||
) -> ComposeState<S> {
|
||||
match reply.into() {
|
||||
ReplyItem::Message(resp) => RunMiddlewares::init(info, resp),
|
||||
ReplyItem::Future(fut) => ComposeState::Handler(WaitingResponse {
|
||||
AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()),
|
||||
AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, resp),
|
||||
AsyncResultItem::Future(fut) => ComposeState::Handler(WaitingResponse {
|
||||
fut,
|
||||
_s: PhantomData,
|
||||
}),
|
||||
@ -441,12 +444,12 @@ impl<S: 'static> RunMiddlewares<S> {
|
||||
resp = match info.mws[curr].response(&mut info.req, resp) {
|
||||
Err(err) => {
|
||||
info.count = curr + 1;
|
||||
return Response::init(err.into());
|
||||
return FinishingMiddlewares::init(info, err.into());
|
||||
}
|
||||
Ok(MiddlewareResponse::Done(r)) => {
|
||||
curr += 1;
|
||||
if curr == len {
|
||||
return Response::init(r);
|
||||
return FinishingMiddlewares::init(info, r);
|
||||
} else {
|
||||
r
|
||||
}
|
||||
@ -473,15 +476,17 @@ impl<S: 'static> RunMiddlewares<S> {
|
||||
self.curr += 1;
|
||||
resp
|
||||
}
|
||||
Err(err) => return Some(Response::init(err.into())),
|
||||
Err(err) => return Some(FinishingMiddlewares::init(info, err.into())),
|
||||
};
|
||||
|
||||
loop {
|
||||
if self.curr == len {
|
||||
return Some(Response::init(resp));
|
||||
return Some(FinishingMiddlewares::init(info, resp));
|
||||
} else {
|
||||
match info.mws[self.curr].response(&mut info.req, resp) {
|
||||
Err(err) => return Some(Response::init(err.into())),
|
||||
Err(err) => {
|
||||
return Some(FinishingMiddlewares::init(info, err.into()))
|
||||
}
|
||||
Ok(MiddlewareResponse::Done(r)) => {
|
||||
self.curr += 1;
|
||||
resp = r
|
||||
@ -497,6 +502,71 @@ impl<S: 'static> RunMiddlewares<S> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Middlewares start executor
|
||||
struct FinishingMiddlewares<S> {
|
||||
resp: Option<HttpResponse>,
|
||||
fut: Option<Box<Future<Item = (), Error = Error>>>,
|
||||
_s: PhantomData<S>,
|
||||
}
|
||||
|
||||
impl<S: 'static> FinishingMiddlewares<S> {
|
||||
fn init(info: &mut ComposeInfo<S>, resp: HttpResponse) -> ComposeState<S> {
|
||||
if info.count == 0 {
|
||||
Response::init(resp)
|
||||
} else {
|
||||
let mut state = FinishingMiddlewares {
|
||||
resp: Some(resp),
|
||||
fut: None,
|
||||
_s: PhantomData,
|
||||
};
|
||||
if let Some(st) = state.poll(info) {
|
||||
st
|
||||
} else {
|
||||
ComposeState::Finishing(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn poll(&mut self, info: &mut ComposeInfo<S>) -> Option<ComposeState<S>> {
|
||||
loop {
|
||||
// poll latest fut
|
||||
let not_ready = if let Some(ref mut fut) = self.fut {
|
||||
match fut.poll() {
|
||||
Ok(Async::NotReady) => true,
|
||||
Ok(Async::Ready(())) => false,
|
||||
Err(err) => {
|
||||
error!("Middleware finish error: {}", err);
|
||||
false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if not_ready {
|
||||
return None;
|
||||
}
|
||||
self.fut = None;
|
||||
if info.count == 0 {
|
||||
return Some(Response::init(self.resp.take().unwrap()));
|
||||
}
|
||||
|
||||
info.count -= 1;
|
||||
match info.mws[info.count as usize]
|
||||
.finish(&mut info.req, self.resp.as_ref().unwrap())
|
||||
{
|
||||
MiddlewareFinished::Done => {
|
||||
if info.count == 0 {
|
||||
return Some(Response::init(self.resp.take().unwrap()));
|
||||
}
|
||||
}
|
||||
MiddlewareFinished::Future(fut) => {
|
||||
self.fut = Some(fut);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Response<S> {
|
||||
resp: Option<HttpResponse>,
|
||||
_s: PhantomData<S>,
|
||||
@ -504,7 +574,7 @@ struct Response<S> {
|
||||
|
||||
impl<S: 'static> Response<S> {
|
||||
fn init(resp: HttpResponse) -> ComposeState<S> {
|
||||
ComposeState::Response(Response {
|
||||
ComposeState::Completed(Response {
|
||||
resp: Some(resp),
|
||||
_s: PhantomData,
|
||||
})
|
||||
|
130
src/router.rs
130
src/router.rs
@ -1,6 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::mem;
|
||||
use std::rc::Rc;
|
||||
|
||||
use regex::{escape, Regex};
|
||||
@ -79,12 +78,13 @@ impl Router {
|
||||
if self.0.prefix_len > req.path().len() {
|
||||
return None;
|
||||
}
|
||||
let path: &str = unsafe { mem::transmute(&req.path()[self.0.prefix_len..]) };
|
||||
let path = unsafe { &*(&req.path()[self.0.prefix_len..] as *const str) };
|
||||
let route_path = if path.is_empty() { "/" } else { path };
|
||||
|
||||
for (idx, pattern) in self.0.patterns.iter().enumerate() {
|
||||
if pattern.match_with_params(route_path, req.match_info_mut()) {
|
||||
req.set_resource(idx);
|
||||
req.set_prefix_len(self.0.prefix_len as u16);
|
||||
return Some(idx);
|
||||
}
|
||||
}
|
||||
@ -113,7 +113,7 @@ impl Router {
|
||||
/// Check [`HttpRequest::url_for()`](../struct.HttpRequest.html#method.
|
||||
/// url_for) for detailed information.
|
||||
pub fn resource_path<U, I>(
|
||||
&self, name: &str, elements: U
|
||||
&self, name: &str, elements: U,
|
||||
) -> Result<String, UrlGenerationError>
|
||||
where
|
||||
U: IntoIterator<Item = I>,
|
||||
@ -142,7 +142,8 @@ enum PatternElement {
|
||||
#[derive(Clone, Debug)]
|
||||
enum PatternType {
|
||||
Static(String),
|
||||
Dynamic(Regex, Vec<String>),
|
||||
Prefix(String),
|
||||
Dynamic(Regex, Vec<String>, usize),
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
@ -150,7 +151,7 @@ enum PatternType {
|
||||
pub enum ResourceType {
|
||||
/// Normal resource
|
||||
Normal,
|
||||
/// Resource for applicaiton default handler
|
||||
/// Resource for application default handler
|
||||
Default,
|
||||
/// External resource
|
||||
External,
|
||||
@ -158,7 +159,7 @@ pub enum ResourceType {
|
||||
Unset,
|
||||
}
|
||||
|
||||
/// Reslource type describes an entry in resources table
|
||||
/// Resource type describes an entry in resources table
|
||||
#[derive(Clone)]
|
||||
pub struct Resource {
|
||||
tp: PatternType,
|
||||
@ -173,14 +174,23 @@ impl Resource {
|
||||
///
|
||||
/// Panics if path pattern is wrong.
|
||||
pub fn new(name: &str, path: &str) -> Self {
|
||||
Resource::with_prefix(name, path, "/")
|
||||
Resource::with_prefix(name, path, "/", false)
|
||||
}
|
||||
|
||||
/// Parse path pattern and create new `Resource` instance.
|
||||
///
|
||||
/// Use `prefix` type instead of `static`.
|
||||
///
|
||||
/// Panics if path regex pattern is wrong.
|
||||
pub fn prefix(name: &str, path: &str) -> Self {
|
||||
Resource::with_prefix(name, path, "/", true)
|
||||
}
|
||||
|
||||
/// Construct external resource
|
||||
///
|
||||
/// Panics if path pattern is wrong.
|
||||
pub fn external(name: &str, path: &str) -> Self {
|
||||
let mut resource = Resource::with_prefix(name, path, "/");
|
||||
let mut resource = Resource::with_prefix(name, path, "/", false);
|
||||
resource.rtp = ResourceType::External;
|
||||
resource
|
||||
}
|
||||
@ -197,8 +207,9 @@ impl Resource {
|
||||
}
|
||||
|
||||
/// Parse path pattern and create new `Resource` instance with custom prefix
|
||||
pub fn with_prefix(name: &str, path: &str, prefix: &str) -> Self {
|
||||
let (pattern, elements, is_dynamic) = Resource::parse(path, prefix);
|
||||
pub fn with_prefix(name: &str, path: &str, prefix: &str, for_prefix: bool) -> Self {
|
||||
let (pattern, elements, is_dynamic, len) =
|
||||
Resource::parse(path, prefix, for_prefix);
|
||||
|
||||
let tp = if is_dynamic {
|
||||
let re = match Regex::new(&pattern) {
|
||||
@ -208,7 +219,9 @@ impl Resource {
|
||||
let names = re.capture_names()
|
||||
.filter_map(|name| name.map(|name| name.to_owned()))
|
||||
.collect();
|
||||
PatternType::Dynamic(re, names)
|
||||
PatternType::Dynamic(re, names, len)
|
||||
} else if for_prefix {
|
||||
PatternType::Prefix(pattern.clone())
|
||||
} else {
|
||||
PatternType::Static(pattern.clone())
|
||||
};
|
||||
@ -240,16 +253,17 @@ impl Resource {
|
||||
pub fn is_match(&self, path: &str) -> bool {
|
||||
match self.tp {
|
||||
PatternType::Static(ref s) => s == path,
|
||||
PatternType::Dynamic(ref re, _) => re.is_match(path),
|
||||
PatternType::Dynamic(ref re, _, _) => re.is_match(path),
|
||||
PatternType::Prefix(ref s) => path.starts_with(s),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn match_with_params<'a>(
|
||||
&'a self, path: &'a str, params: &'a mut 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) => {
|
||||
PatternType::Dynamic(ref re, ref names, _) => {
|
||||
if let Some(captures) = re.captures(path) {
|
||||
let mut idx = 0;
|
||||
for capture in captures.iter() {
|
||||
@ -265,12 +279,48 @@ impl Resource {
|
||||
false
|
||||
}
|
||||
}
|
||||
PatternType::Prefix(ref s) => path.starts_with(s),
|
||||
}
|
||||
}
|
||||
|
||||
/// Build reousrce path.
|
||||
pub fn match_prefix_with_params<'a>(
|
||||
&'a self, path: &'a str, params: &'a mut Params<'a>,
|
||||
) -> Option<usize> {
|
||||
match self.tp {
|
||||
PatternType::Static(ref s) => if s == path {
|
||||
Some(s.len())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
PatternType::Dynamic(ref re, ref names, len) => {
|
||||
if let Some(captures) = re.captures(path) {
|
||||
let mut idx = 0;
|
||||
let mut pos = 0;
|
||||
for capture in captures.iter() {
|
||||
if let Some(ref m) = capture {
|
||||
if idx != 0 {
|
||||
params.add(names[idx - 1].as_str(), m.as_str());
|
||||
}
|
||||
idx += 1;
|
||||
pos = m.end();
|
||||
}
|
||||
}
|
||||
Some(pos + len)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
PatternType::Prefix(ref s) => if path.starts_with(s) {
|
||||
Some(s.len())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Build resource path.
|
||||
pub fn resource_path<U, I>(
|
||||
&self, router: &Router, elements: U
|
||||
&self, router: &Router, elements: U,
|
||||
) -> Result<String, UrlGenerationError>
|
||||
where
|
||||
U: IntoIterator<Item = I>,
|
||||
@ -297,7 +347,9 @@ impl Resource {
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
fn parse(pattern: &str, prefix: &str) -> (String, Vec<PatternElement>, bool) {
|
||||
fn parse(
|
||||
pattern: &str, prefix: &str, for_prefix: bool,
|
||||
) -> (String, Vec<PatternElement>, bool, usize) {
|
||||
const DEFAULT_PATTERN: &str = "[^/]+";
|
||||
|
||||
let mut re1 = String::from("^") + prefix;
|
||||
@ -309,6 +361,7 @@ impl Resource {
|
||||
let mut param_pattern = String::from(DEFAULT_PATTERN);
|
||||
let mut is_dynamic = false;
|
||||
let mut elems = Vec::new();
|
||||
let mut len = 0;
|
||||
|
||||
for (index, ch) in pattern.chars().enumerate() {
|
||||
// All routes must have a leading slash so its optional to have one
|
||||
@ -325,6 +378,7 @@ impl Resource {
|
||||
param_name.clear();
|
||||
param_pattern = String::from(DEFAULT_PATTERN);
|
||||
|
||||
len = 0;
|
||||
in_param_pattern = false;
|
||||
in_param = false;
|
||||
} else if ch == ':' {
|
||||
@ -348,16 +402,19 @@ impl Resource {
|
||||
re1.push_str(escape(&ch.to_string()).as_str());
|
||||
re2.push(ch);
|
||||
el.push(ch);
|
||||
len += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let re = if is_dynamic {
|
||||
re1.push('$');
|
||||
if !for_prefix {
|
||||
re1.push('$');
|
||||
}
|
||||
re1
|
||||
} else {
|
||||
re2
|
||||
};
|
||||
(re, elems, is_dynamic)
|
||||
(re, elems, is_dynamic, len)
|
||||
}
|
||||
}
|
||||
|
||||
@ -570,6 +627,41 @@ mod tests {
|
||||
assert_eq!(req.match_info().get("id").unwrap(), "adahg32");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resource_prefix() {
|
||||
let re = Resource::prefix("test", "/name");
|
||||
assert!(re.is_match("/name"));
|
||||
assert!(re.is_match("/name/"));
|
||||
assert!(re.is_match("/name/test/test"));
|
||||
assert!(re.is_match("/name1"));
|
||||
assert!(re.is_match("/name~"));
|
||||
|
||||
let re = Resource::prefix("test", "/name/");
|
||||
assert!(re.is_match("/name/"));
|
||||
assert!(re.is_match("/name/gs"));
|
||||
assert!(!re.is_match("/name"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reousrce_prefix_dynamic() {
|
||||
let re = Resource::prefix("test", "/{name}/");
|
||||
assert!(re.is_match("/name/"));
|
||||
assert!(re.is_match("/name/gs"));
|
||||
assert!(!re.is_match("/name"));
|
||||
|
||||
let mut req = TestRequest::with_uri("/test2/").finish();
|
||||
assert!(re.match_with_params("/test2/", req.match_info_mut()));
|
||||
assert_eq!(&req.match_info()["name"], "test2");
|
||||
|
||||
let mut req =
|
||||
TestRequest::with_uri("/test2/subpath1/subpath2/index.html").finish();
|
||||
assert!(re.match_with_params(
|
||||
"/test2/subpath1/subpath2/index.html",
|
||||
req.match_info_mut()
|
||||
));
|
||||
assert_eq!(&req.match_info()["name"], "test2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_request_resource() {
|
||||
let routes = vec![
|
||||
|
1038
src/scope.rs
Normal file
1038
src/scope.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,13 +1,13 @@
|
||||
use std::net::{Shutdown, SocketAddr};
|
||||
use std::rc::Rc;
|
||||
use std::{io, mem, ptr, time};
|
||||
use std::{io, ptr, time};
|
||||
|
||||
use bytes::{Buf, BufMut, Bytes, BytesMut};
|
||||
use futures::{Async, Future, Poll};
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
|
||||
use super::settings::WorkerSettings;
|
||||
use super::{utils, HttpHandler, IoStream, h1, h2};
|
||||
use super::{h1, h2, utils, HttpHandler, IoStream};
|
||||
|
||||
const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0";
|
||||
|
||||
@ -112,7 +112,9 @@ where
|
||||
match result {
|
||||
Ok(Async::Ready(())) | Err(_) => {
|
||||
h1.settings().remove_channel();
|
||||
self.node.as_mut().map(|n| n.remove());
|
||||
if let Some(n) = self.node.as_mut() {
|
||||
n.remove()
|
||||
};
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
@ -123,7 +125,9 @@ where
|
||||
match result {
|
||||
Ok(Async::Ready(())) | Err(_) => {
|
||||
h2.settings().remove_channel();
|
||||
self.node.as_mut().map(|n| n.remove());
|
||||
if let Some(n) = self.node.as_mut() {
|
||||
n.remove()
|
||||
};
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
@ -139,7 +143,9 @@ where
|
||||
Ok(Async::Ready(0)) | Err(_) => {
|
||||
debug!("Ignored premature client disconnection");
|
||||
settings.remove_channel();
|
||||
self.node.as_mut().map(|n| n.remove());
|
||||
if let Some(n) = self.node.as_mut() {
|
||||
n.remove()
|
||||
};
|
||||
return Err(());
|
||||
}
|
||||
_ => (),
|
||||
@ -163,10 +169,7 @@ where
|
||||
match kind {
|
||||
ProtocolKind::Http1 => {
|
||||
self.proto = Some(HttpProtocol::H1(h1::Http1::new(
|
||||
settings,
|
||||
io,
|
||||
addr,
|
||||
buf,
|
||||
settings, io, addr, buf,
|
||||
)));
|
||||
return self.poll();
|
||||
}
|
||||
@ -204,13 +207,15 @@ impl<T> Node<T> {
|
||||
#[allow(mutable_transmutes)]
|
||||
unsafe {
|
||||
if let Some(ref next2) = self.next {
|
||||
let n: &mut Node<()> = mem::transmute(next2.as_ref().unwrap());
|
||||
let n: &mut Node<()> =
|
||||
&mut *(next2.as_ref().unwrap() as *const _ as *mut _);
|
||||
n.prev = Some(next as *const _ as *mut _);
|
||||
}
|
||||
let slf: &mut Node<T> = mem::transmute(self);
|
||||
let slf: &mut Node<T> = &mut *(self as *const _ as *mut _);
|
||||
|
||||
slf.next = Some(next as *const _ as *mut _);
|
||||
|
||||
let next: &mut Node<T> = mem::transmute(next);
|
||||
let next: &mut Node<T> = &mut *(next as *const _ as *mut _);
|
||||
next.prev = Some(slf as *const _ as *mut _);
|
||||
}
|
||||
}
|
||||
@ -246,12 +251,12 @@ impl Node<()> {
|
||||
loop {
|
||||
if let Some(n) = next {
|
||||
unsafe {
|
||||
let n: &Node<()> = mem::transmute(n.as_ref().unwrap());
|
||||
let n: &Node<()> = &*(n.as_ref().unwrap() as *const _);
|
||||
next = n.next.as_ref();
|
||||
|
||||
if !n.element.is_null() {
|
||||
let ch: &mut HttpChannel<T, H> =
|
||||
mem::transmute(&mut *(n.element as *mut _));
|
||||
&mut *(&mut *(n.element as *mut _) as *mut () as *mut _);
|
||||
ch.shutdown();
|
||||
}
|
||||
}
|
||||
|
@ -6,11 +6,14 @@ use std::{cmp, io, mem};
|
||||
#[cfg(feature = "brotli")]
|
||||
use brotli2::write::{BrotliDecoder, BrotliEncoder};
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use flate2::Compression;
|
||||
#[cfg(feature = "flate2")]
|
||||
use flate2::read::GzDecoder;
|
||||
#[cfg(feature = "flate2")]
|
||||
use flate2::write::{DeflateDecoder, DeflateEncoder, GzEncoder};
|
||||
use http::header::{HeaderMap, HeaderValue, ACCEPT_ENCODING, CONNECTION,
|
||||
CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING};
|
||||
#[cfg(feature = "flate2")]
|
||||
use flate2::Compression;
|
||||
use http::header::{HeaderMap, HeaderValue, ACCEPT_ENCODING, CONTENT_ENCODING,
|
||||
CONTENT_LENGTH, TRANSFER_ENCODING};
|
||||
use http::{HttpTryFrom, Method, Version};
|
||||
|
||||
use body::{Binary, Body};
|
||||
@ -144,7 +147,9 @@ impl PayloadWriter for EncodedPayload {
|
||||
}
|
||||
|
||||
pub(crate) enum Decoder {
|
||||
#[cfg(feature = "flate2")]
|
||||
Deflate(Box<DeflateDecoder<Writer>>),
|
||||
#[cfg(feature = "flate2")]
|
||||
Gzip(Option<Box<GzDecoder<Wrapper>>>),
|
||||
#[cfg(feature = "brotli")]
|
||||
Br(Box<BrotliDecoder<Writer>>),
|
||||
@ -223,9 +228,11 @@ impl PayloadStream {
|
||||
ContentEncoding::Br => {
|
||||
Decoder::Br(Box::new(BrotliDecoder::new(Writer::new())))
|
||||
}
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Deflate => {
|
||||
Decoder::Deflate(Box::new(DeflateDecoder::new(Writer::new())))
|
||||
}
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Gzip => Decoder::Gzip(None),
|
||||
_ => Decoder::Identity,
|
||||
};
|
||||
@ -251,6 +258,7 @@ impl PayloadStream {
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
#[cfg(feature = "flate2")]
|
||||
Decoder::Gzip(ref mut decoder) => {
|
||||
if let Some(ref mut decoder) = *decoder {
|
||||
decoder.as_mut().get_mut().eof = true;
|
||||
@ -267,6 +275,7 @@ impl PayloadStream {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "flate2")]
|
||||
Decoder::Deflate(ref mut decoder) => match decoder.try_finish() {
|
||||
Ok(_) => {
|
||||
let b = decoder.get_mut().take();
|
||||
@ -297,6 +306,7 @@ impl PayloadStream {
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
#[cfg(feature = "flate2")]
|
||||
Decoder::Gzip(ref mut decoder) => {
|
||||
if decoder.is_none() {
|
||||
*decoder = Some(Box::new(GzDecoder::new(Wrapper {
|
||||
@ -334,6 +344,7 @@ impl PayloadStream {
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "flate2")]
|
||||
Decoder::Deflate(ref mut decoder) => match decoder.write_all(&data) {
|
||||
Ok(_) => {
|
||||
decoder.flush()?;
|
||||
@ -352,7 +363,9 @@ impl PayloadStream {
|
||||
}
|
||||
|
||||
pub(crate) enum ContentEncoder {
|
||||
#[cfg(feature = "flate2")]
|
||||
Deflate(DeflateEncoder<TransferEncoding>),
|
||||
#[cfg(feature = "flate2")]
|
||||
Gzip(GzEncoder<TransferEncoding>),
|
||||
#[cfg(feature = "brotli")]
|
||||
Br(BrotliEncoder<TransferEncoding>),
|
||||
@ -422,9 +435,11 @@ impl ContentEncoder {
|
||||
let tmp = SharedBytes::default();
|
||||
let transfer = TransferEncoding::eof(tmp.clone());
|
||||
let mut enc = match encoding {
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Deflate => ContentEncoder::Deflate(
|
||||
DeflateEncoder::new(transfer, Compression::fast()),
|
||||
),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new(
|
||||
transfer,
|
||||
Compression::fast(),
|
||||
@ -459,9 +474,6 @@ impl ContentEncoder {
|
||||
if resp.upgrade() {
|
||||
if version == Version::HTTP_2 {
|
||||
error!("Connection upgrade is forbidden for HTTP/2");
|
||||
} else {
|
||||
resp.headers_mut()
|
||||
.insert(CONNECTION, HeaderValue::from_static("upgrade"));
|
||||
}
|
||||
if encoding != ContentEncoding::Identity {
|
||||
encoding = ContentEncoding::Identity;
|
||||
@ -481,10 +493,12 @@ impl ContentEncoder {
|
||||
}
|
||||
|
||||
match encoding {
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Deflate => ContentEncoder::Deflate(DeflateEncoder::new(
|
||||
transfer,
|
||||
Compression::fast(),
|
||||
)),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Gzip => {
|
||||
ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::fast()))
|
||||
}
|
||||
@ -497,7 +511,7 @@ impl ContentEncoder {
|
||||
}
|
||||
|
||||
fn streaming_encoding(
|
||||
buf: SharedBytes, version: Version, resp: &mut HttpResponse
|
||||
buf: SharedBytes, version: Version, resp: &mut HttpResponse,
|
||||
) -> TransferEncoding {
|
||||
match resp.chunked() {
|
||||
Some(true) => {
|
||||
@ -566,7 +580,9 @@ impl ContentEncoder {
|
||||
match *self {
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoder::Br(ref encoder) => encoder.get_ref().is_eof(),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Deflate(ref encoder) => encoder.get_ref().is_eof(),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Gzip(ref encoder) => encoder.get_ref().is_eof(),
|
||||
ContentEncoder::Identity(ref encoder) => encoder.is_eof(),
|
||||
}
|
||||
@ -590,6 +606,7 @@ impl ContentEncoder {
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Gzip(encoder) => match encoder.finish() {
|
||||
Ok(mut writer) => {
|
||||
writer.encode_eof();
|
||||
@ -598,6 +615,7 @@ impl ContentEncoder {
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Deflate(encoder) => match encoder.finish() {
|
||||
Ok(mut writer) => {
|
||||
writer.encode_eof();
|
||||
@ -628,6 +646,7 @@ impl ContentEncoder {
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Gzip(ref mut encoder) => {
|
||||
match encoder.write_all(data.as_ref()) {
|
||||
Ok(_) => Ok(()),
|
||||
@ -637,6 +656,7 @@ impl ContentEncoder {
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Deflate(ref mut encoder) => {
|
||||
match encoder.write_all(data.as_ref()) {
|
||||
Ok(_) => Ok(()),
|
||||
|
1329
src/server/h1.rs
1329
src/server/h1.rs
File diff suppressed because it is too large
Load Diff
491
src/server/h1decoder.rs
Normal file
491
src/server/h1decoder.rs
Normal file
@ -0,0 +1,491 @@
|
||||
use std::{io, mem};
|
||||
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::{Async, Poll};
|
||||
use httparse;
|
||||
|
||||
use super::helpers::SharedHttpInnerMessage;
|
||||
use super::settings::WorkerSettings;
|
||||
use error::ParseError;
|
||||
use http::header::{HeaderName, HeaderValue};
|
||||
use http::{header, HttpTryFrom, Method, Uri, Version};
|
||||
use httprequest::MessageFlags;
|
||||
use uri::Url;
|
||||
|
||||
const MAX_BUFFER_SIZE: usize = 131_072;
|
||||
const MAX_HEADERS: usize = 96;
|
||||
|
||||
pub(crate) struct H1Decoder {
|
||||
decoder: Option<EncodingDecoder>,
|
||||
}
|
||||
|
||||
pub(crate) enum Message {
|
||||
Message {
|
||||
msg: SharedHttpInnerMessage,
|
||||
payload: bool,
|
||||
},
|
||||
Chunk(Bytes),
|
||||
Eof,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum DecoderError {
|
||||
Io(io::Error),
|
||||
Error(ParseError),
|
||||
}
|
||||
|
||||
impl From<io::Error> for DecoderError {
|
||||
fn from(err: io::Error) -> DecoderError {
|
||||
DecoderError::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl H1Decoder {
|
||||
pub fn new() -> H1Decoder {
|
||||
H1Decoder { decoder: None }
|
||||
}
|
||||
|
||||
pub fn decode<H>(
|
||||
&mut self, src: &mut BytesMut, settings: &WorkerSettings<H>,
|
||||
) -> Result<Option<Message>, DecoderError> {
|
||||
// read payload
|
||||
if self.decoder.is_some() {
|
||||
match self.decoder.as_mut().unwrap().decode(src)? {
|
||||
Async::Ready(Some(bytes)) => return Ok(Some(Message::Chunk(bytes))),
|
||||
Async::Ready(None) => {
|
||||
self.decoder.take();
|
||||
return Ok(Some(Message::Eof));
|
||||
}
|
||||
Async::NotReady => return Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
match self.parse_message(src, settings)
|
||||
.map_err(DecoderError::Error)?
|
||||
{
|
||||
Async::Ready((msg, decoder)) => {
|
||||
if let Some(decoder) = decoder {
|
||||
self.decoder = Some(decoder);
|
||||
Ok(Some(Message::Message {
|
||||
msg,
|
||||
payload: true,
|
||||
}))
|
||||
} else {
|
||||
Ok(Some(Message::Message {
|
||||
msg,
|
||||
payload: false,
|
||||
}))
|
||||
}
|
||||
}
|
||||
Async::NotReady => {
|
||||
if src.len() >= MAX_BUFFER_SIZE {
|
||||
error!("MAX_BUFFER_SIZE unprocessed data reached, closing");
|
||||
Err(DecoderError::Error(ParseError::TooLarge))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_message<H>(
|
||||
&self, buf: &mut BytesMut, settings: &WorkerSettings<H>,
|
||||
) -> Poll<(SharedHttpInnerMessage, Option<EncodingDecoder>), ParseError> {
|
||||
// Parse http message
|
||||
let mut has_upgrade = false;
|
||||
let mut chunked = false;
|
||||
let mut content_length = None;
|
||||
|
||||
let msg = {
|
||||
let bytes_ptr = buf.as_ref().as_ptr() as usize;
|
||||
let mut headers: [httparse::Header; MAX_HEADERS] =
|
||||
unsafe { mem::uninitialized() };
|
||||
|
||||
let (len, method, path, version, headers_len) = {
|
||||
let b = unsafe {
|
||||
let b: &[u8] = buf;
|
||||
&*(b as *const [u8])
|
||||
};
|
||||
let mut req = httparse::Request::new(&mut headers);
|
||||
match req.parse(b)? {
|
||||
httparse::Status::Complete(len) => {
|
||||
let method = Method::from_bytes(req.method.unwrap().as_bytes())
|
||||
.map_err(|_| ParseError::Method)?;
|
||||
let path = Url::new(Uri::try_from(req.path.unwrap())?);
|
||||
let version = if req.version.unwrap() == 1 {
|
||||
Version::HTTP_11
|
||||
} else {
|
||||
Version::HTTP_10
|
||||
};
|
||||
(len, method, path, version, req.headers.len())
|
||||
}
|
||||
httparse::Status::Partial => return Ok(Async::NotReady),
|
||||
}
|
||||
};
|
||||
|
||||
let slice = buf.split_to(len).freeze();
|
||||
|
||||
// convert headers
|
||||
let msg = settings.get_http_message();
|
||||
{
|
||||
let msg_mut = msg.get_mut();
|
||||
msg_mut
|
||||
.flags
|
||||
.set(MessageFlags::KEEPALIVE, version != Version::HTTP_10);
|
||||
|
||||
for header in headers[..headers_len].iter() {
|
||||
if let Ok(name) = HeaderName::from_bytes(header.name.as_bytes()) {
|
||||
has_upgrade = has_upgrade || name == header::UPGRADE;
|
||||
|
||||
let v_start = header.value.as_ptr() as usize - bytes_ptr;
|
||||
let v_end = v_start + header.value.len();
|
||||
let value = unsafe {
|
||||
HeaderValue::from_shared_unchecked(
|
||||
slice.slice(v_start, v_end),
|
||||
)
|
||||
};
|
||||
match name {
|
||||
header::CONTENT_LENGTH => {
|
||||
if let Ok(s) = value.to_str() {
|
||||
if let Ok(len) = s.parse::<u64>() {
|
||||
content_length = Some(len)
|
||||
} else {
|
||||
debug!("illegal Content-Length: {:?}", len);
|
||||
return Err(ParseError::Header);
|
||||
}
|
||||
} else {
|
||||
debug!("illegal Content-Length: {:?}", len);
|
||||
return Err(ParseError::Header);
|
||||
}
|
||||
}
|
||||
// transfer-encoding
|
||||
header::TRANSFER_ENCODING => {
|
||||
if let Ok(s) = value.to_str() {
|
||||
chunked = s.to_lowercase().contains("chunked");
|
||||
} else {
|
||||
return Err(ParseError::Header);
|
||||
}
|
||||
}
|
||||
// connection keep-alive state
|
||||
header::CONNECTION => {
|
||||
let ka = if let Ok(conn) = value.to_str() {
|
||||
if version == Version::HTTP_10
|
||||
&& conn.contains("keep-alive")
|
||||
{
|
||||
true
|
||||
} else {
|
||||
version == Version::HTTP_11
|
||||
&& !(conn.contains("close")
|
||||
|| conn.contains("upgrade"))
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
msg_mut.flags.set(MessageFlags::KEEPALIVE, ka);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
msg_mut.headers.append(name, value);
|
||||
} else {
|
||||
return Err(ParseError::Header);
|
||||
}
|
||||
}
|
||||
|
||||
msg_mut.url = path;
|
||||
msg_mut.method = method;
|
||||
msg_mut.version = version;
|
||||
}
|
||||
msg
|
||||
};
|
||||
|
||||
// https://tools.ietf.org/html/rfc7230#section-3.3.3
|
||||
let decoder = if chunked {
|
||||
// Chunked encoding
|
||||
Some(EncodingDecoder::chunked())
|
||||
} else if let Some(len) = content_length {
|
||||
// Content-Length
|
||||
Some(EncodingDecoder::length(len))
|
||||
} else if has_upgrade || msg.get_ref().method == Method::CONNECT {
|
||||
// upgrade(websocket) or connect
|
||||
Some(EncodingDecoder::eof())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Async::Ready((msg, decoder)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Decoders to handle different Transfer-Encodings.
|
||||
///
|
||||
/// If a message body does not include a Transfer-Encoding, it *should*
|
||||
/// include a Content-Length header.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct EncodingDecoder {
|
||||
kind: Kind,
|
||||
}
|
||||
|
||||
impl EncodingDecoder {
|
||||
pub fn length(x: u64) -> EncodingDecoder {
|
||||
EncodingDecoder {
|
||||
kind: Kind::Length(x),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn chunked() -> EncodingDecoder {
|
||||
EncodingDecoder {
|
||||
kind: Kind::Chunked(ChunkedState::Size, 0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eof() -> EncodingDecoder {
|
||||
EncodingDecoder {
|
||||
kind: Kind::Eof(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
enum Kind {
|
||||
/// A Reader used when a Content-Length header is passed with a positive
|
||||
/// integer.
|
||||
Length(u64),
|
||||
/// A Reader used when Transfer-Encoding is `chunked`.
|
||||
Chunked(ChunkedState, u64),
|
||||
/// A Reader used for responses that don't indicate a length or chunked.
|
||||
///
|
||||
/// Note: This should only used for `Response`s. It is illegal for a
|
||||
/// `Request` to be made with both `Content-Length` and
|
||||
/// `Transfer-Encoding: chunked` missing, as explained from the spec:
|
||||
///
|
||||
/// > If a Transfer-Encoding header field is present in a response and
|
||||
/// > the chunked transfer coding is not the final encoding, the
|
||||
/// > message body length is determined by reading the connection until
|
||||
/// > it is closed by the server. If a Transfer-Encoding header field
|
||||
/// > is present in a request and the chunked transfer coding is not
|
||||
/// > the final encoding, the message body length cannot be determined
|
||||
/// > reliably; the server MUST respond with the 400 (Bad Request)
|
||||
/// > status code and then close the connection.
|
||||
Eof(bool),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
enum ChunkedState {
|
||||
Size,
|
||||
SizeLws,
|
||||
Extension,
|
||||
SizeLf,
|
||||
Body,
|
||||
BodyCr,
|
||||
BodyLf,
|
||||
EndCr,
|
||||
EndLf,
|
||||
End,
|
||||
}
|
||||
|
||||
impl EncodingDecoder {
|
||||
pub fn decode(&mut self, body: &mut BytesMut) -> Poll<Option<Bytes>, io::Error> {
|
||||
match self.kind {
|
||||
Kind::Length(ref mut remaining) => {
|
||||
if *remaining == 0 {
|
||||
Ok(Async::Ready(None))
|
||||
} else {
|
||||
if body.is_empty() {
|
||||
return Ok(Async::NotReady);
|
||||
}
|
||||
let len = body.len() as u64;
|
||||
let buf;
|
||||
if *remaining > len {
|
||||
buf = body.take().freeze();
|
||||
*remaining -= len;
|
||||
} else {
|
||||
buf = body.split_to(*remaining as usize).freeze();
|
||||
*remaining = 0;
|
||||
}
|
||||
trace!("Length read: {}", buf.len());
|
||||
Ok(Async::Ready(Some(buf)))
|
||||
}
|
||||
}
|
||||
Kind::Chunked(ref mut state, ref mut size) => {
|
||||
loop {
|
||||
let mut buf = None;
|
||||
// advances the chunked state
|
||||
*state = try_ready!(state.step(body, size, &mut buf));
|
||||
if *state == ChunkedState::End {
|
||||
trace!("End of chunked stream");
|
||||
return Ok(Async::Ready(None));
|
||||
}
|
||||
if let Some(buf) = buf {
|
||||
return Ok(Async::Ready(Some(buf)));
|
||||
}
|
||||
if body.is_empty() {
|
||||
return Ok(Async::NotReady);
|
||||
}
|
||||
}
|
||||
}
|
||||
Kind::Eof(ref mut is_eof) => {
|
||||
if *is_eof {
|
||||
Ok(Async::Ready(None))
|
||||
} else if !body.is_empty() {
|
||||
Ok(Async::Ready(Some(body.take().freeze())))
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! byte (
|
||||
($rdr:ident) => ({
|
||||
if $rdr.len() > 0 {
|
||||
let b = $rdr[0];
|
||||
$rdr.split_to(1);
|
||||
b
|
||||
} else {
|
||||
return Ok(Async::NotReady)
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
impl ChunkedState {
|
||||
fn step(
|
||||
&self, body: &mut BytesMut, size: &mut u64, buf: &mut Option<Bytes>,
|
||||
) -> Poll<ChunkedState, io::Error> {
|
||||
use self::ChunkedState::*;
|
||||
match *self {
|
||||
Size => ChunkedState::read_size(body, size),
|
||||
SizeLws => ChunkedState::read_size_lws(body),
|
||||
Extension => ChunkedState::read_extension(body),
|
||||
SizeLf => ChunkedState::read_size_lf(body, size),
|
||||
Body => ChunkedState::read_body(body, size, buf),
|
||||
BodyCr => ChunkedState::read_body_cr(body),
|
||||
BodyLf => ChunkedState::read_body_lf(body),
|
||||
EndCr => ChunkedState::read_end_cr(body),
|
||||
EndLf => ChunkedState::read_end_lf(body),
|
||||
End => Ok(Async::Ready(ChunkedState::End)),
|
||||
}
|
||||
}
|
||||
fn read_size(rdr: &mut BytesMut, size: &mut u64) -> Poll<ChunkedState, io::Error> {
|
||||
let radix = 16;
|
||||
match byte!(rdr) {
|
||||
b @ b'0'...b'9' => {
|
||||
*size *= radix;
|
||||
*size += u64::from(b - b'0');
|
||||
}
|
||||
b @ b'a'...b'f' => {
|
||||
*size *= radix;
|
||||
*size += u64::from(b + 10 - b'a');
|
||||
}
|
||||
b @ b'A'...b'F' => {
|
||||
*size *= radix;
|
||||
*size += u64::from(b + 10 - b'A');
|
||||
}
|
||||
b'\t' | b' ' => return Ok(Async::Ready(ChunkedState::SizeLws)),
|
||||
b';' => return Ok(Async::Ready(ChunkedState::Extension)),
|
||||
b'\r' => return Ok(Async::Ready(ChunkedState::SizeLf)),
|
||||
_ => {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Invalid chunk size line: Invalid Size",
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(Async::Ready(ChunkedState::Size))
|
||||
}
|
||||
fn read_size_lws(rdr: &mut BytesMut) -> Poll<ChunkedState, io::Error> {
|
||||
trace!("read_size_lws");
|
||||
match byte!(rdr) {
|
||||
// LWS can follow the chunk size, but no more digits can come
|
||||
b'\t' | b' ' => Ok(Async::Ready(ChunkedState::SizeLws)),
|
||||
b';' => Ok(Async::Ready(ChunkedState::Extension)),
|
||||
b'\r' => Ok(Async::Ready(ChunkedState::SizeLf)),
|
||||
_ => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Invalid chunk size linear white space",
|
||||
)),
|
||||
}
|
||||
}
|
||||
fn read_extension(rdr: &mut BytesMut) -> Poll<ChunkedState, io::Error> {
|
||||
match byte!(rdr) {
|
||||
b'\r' => Ok(Async::Ready(ChunkedState::SizeLf)),
|
||||
_ => Ok(Async::Ready(ChunkedState::Extension)), // no supported extensions
|
||||
}
|
||||
}
|
||||
fn read_size_lf(
|
||||
rdr: &mut BytesMut, size: &mut u64,
|
||||
) -> Poll<ChunkedState, io::Error> {
|
||||
match byte!(rdr) {
|
||||
b'\n' if *size > 0 => Ok(Async::Ready(ChunkedState::Body)),
|
||||
b'\n' if *size == 0 => Ok(Async::Ready(ChunkedState::EndCr)),
|
||||
_ => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Invalid chunk size LF",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_body(
|
||||
rdr: &mut BytesMut, rem: &mut u64, buf: &mut Option<Bytes>,
|
||||
) -> Poll<ChunkedState, io::Error> {
|
||||
trace!("Chunked read, remaining={:?}", rem);
|
||||
|
||||
let len = rdr.len() as u64;
|
||||
if len == 0 {
|
||||
Ok(Async::Ready(ChunkedState::Body))
|
||||
} else {
|
||||
let slice;
|
||||
if *rem > len {
|
||||
slice = rdr.take().freeze();
|
||||
*rem -= len;
|
||||
} else {
|
||||
slice = rdr.split_to(*rem as usize).freeze();
|
||||
*rem = 0;
|
||||
}
|
||||
*buf = Some(slice);
|
||||
if *rem > 0 {
|
||||
Ok(Async::Ready(ChunkedState::Body))
|
||||
} else {
|
||||
Ok(Async::Ready(ChunkedState::BodyCr))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_body_cr(rdr: &mut BytesMut) -> Poll<ChunkedState, io::Error> {
|
||||
match byte!(rdr) {
|
||||
b'\r' => Ok(Async::Ready(ChunkedState::BodyLf)),
|
||||
_ => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Invalid chunk body CR",
|
||||
)),
|
||||
}
|
||||
}
|
||||
fn read_body_lf(rdr: &mut BytesMut) -> Poll<ChunkedState, io::Error> {
|
||||
match byte!(rdr) {
|
||||
b'\n' => Ok(Async::Ready(ChunkedState::Size)),
|
||||
_ => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Invalid chunk body LF",
|
||||
)),
|
||||
}
|
||||
}
|
||||
fn read_end_cr(rdr: &mut BytesMut) -> Poll<ChunkedState, io::Error> {
|
||||
match byte!(rdr) {
|
||||
b'\r' => Ok(Async::Ready(ChunkedState::EndLf)),
|
||||
_ => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Invalid chunk end CR",
|
||||
)),
|
||||
}
|
||||
}
|
||||
fn read_end_lf(rdr: &mut BytesMut) -> Poll<ChunkedState, io::Error> {
|
||||
match byte!(rdr) {
|
||||
b'\n' => Ok(Async::Ready(ChunkedState::End)),
|
||||
_ => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Invalid chunk end LF",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
@ -2,10 +2,8 @@
|
||||
|
||||
use bytes::BufMut;
|
||||
use futures::{Async, Poll};
|
||||
use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE};
|
||||
use http::{Method, Version};
|
||||
use std::io;
|
||||
use std::rc::Rc;
|
||||
use std::{io, mem};
|
||||
use tokio_io::AsyncWrite;
|
||||
|
||||
use super::encoding::ContentEncoder;
|
||||
@ -15,6 +13,8 @@ use super::shared::SharedBytes;
|
||||
use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE};
|
||||
use body::{Binary, Body};
|
||||
use header::ContentEncoding;
|
||||
use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE};
|
||||
use http::{Method, Version};
|
||||
use httprequest::HttpInnerMessage;
|
||||
use httpresponse::HttpResponse;
|
||||
|
||||
@ -42,7 +42,7 @@ pub(crate) struct H1Writer<T: AsyncWrite, H: 'static> {
|
||||
|
||||
impl<T: AsyncWrite, H: 'static> H1Writer<T, H> {
|
||||
pub fn new(
|
||||
stream: T, buf: SharedBytes, settings: Rc<WorkerSettings<H>>
|
||||
stream: T, buf: SharedBytes, settings: Rc<WorkerSettings<H>>,
|
||||
) -> H1Writer<T, H> {
|
||||
H1Writer {
|
||||
flags: Flags::empty(),
|
||||
@ -169,7 +169,7 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
|
||||
let mut pos = 0;
|
||||
let mut has_date = false;
|
||||
let mut remaining = buffer.remaining_mut();
|
||||
let mut buf: &mut [u8] = unsafe { mem::transmute(buffer.bytes_mut()) };
|
||||
let mut buf = unsafe { &mut *(buffer.bytes_mut() as *mut [u8]) };
|
||||
for (key, value) in msg.headers() {
|
||||
if is_bin && key == CONTENT_LENGTH {
|
||||
is_bin = false;
|
||||
@ -184,7 +184,7 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
|
||||
pos = 0;
|
||||
buffer.reserve(len);
|
||||
remaining = buffer.remaining_mut();
|
||||
buf = unsafe { mem::transmute(buffer.bytes_mut()) };
|
||||
buf = unsafe { &mut *(buffer.bytes_mut() as *mut _) };
|
||||
}
|
||||
|
||||
buf[pos..pos + k.len()].copy_from_slice(k);
|
||||
@ -272,7 +272,8 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
|
||||
#[inline]
|
||||
fn poll_completed(&mut self, shutdown: bool) -> Poll<(), io::Error> {
|
||||
if !self.buffer.is_empty() {
|
||||
let buf: &[u8] = unsafe { mem::transmute(self.buffer.as_ref()) };
|
||||
let buf: &[u8] =
|
||||
unsafe { &mut *(self.buffer.as_ref() as *const _ as *mut _) };
|
||||
let written = self.write_data(buf)?;
|
||||
let _ = self.buffer.split_to(written);
|
||||
if self.buffer.len() > self.buffer_capacity {
|
||||
|
@ -61,7 +61,7 @@ where
|
||||
H: HttpHandler + 'static,
|
||||
{
|
||||
pub fn new(
|
||||
settings: Rc<WorkerSettings<H>>, io: T, addr: Option<SocketAddr>, buf: Bytes
|
||||
settings: Rc<WorkerSettings<H>>, io: T, addr: Option<SocketAddr>, buf: Bytes,
|
||||
) -> Self {
|
||||
Http2 {
|
||||
flags: Flags::empty(),
|
||||
|
@ -45,7 +45,7 @@ pub(crate) struct H2Writer<H: 'static> {
|
||||
|
||||
impl<H: 'static> H2Writer<H> {
|
||||
pub fn new(
|
||||
respond: SendResponse<Bytes>, buf: SharedBytes, settings: Rc<WorkerSettings<H>>
|
||||
respond: SendResponse<Bytes>, buf: SharedBytes, settings: Rc<WorkerSettings<H>>,
|
||||
) -> H2Writer<H> {
|
||||
H2Writer {
|
||||
respond,
|
||||
|
@ -69,7 +69,7 @@ impl SharedHttpInnerMessage {
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
msg: Rc<HttpInnerMessage>, pool: Rc<SharedMessagePool>
|
||||
msg: Rc<HttpInnerMessage>, pool: Rc<SharedMessagePool>,
|
||||
) -> SharedHttpInnerMessage {
|
||||
SharedHttpInnerMessage(Some(msg), Some(pool))
|
||||
}
|
||||
@ -79,7 +79,7 @@ impl SharedHttpInnerMessage {
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))]
|
||||
pub fn get_mut(&self) -> &mut HttpInnerMessage {
|
||||
let r: &HttpInnerMessage = self.0.as_ref().unwrap().as_ref();
|
||||
unsafe { mem::transmute(r) }
|
||||
unsafe { &mut *(r as *const _ as *mut _) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@ -97,7 +97,7 @@ const DEC_DIGITS_LUT: &[u8] = b"0001020304050607080910111213141516171819\
|
||||
|
||||
pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesMut) {
|
||||
let mut buf: [u8; 13] = [
|
||||
b'H', b'T', b'T', b'P', b'/', b'1', b'.', b'1', b' ', b' ', b' ', b' ', b' '
|
||||
b'H', b'T', b'T', b'P', b'/', b'1', b'.', b'1', b' ', b' ', b' ', b' ', b' ',
|
||||
];
|
||||
match version {
|
||||
Version::HTTP_2 => buf[5] = b'2',
|
||||
|
@ -10,6 +10,7 @@ use tokio_io::{AsyncRead, AsyncWrite};
|
||||
mod channel;
|
||||
pub(crate) mod encoding;
|
||||
pub(crate) mod h1;
|
||||
pub(crate) mod h1decoder;
|
||||
mod h1writer;
|
||||
mod h2;
|
||||
mod h2writer;
|
||||
|
@ -8,10 +8,10 @@ use std::sync::Arc;
|
||||
use std::{fmt, mem, net};
|
||||
use time;
|
||||
|
||||
use super::KeepAlive;
|
||||
use super::channel::Node;
|
||||
use super::helpers;
|
||||
use super::shared::{SharedBytes, SharedBytesPool};
|
||||
use super::KeepAlive;
|
||||
use body::Body;
|
||||
use httpresponse::{HttpResponse, HttpResponseBuilder, HttpResponsePool};
|
||||
|
||||
@ -72,7 +72,7 @@ impl Default for ServerSettings {
|
||||
impl ServerSettings {
|
||||
/// Crate server settings instance
|
||||
pub(crate) fn new(
|
||||
addr: Option<net::SocketAddr>, host: &Option<String>, secure: bool
|
||||
addr: Option<net::SocketAddr>, host: &Option<String>, secure: bool,
|
||||
) -> ServerSettings {
|
||||
let host = if let Some(ref host) = *host {
|
||||
host.clone()
|
||||
@ -119,7 +119,7 @@ impl ServerSettings {
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn get_response_builder(
|
||||
&self, status: StatusCode
|
||||
&self, status: StatusCode,
|
||||
) -> HttpResponseBuilder {
|
||||
HttpResponsePool::get_builder(&self.responses, status)
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
use std::io;
|
||||
use std::rc::Rc;
|
||||
use std::{io, mem};
|
||||
|
||||
use body::Binary;
|
||||
|
||||
@ -61,7 +61,7 @@ impl SharedBytes {
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))]
|
||||
pub(crate) fn get_mut(&self) -> &mut BytesMut {
|
||||
let r: &BytesMut = self.0.as_ref().unwrap().as_ref();
|
||||
unsafe { mem::transmute(r) }
|
||||
unsafe { &mut *(r as *const _ as *mut _) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -10,6 +10,7 @@ use futures::{Future, Sink, Stream};
|
||||
use mio;
|
||||
use net2::TcpBuilder;
|
||||
use num_cpus;
|
||||
use slab::Slab;
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
@ -20,7 +21,7 @@ use openssl::ssl::{AlpnError, SslAcceptorBuilder};
|
||||
|
||||
use super::channel::{HttpChannel, WrapperStream};
|
||||
use super::settings::{ServerSettings, WorkerSettings};
|
||||
use super::worker::{Conn, StopWorker, StreamHandlerType, Worker};
|
||||
use super::worker::{Conn, SocketInfo, StopWorker, StreamHandlerType, Worker};
|
||||
use super::{IntoHttpHandler, IoStream, KeepAlive};
|
||||
use super::{PauseServer, ResumeServer, StopServer};
|
||||
|
||||
@ -37,7 +38,7 @@ where
|
||||
factory: Arc<Fn() -> Vec<H> + Send + Sync>,
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(type_complexity))]
|
||||
workers: Vec<(usize, Addr<Syn, Worker<H::Handler>>)>,
|
||||
sockets: Vec<(net::SocketAddr, net::TcpListener)>,
|
||||
sockets: Vec<Socket>,
|
||||
accept: Vec<(mio::SetReadiness, sync_mpsc::Sender<Command>)>,
|
||||
exit: bool,
|
||||
shutdown_timeout: u16,
|
||||
@ -57,14 +58,8 @@ where
|
||||
{
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Info {
|
||||
addr: net::SocketAddr,
|
||||
handler: StreamHandlerType,
|
||||
}
|
||||
|
||||
enum ServerCommand {
|
||||
WorkerDied(usize, Info),
|
||||
WorkerDied(usize, Slab<SocketInfo>),
|
||||
}
|
||||
|
||||
impl<H> Actor for HttpServer<H>
|
||||
@ -74,6 +69,12 @@ where
|
||||
type Context = Context<Self>;
|
||||
}
|
||||
|
||||
struct Socket {
|
||||
lst: net::TcpListener,
|
||||
addr: net::SocketAddr,
|
||||
tp: StreamHandlerType,
|
||||
}
|
||||
|
||||
impl<H> HttpServer<H>
|
||||
where
|
||||
H: IntoHttpHandler + 'static,
|
||||
@ -108,11 +109,17 @@ where
|
||||
///
|
||||
/// By default http server uses number of available logical cpu as threads
|
||||
/// count.
|
||||
pub fn threads(mut self, num: usize) -> Self {
|
||||
pub fn workers(mut self, num: usize) -> Self {
|
||||
self.threads = num;
|
||||
self
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[deprecated(since = "0.6.0", note = "please use `HttpServer::workers()` instead")]
|
||||
pub fn threads(self, num: usize) -> Self {
|
||||
self.workers(num)
|
||||
}
|
||||
|
||||
/// Set the maximum number of pending connections.
|
||||
///
|
||||
/// This refers to the number of clients that can be waiting to be served.
|
||||
@ -187,7 +194,7 @@ where
|
||||
|
||||
/// Get addresses of bound sockets.
|
||||
pub fn addrs(&self) -> Vec<net::SocketAddr> {
|
||||
self.sockets.iter().map(|s| s.0).collect()
|
||||
self.sockets.iter().map(|s| s.addr).collect()
|
||||
}
|
||||
|
||||
/// Use listener for accepting incoming connection requests
|
||||
@ -195,21 +202,29 @@ where
|
||||
/// HttpServer does not change any configuration for TcpListener,
|
||||
/// it needs to be configured before passing it to listen() method.
|
||||
pub fn listen(mut self, lst: net::TcpListener) -> Self {
|
||||
self.sockets.push((lst.local_addr().unwrap(), lst));
|
||||
let addr = lst.local_addr().unwrap();
|
||||
self.sockets.push(Socket {
|
||||
addr,
|
||||
lst,
|
||||
tp: StreamHandlerType::Normal,
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
/// The socket address to bind
|
||||
///
|
||||
/// To mind multiple addresses this method can be call multiple times.
|
||||
pub fn bind<S: net::ToSocketAddrs>(mut self, addr: S) -> io::Result<Self> {
|
||||
fn bind2<S: net::ToSocketAddrs>(&mut self, addr: S) -> io::Result<Vec<Socket>> {
|
||||
let mut err = None;
|
||||
let mut succ = false;
|
||||
let mut sockets = Vec::new();
|
||||
for addr in addr.to_socket_addrs()? {
|
||||
match create_tcp_listener(addr, self.backlog) {
|
||||
Ok(lst) => {
|
||||
succ = true;
|
||||
self.sockets.push((lst.local_addr().unwrap(), lst));
|
||||
let addr = lst.local_addr().unwrap();
|
||||
sockets.push(Socket {
|
||||
lst,
|
||||
addr,
|
||||
tp: StreamHandlerType::Normal,
|
||||
});
|
||||
}
|
||||
Err(e) => err = Some(e),
|
||||
}
|
||||
@ -225,12 +240,65 @@ where
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Ok(self)
|
||||
Ok(sockets)
|
||||
}
|
||||
}
|
||||
|
||||
/// The socket address to bind
|
||||
///
|
||||
/// To mind multiple addresses this method can be call multiple times.
|
||||
pub fn bind<S: net::ToSocketAddrs>(mut self, addr: S) -> io::Result<Self> {
|
||||
let sockets = self.bind2(addr)?;
|
||||
self.sockets.extend(sockets);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
/// The ssl socket address to bind
|
||||
///
|
||||
/// To mind multiple addresses this method can be call multiple times.
|
||||
pub fn bind_tls<S: net::ToSocketAddrs>(
|
||||
mut self, addr: S, acceptor: TlsAcceptor,
|
||||
) -> io::Result<Self> {
|
||||
let sockets = self.bind2(addr)?;
|
||||
self.sockets.extend(sockets.into_iter().map(|mut s| {
|
||||
s.tp = StreamHandlerType::Tls(acceptor.clone());
|
||||
s
|
||||
}));
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
#[cfg(feature = "alpn")]
|
||||
/// Start listening for incoming tls connections.
|
||||
///
|
||||
/// This method sets alpn protocols to "h2" and "http/1.1"
|
||||
pub fn bind_ssl<S: net::ToSocketAddrs>(
|
||||
mut self, addr: S, mut builder: SslAcceptorBuilder,
|
||||
) -> io::Result<Self> {
|
||||
// alpn support
|
||||
if !self.no_http2 {
|
||||
builder.set_alpn_protos(b"\x02h2\x08http/1.1")?;
|
||||
builder.set_alpn_select_callback(|_, protos| {
|
||||
const H2: &[u8] = b"\x02h2";
|
||||
if protos.windows(3).any(|window| window == H2) {
|
||||
Ok(b"h2")
|
||||
} else {
|
||||
Err(AlpnError::NOACK)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let acceptor = builder.build();
|
||||
let sockets = self.bind2(addr)?;
|
||||
self.sockets.extend(sockets.into_iter().map(|mut s| {
|
||||
s.tp = StreamHandlerType::Alpn(acceptor.clone());
|
||||
s
|
||||
}));
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn start_workers(
|
||||
&mut self, settings: &ServerSettings, handler: &StreamHandlerType
|
||||
&mut self, settings: &ServerSettings, sockets: &Slab<SocketInfo>,
|
||||
) -> Vec<(usize, mpsc::UnboundedSender<Conn<net::TcpStream>>)> {
|
||||
// start workers
|
||||
let mut workers = Vec::new();
|
||||
@ -238,8 +306,8 @@ where
|
||||
let s = settings.clone();
|
||||
let (tx, rx) = mpsc::unbounded::<Conn<net::TcpStream>>();
|
||||
|
||||
let h = handler.clone();
|
||||
let ka = self.keep_alive;
|
||||
let socks = sockets.clone();
|
||||
let factory = Arc::clone(&self.factory);
|
||||
let addr = Arbiter::start(move |ctx: &mut Context<_>| {
|
||||
let apps: Vec<_> = (*factory)()
|
||||
@ -247,7 +315,7 @@ where
|
||||
.map(|h| h.into_handler(s.clone()))
|
||||
.collect();
|
||||
ctx.add_message_stream(rx);
|
||||
Worker::new(apps, h, ka)
|
||||
Worker::new(apps, socks, ka)
|
||||
});
|
||||
workers.push((idx, tx));
|
||||
self.workers.push((idx, addr));
|
||||
@ -304,24 +372,32 @@ impl<H: IntoHttpHandler> HttpServer<H> {
|
||||
panic!("HttpServer::bind() has to be called before start()");
|
||||
} else {
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
let addrs: Vec<(net::SocketAddr, net::TcpListener)> =
|
||||
self.sockets.drain(..).collect();
|
||||
let settings = ServerSettings::new(Some(addrs[0].0), &self.host, false);
|
||||
let workers = self.start_workers(&settings, &StreamHandlerType::Normal);
|
||||
let info = Info {
|
||||
addr: addrs[0].0,
|
||||
handler: StreamHandlerType::Normal,
|
||||
};
|
||||
|
||||
let mut socks = Slab::new();
|
||||
let mut addrs: Vec<(usize, Socket)> = Vec::new();
|
||||
|
||||
for socket in self.sockets.drain(..) {
|
||||
let entry = socks.vacant_entry();
|
||||
let token = entry.key();
|
||||
entry.insert(SocketInfo {
|
||||
addr: socket.addr,
|
||||
htype: socket.tp.clone(),
|
||||
});
|
||||
addrs.push((token, socket));
|
||||
}
|
||||
|
||||
let settings = ServerSettings::new(Some(addrs[0].1.addr), &self.host, false);
|
||||
let workers = self.start_workers(&settings, &socks);
|
||||
|
||||
// start acceptors threads
|
||||
for (addr, sock) in addrs {
|
||||
info!("Starting server on http://{}", addr);
|
||||
for (token, sock) in addrs {
|
||||
info!("Starting server on http://{}", sock.addr);
|
||||
self.accept.push(start_accept_thread(
|
||||
token,
|
||||
sock,
|
||||
addr,
|
||||
self.backlog,
|
||||
tx.clone(),
|
||||
info.clone(),
|
||||
socks.clone(),
|
||||
workers.clone(),
|
||||
));
|
||||
}
|
||||
@ -332,9 +408,9 @@ impl<H: IntoHttpHandler> HttpServer<H> {
|
||||
ctx.add_stream(rx);
|
||||
self
|
||||
});
|
||||
signals.map(|signals| {
|
||||
if let Some(signals) = signals {
|
||||
signals.do_send(signal::Subscribe(addr.clone().recipient()))
|
||||
});
|
||||
}
|
||||
addr
|
||||
}
|
||||
}
|
||||
@ -373,119 +449,59 @@ impl<H: IntoHttpHandler> HttpServer<H> {
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[cfg(feature = "tls")]
|
||||
#[deprecated(
|
||||
since = "0.6.0", note = "please use `actix_web::HttpServer::bind_tls` instead"
|
||||
)]
|
||||
impl<H: IntoHttpHandler> HttpServer<H> {
|
||||
/// Start listening for incoming tls connections.
|
||||
pub fn start_tls(mut self, acceptor: TlsAcceptor) -> io::Result<Addr<Syn, Self>> {
|
||||
if self.sockets.is_empty() {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"No socket addresses are bound",
|
||||
))
|
||||
} else {
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
let addrs: Vec<(net::SocketAddr, net::TcpListener)> =
|
||||
self.sockets.drain(..).collect();
|
||||
let settings = ServerSettings::new(Some(addrs[0].0), &self.host, false);
|
||||
let workers =
|
||||
self.start_workers(&settings, &StreamHandlerType::Tls(acceptor.clone()));
|
||||
let info = Info {
|
||||
addr: addrs[0].0,
|
||||
handler: StreamHandlerType::Tls(acceptor),
|
||||
};
|
||||
|
||||
// start acceptors threads
|
||||
for (addr, sock) in addrs {
|
||||
info!("Starting server on https://{}", addr);
|
||||
self.accept.push(start_accept_thread(
|
||||
sock,
|
||||
addr,
|
||||
self.backlog,
|
||||
tx.clone(),
|
||||
info.clone(),
|
||||
workers.clone(),
|
||||
));
|
||||
for sock in &mut self.sockets {
|
||||
match sock.tp {
|
||||
StreamHandlerType::Normal => (),
|
||||
_ => continue,
|
||||
}
|
||||
|
||||
// start http server actor
|
||||
let signals = self.subscribe_to_signals();
|
||||
let addr: Addr<Syn, _> = Actor::create(|ctx| {
|
||||
ctx.add_stream(rx);
|
||||
self
|
||||
});
|
||||
signals.map(|signals| {
|
||||
signals.do_send(signal::Subscribe(addr.clone().recipient()))
|
||||
});
|
||||
Ok(addr)
|
||||
sock.tp = StreamHandlerType::Tls(acceptor.clone());
|
||||
}
|
||||
Ok(self.start())
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[cfg(feature = "alpn")]
|
||||
#[deprecated(
|
||||
since = "0.6.0", note = "please use `actix_web::HttpServer::bind_ssl` instead"
|
||||
)]
|
||||
impl<H: IntoHttpHandler> HttpServer<H> {
|
||||
/// Start listening for incoming tls connections.
|
||||
///
|
||||
/// This method sets alpn protocols to "h2" and "http/1.1"
|
||||
pub fn start_ssl(
|
||||
mut self, mut builder: SslAcceptorBuilder
|
||||
mut self, mut builder: SslAcceptorBuilder,
|
||||
) -> io::Result<Addr<Syn, Self>> {
|
||||
if self.sockets.is_empty() {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"No socket addresses are bound",
|
||||
))
|
||||
} else {
|
||||
// alpn support
|
||||
if !self.no_http2 {
|
||||
builder.set_alpn_protos(b"\x02h2\x08http/1.1")?;
|
||||
builder.set_alpn_select_callback(|_, protos| {
|
||||
const H2: &[u8] = b"\x02h2";
|
||||
if protos.windows(3).any(|window| window == H2) {
|
||||
Ok(b"h2")
|
||||
} else {
|
||||
Err(AlpnError::NOACK)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
let acceptor = builder.build();
|
||||
let addrs: Vec<(net::SocketAddr, net::TcpListener)> =
|
||||
self.sockets.drain(..).collect();
|
||||
let settings = ServerSettings::new(Some(addrs[0].0), &self.host, false);
|
||||
let workers = self.start_workers(
|
||||
&settings,
|
||||
&StreamHandlerType::Alpn(acceptor.clone()),
|
||||
);
|
||||
let info = Info {
|
||||
addr: addrs[0].0,
|
||||
handler: StreamHandlerType::Alpn(acceptor),
|
||||
};
|
||||
|
||||
// start acceptors threads
|
||||
for (addr, sock) in addrs {
|
||||
info!("Starting server on https://{}", addr);
|
||||
self.accept.push(start_accept_thread(
|
||||
sock,
|
||||
addr,
|
||||
self.backlog,
|
||||
tx.clone(),
|
||||
info.clone(),
|
||||
workers.clone(),
|
||||
));
|
||||
}
|
||||
|
||||
// start http server actor
|
||||
let signals = self.subscribe_to_signals();
|
||||
let addr: Addr<Syn, _> = Actor::create(|ctx| {
|
||||
ctx.add_stream(rx);
|
||||
self
|
||||
// alpn support
|
||||
if !self.no_http2 {
|
||||
builder.set_alpn_protos(b"\x02h2\x08http/1.1")?;
|
||||
builder.set_alpn_select_callback(|_, protos| {
|
||||
const H2: &[u8] = b"\x02h2";
|
||||
if protos.windows(3).any(|window| window == H2) {
|
||||
Ok(b"h2")
|
||||
} else {
|
||||
Err(AlpnError::NOACK)
|
||||
}
|
||||
});
|
||||
signals.map(|signals| {
|
||||
signals.do_send(signal::Subscribe(addr.clone().recipient()))
|
||||
});
|
||||
Ok(addr)
|
||||
}
|
||||
|
||||
let acceptor = builder.build();
|
||||
for sock in &mut self.sockets {
|
||||
match sock.tp {
|
||||
StreamHandlerType::Normal => (),
|
||||
_ => continue,
|
||||
}
|
||||
sock.tp = StreamHandlerType::Alpn(acceptor.clone());
|
||||
}
|
||||
Ok(self.start())
|
||||
}
|
||||
}
|
||||
|
||||
@ -499,32 +515,6 @@ impl<H: IntoHttpHandler> HttpServer<H> {
|
||||
T: AsyncRead + AsyncWrite + 'static,
|
||||
A: 'static,
|
||||
{
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
|
||||
if !self.sockets.is_empty() {
|
||||
let addrs: Vec<(net::SocketAddr, net::TcpListener)> =
|
||||
self.sockets.drain(..).collect();
|
||||
let settings = ServerSettings::new(Some(addrs[0].0), &self.host, false);
|
||||
let workers = self.start_workers(&settings, &StreamHandlerType::Normal);
|
||||
let info = Info {
|
||||
addr: addrs[0].0,
|
||||
handler: StreamHandlerType::Normal,
|
||||
};
|
||||
|
||||
// start acceptors threads
|
||||
for (addr, sock) in addrs {
|
||||
info!("Starting server on http://{}", addr);
|
||||
self.accept.push(start_accept_thread(
|
||||
sock,
|
||||
addr,
|
||||
self.backlog,
|
||||
tx.clone(),
|
||||
info.clone(),
|
||||
workers.clone(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// set server settings
|
||||
let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap();
|
||||
let settings = ServerSettings::new(Some(addr), &self.host, secure);
|
||||
@ -537,16 +527,17 @@ impl<H: IntoHttpHandler> HttpServer<H> {
|
||||
// start server
|
||||
let signals = self.subscribe_to_signals();
|
||||
let addr: Addr<Syn, _> = HttpServer::create(move |ctx| {
|
||||
ctx.add_stream(rx);
|
||||
ctx.add_message_stream(stream.map_err(|_| ()).map(move |(t, _)| Conn {
|
||||
io: WrapperStream::new(t),
|
||||
token: 0,
|
||||
peer: None,
|
||||
http2: false,
|
||||
}));
|
||||
self
|
||||
});
|
||||
signals
|
||||
.map(|signals| signals.do_send(signal::Subscribe(addr.clone().recipient())));
|
||||
if let Some(signals) = signals {
|
||||
signals.do_send(signal::Subscribe(addr.clone().recipient()))
|
||||
}
|
||||
addr
|
||||
}
|
||||
}
|
||||
@ -584,7 +575,7 @@ impl<H: IntoHttpHandler> StreamHandler<ServerCommand, ()> for HttpServer<H> {
|
||||
fn finished(&mut self, _: &mut Context<Self>) {}
|
||||
fn handle(&mut self, msg: ServerCommand, _: &mut Context<Self>) {
|
||||
match msg {
|
||||
ServerCommand::WorkerDied(idx, info) => {
|
||||
ServerCommand::WorkerDied(idx, socks) => {
|
||||
let mut found = false;
|
||||
for i in 0..self.workers.len() {
|
||||
if self.workers[i].0 == idx {
|
||||
@ -609,11 +600,10 @@ impl<H: IntoHttpHandler> StreamHandler<ServerCommand, ()> for HttpServer<H> {
|
||||
break;
|
||||
}
|
||||
|
||||
let h = info.handler;
|
||||
let ka = self.keep_alive;
|
||||
let factory = Arc::clone(&self.factory);
|
||||
let settings =
|
||||
ServerSettings::new(Some(info.addr), &self.host, false);
|
||||
ServerSettings::new(Some(socks[0].addr), &self.host, false);
|
||||
|
||||
let addr = Arbiter::start(move |ctx: &mut Context<_>| {
|
||||
let apps: Vec<_> = (*factory)()
|
||||
@ -621,7 +611,7 @@ impl<H: IntoHttpHandler> StreamHandler<ServerCommand, ()> for HttpServer<H> {
|
||||
.map(|h| h.into_handler(settings.clone()))
|
||||
.collect();
|
||||
ctx.add_message_stream(rx);
|
||||
Worker::new(apps, h, ka)
|
||||
Worker::new(apps, socks, ka)
|
||||
});
|
||||
for item in &self.accept {
|
||||
let _ = item.1.send(Command::Worker(new_idx, tx.clone()));
|
||||
@ -737,8 +727,8 @@ enum Command {
|
||||
}
|
||||
|
||||
fn start_accept_thread(
|
||||
sock: net::TcpListener, addr: net::SocketAddr, backlog: i32,
|
||||
srv: mpsc::UnboundedSender<ServerCommand>, info: Info,
|
||||
token: usize, sock: Socket, backlog: i32, srv: mpsc::UnboundedSender<ServerCommand>,
|
||||
socks: Slab<SocketInfo>,
|
||||
mut workers: Vec<(usize, mpsc::UnboundedSender<Conn<net::TcpStream>>)>,
|
||||
) -> (mio::SetReadiness, sync_mpsc::Sender<Command>) {
|
||||
let (tx, rx) = sync_mpsc::channel();
|
||||
@ -747,13 +737,14 @@ fn start_accept_thread(
|
||||
// start accept thread
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))]
|
||||
let _ = thread::Builder::new()
|
||||
.name(format!("Accept on {}", addr))
|
||||
.name(format!("Accept on {}", sock.addr))
|
||||
.spawn(move || {
|
||||
const SRV: mio::Token = mio::Token(0);
|
||||
const CMD: mio::Token = mio::Token(1);
|
||||
|
||||
let addr = sock.addr;
|
||||
let mut server = Some(
|
||||
mio::net::TcpListener::from_std(sock)
|
||||
mio::net::TcpListener::from_std(sock.lst)
|
||||
.expect("Can not create mio::net::TcpListener"),
|
||||
);
|
||||
|
||||
@ -799,9 +790,10 @@ fn start_accept_thread(
|
||||
SRV => if let Some(ref server) = server {
|
||||
loop {
|
||||
match server.accept_std() {
|
||||
Ok((sock, addr)) => {
|
||||
Ok((io, addr)) => {
|
||||
let mut msg = Conn {
|
||||
io: sock,
|
||||
io,
|
||||
token,
|
||||
peer: Some(addr),
|
||||
http2: false,
|
||||
};
|
||||
@ -812,7 +804,7 @@ fn start_accept_thread(
|
||||
let _ = srv.unbounded_send(
|
||||
ServerCommand::WorkerDied(
|
||||
workers[next].0,
|
||||
info.clone(),
|
||||
socks.clone(),
|
||||
),
|
||||
);
|
||||
msg = err.into_inner();
|
||||
@ -915,7 +907,7 @@ fn start_accept_thread(
|
||||
}
|
||||
|
||||
fn create_tcp_listener(
|
||||
addr: net::SocketAddr, backlog: i32
|
||||
addr: net::SocketAddr, backlog: i32,
|
||||
) -> io::Result<net::TcpListener> {
|
||||
let builder = match addr {
|
||||
net::SocketAddr::V4(_) => TcpBuilder::new_v4()?,
|
||||
|
@ -8,7 +8,7 @@ const LW_BUFFER_SIZE: usize = 4096;
|
||||
const HW_BUFFER_SIZE: usize = 32_768;
|
||||
|
||||
pub fn read_from_io<T: IoStream>(
|
||||
io: &mut T, buf: &mut BytesMut
|
||||
io: &mut T, buf: &mut BytesMut,
|
||||
) -> Poll<usize, io::Error> {
|
||||
unsafe {
|
||||
if buf.remaining_mut() < LW_BUFFER_SIZE {
|
||||
|
@ -1,6 +1,7 @@
|
||||
use futures::Future;
|
||||
use futures::unsync::oneshot;
|
||||
use futures::Future;
|
||||
use net2::TcpStreamExt;
|
||||
use slab::Slab;
|
||||
use std::rc::Rc;
|
||||
use std::{net, time};
|
||||
use tokio_core::net::TcpStream;
|
||||
@ -29,10 +30,17 @@ use server::{HttpHandler, KeepAlive};
|
||||
#[derive(Message)]
|
||||
pub(crate) struct Conn<T> {
|
||||
pub io: T,
|
||||
pub token: usize,
|
||||
pub peer: Option<net::SocketAddr>,
|
||||
pub http2: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct SocketInfo {
|
||||
pub addr: net::SocketAddr,
|
||||
pub htype: StreamHandlerType,
|
||||
}
|
||||
|
||||
/// Stop worker message. Returns `true` on successful shutdown
|
||||
/// and `false` if some connections still alive.
|
||||
pub(crate) struct StopWorker {
|
||||
@ -53,13 +61,13 @@ where
|
||||
{
|
||||
settings: Rc<WorkerSettings<H>>,
|
||||
hnd: Handle,
|
||||
handler: StreamHandlerType,
|
||||
socks: Slab<SocketInfo>,
|
||||
tcp_ka: Option<time::Duration>,
|
||||
}
|
||||
|
||||
impl<H: HttpHandler + 'static> Worker<H> {
|
||||
pub(crate) fn new(
|
||||
h: Vec<H>, handler: StreamHandlerType, keep_alive: KeepAlive
|
||||
h: Vec<H>, socks: Slab<SocketInfo>, keep_alive: KeepAlive,
|
||||
) -> Worker<H> {
|
||||
let tcp_ka = if let KeepAlive::Tcp(val) = keep_alive {
|
||||
Some(time::Duration::new(val as u64, 0))
|
||||
@ -70,7 +78,7 @@ impl<H: HttpHandler + 'static> Worker<H> {
|
||||
Worker {
|
||||
settings: Rc::new(WorkerSettings::new(h, keep_alive)),
|
||||
hnd: Arbiter::handle().clone(),
|
||||
handler,
|
||||
socks,
|
||||
tcp_ka,
|
||||
}
|
||||
}
|
||||
@ -83,7 +91,7 @@ impl<H: HttpHandler + 'static> Worker<H> {
|
||||
}
|
||||
|
||||
fn shutdown_timeout(
|
||||
&self, ctx: &mut Context<Self>, tx: oneshot::Sender<bool>, dur: time::Duration
|
||||
&self, ctx: &mut Context<Self>, tx: oneshot::Sender<bool>, dur: time::Duration,
|
||||
) {
|
||||
// sleep for 1 second and then check again
|
||||
ctx.run_later(time::Duration::new(1, 0), move |slf, ctx| {
|
||||
@ -124,8 +132,11 @@ where
|
||||
if self.tcp_ka.is_some() && msg.io.set_keepalive(self.tcp_ka).is_err() {
|
||||
error!("Can not set socket keep-alive option");
|
||||
}
|
||||
self.handler
|
||||
.handle(Rc::clone(&self.settings), &self.hnd, msg);
|
||||
self.socks.get_mut(msg.token).unwrap().htype.handle(
|
||||
Rc::clone(&self.settings),
|
||||
&self.hnd,
|
||||
msg,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -165,7 +176,7 @@ pub(crate) enum StreamHandlerType {
|
||||
|
||||
impl StreamHandlerType {
|
||||
fn handle<H: HttpHandler>(
|
||||
&mut self, h: Rc<WorkerSettings<H>>, hnd: &Handle, msg: Conn<net::TcpStream>
|
||||
&mut self, h: Rc<WorkerSettings<H>>, hnd: &Handle, msg: Conn<net::TcpStream>,
|
||||
) {
|
||||
match *self {
|
||||
StreamHandlerType::Normal => {
|
||||
@ -177,7 +188,9 @@ impl StreamHandlerType {
|
||||
}
|
||||
#[cfg(feature = "tls")]
|
||||
StreamHandlerType::Tls(ref acceptor) => {
|
||||
let Conn { io, peer, http2 } = msg;
|
||||
let Conn {
|
||||
io, peer, http2, ..
|
||||
} = msg;
|
||||
let _ = io.set_nodelay(true);
|
||||
let io = TcpStream::from_stream(io, hnd)
|
||||
.expect("failed to associate TCP stream");
|
||||
@ -185,12 +198,8 @@ impl StreamHandlerType {
|
||||
hnd.spawn(
|
||||
TlsAcceptorExt::accept_async(acceptor, io).then(move |res| {
|
||||
match res {
|
||||
Ok(io) => Arbiter::handle().spawn(HttpChannel::new(
|
||||
h,
|
||||
io,
|
||||
peer,
|
||||
http2,
|
||||
)),
|
||||
Ok(io) => Arbiter::handle()
|
||||
.spawn(HttpChannel::new(h, io, peer, http2)),
|
||||
Err(err) => {
|
||||
trace!("Error during handling tls connection: {}", err)
|
||||
}
|
||||
@ -217,12 +226,8 @@ impl StreamHandlerType {
|
||||
} else {
|
||||
false
|
||||
};
|
||||
Arbiter::handle().spawn(HttpChannel::new(
|
||||
h,
|
||||
io,
|
||||
peer,
|
||||
http2,
|
||||
));
|
||||
Arbiter::handle()
|
||||
.spawn(HttpChannel::new(h, io, peer, http2));
|
||||
}
|
||||
Err(err) => {
|
||||
trace!("Error during handling tls connection: {}", err)
|
||||
|
25
src/test.rs
25
src/test.rs
@ -21,7 +21,7 @@ use application::{App, HttpApplication};
|
||||
use body::Binary;
|
||||
use client::{ClientConnector, ClientRequest, ClientRequestBuilder};
|
||||
use error::Error;
|
||||
use handler::{Handler, ReplyItem, Responder};
|
||||
use handler::{AsyncResultItem, Handler, Responder};
|
||||
use header::{Header, IntoHeaderValue};
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
@ -88,7 +88,7 @@ impl TestServer {
|
||||
/// Create test server builder with specific state factory
|
||||
///
|
||||
/// This method can be used for constructing application state.
|
||||
/// Also it can be used for external dependecy initialization,
|
||||
/// Also it can be used for external dependency initialization,
|
||||
/// like creating sync actors for diesel integration.
|
||||
pub fn build_with_state<F, S>(state: F) -> TestServerBuilder<S>
|
||||
where
|
||||
@ -202,7 +202,7 @@ impl TestServer {
|
||||
|
||||
/// Connect to websocket server
|
||||
pub fn ws(
|
||||
&mut self
|
||||
&mut self,
|
||||
) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> {
|
||||
let url = self.url("/");
|
||||
self.system.run_until_complete(
|
||||
@ -478,7 +478,7 @@ impl TestRequest<()> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> TestRequest<S> {
|
||||
impl<S: 'static> TestRequest<S> {
|
||||
/// Start HttpRequest build process with application state
|
||||
pub fn with_state(state: S) -> TestRequest<S> {
|
||||
TestRequest {
|
||||
@ -593,18 +593,17 @@ impl<S> TestRequest<S> {
|
||||
/// with generated request.
|
||||
///
|
||||
/// This method panics is handler returns actor or async result.
|
||||
pub fn run<H: Handler<S>>(
|
||||
self, mut h: H
|
||||
) -> Result<HttpResponse, <<H as Handler<S>>::Result as Responder>::Error> {
|
||||
pub fn run<H: Handler<S>>(self, mut h: H) -> Result<HttpResponse, Error> {
|
||||
let req = self.finish();
|
||||
let resp = h.handle(req.clone());
|
||||
|
||||
match resp.respond_to(req.drop_state()) {
|
||||
match resp.respond_to(&req) {
|
||||
Ok(resp) => match resp.into().into() {
|
||||
ReplyItem::Message(resp) => Ok(resp),
|
||||
ReplyItem::Future(_) => panic!("Async handler is not supported."),
|
||||
AsyncResultItem::Ok(resp) => Ok(resp),
|
||||
AsyncResultItem::Err(err) => Err(err),
|
||||
AsyncResultItem::Future(_) => panic!("Async handler is not supported."),
|
||||
},
|
||||
Err(err) => Err(err),
|
||||
Err(err) => Err(err.into()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -624,9 +623,9 @@ impl<S> TestRequest<S> {
|
||||
|
||||
let mut core = Core::new().unwrap();
|
||||
match core.run(fut) {
|
||||
Ok(r) => match r.respond_to(req.drop_state()) {
|
||||
Ok(r) => match r.respond_to(&req) {
|
||||
Ok(reply) => match reply.into().into() {
|
||||
ReplyItem::Message(resp) => Ok(resp),
|
||||
AsyncResultItem::Ok(resp) => Ok(resp),
|
||||
_ => panic!("Nested async replies are not supported"),
|
||||
},
|
||||
Err(e) => Err(e),
|
||||
|
@ -51,10 +51,6 @@ impl Url {
|
||||
&self.uri
|
||||
}
|
||||
|
||||
pub fn uri_mut(&mut self) -> &mut Uri {
|
||||
&mut self.uri
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &str {
|
||||
if let Some(ref s) = self.path {
|
||||
s
|
||||
|
315
src/with.rs
315
src/with.rs
@ -5,7 +5,7 @@ use std::ops::{Deref, DerefMut};
|
||||
use std::rc::Rc;
|
||||
|
||||
use error::Error;
|
||||
use handler::{FromRequest, Handler, Reply, ReplyItem, Responder};
|
||||
use handler::{AsyncResult, AsyncResultItem, FromRequest, Handler, Responder};
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
|
||||
@ -82,7 +82,7 @@ where
|
||||
T: FromRequest<S> + 'static,
|
||||
S: 'static,
|
||||
{
|
||||
type Result = Reply;
|
||||
type Result = AsyncResult<HttpResponse>;
|
||||
|
||||
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
|
||||
let mut fut = WithHandlerFut {
|
||||
@ -95,9 +95,9 @@ where
|
||||
};
|
||||
|
||||
match fut.poll() {
|
||||
Ok(Async::Ready(resp)) => Reply::response(resp),
|
||||
Ok(Async::NotReady) => Reply::async(fut),
|
||||
Err(e) => Reply::response(e),
|
||||
Ok(Async::Ready(resp)) => AsyncResult::ok(resp),
|
||||
Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)),
|
||||
Err(e) => AsyncResult::err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -134,14 +134,14 @@ where
|
||||
|
||||
let item = if !self.started {
|
||||
self.started = true;
|
||||
let mut fut = T::from_request(&self.req, self.cfg.as_ref());
|
||||
match fut.poll() {
|
||||
Ok(Async::Ready(item)) => item,
|
||||
Ok(Async::NotReady) => {
|
||||
self.fut1 = Some(Box::new(fut));
|
||||
return Ok(Async::NotReady);
|
||||
let reply = T::from_request(&self.req, self.cfg.as_ref()).into();
|
||||
match reply.into() {
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
AsyncResultItem::Ok(msg) => msg,
|
||||
AsyncResultItem::Future(fut) => {
|
||||
self.fut1 = Some(fut);
|
||||
return self.poll();
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
} else {
|
||||
match self.fut1.as_mut().unwrap().poll()? {
|
||||
@ -151,14 +151,15 @@ where
|
||||
};
|
||||
|
||||
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
|
||||
let item = match (*hnd)(item).respond_to(self.req.drop_state()) {
|
||||
let item = match (*hnd)(item).respond_to(&self.req) {
|
||||
Ok(item) => item.into(),
|
||||
Err(e) => return Err(e.into()),
|
||||
};
|
||||
|
||||
match item.into() {
|
||||
ReplyItem::Message(resp) => Ok(Async::Ready(resp)),
|
||||
ReplyItem::Future(fut) => {
|
||||
AsyncResultItem::Err(err) => Err(err),
|
||||
AsyncResultItem::Ok(resp) => Ok(Async::Ready(resp)),
|
||||
AsyncResultItem::Future(fut) => {
|
||||
self.fut2 = Some(fut);
|
||||
self.poll()
|
||||
}
|
||||
@ -187,7 +188,7 @@ where
|
||||
S: 'static,
|
||||
{
|
||||
pub fn new(
|
||||
f: F, cfg1: ExtractorConfig<S, T1>, cfg2: ExtractorConfig<S, T2>
|
||||
f: F, cfg1: ExtractorConfig<S, T1>, cfg2: ExtractorConfig<S, T2>,
|
||||
) -> Self {
|
||||
With2 {
|
||||
hnd: Rc::new(UnsafeCell::new(f)),
|
||||
@ -206,7 +207,7 @@ where
|
||||
T2: FromRequest<S> + 'static,
|
||||
S: 'static,
|
||||
{
|
||||
type Result = Reply;
|
||||
type Result = AsyncResult<HttpResponse>;
|
||||
|
||||
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
|
||||
let mut fut = WithHandlerFut2 {
|
||||
@ -221,9 +222,9 @@ where
|
||||
fut3: None,
|
||||
};
|
||||
match fut.poll() {
|
||||
Ok(Async::Ready(resp)) => Reply::response(resp),
|
||||
Ok(Async::NotReady) => Reply::async(fut),
|
||||
Err(e) => Reply::response(e),
|
||||
Ok(Async::Ready(resp)) => AsyncResult::ok(resp),
|
||||
Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)),
|
||||
Err(e) => AsyncResult::ok(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -265,52 +266,67 @@ where
|
||||
|
||||
if !self.started {
|
||||
self.started = true;
|
||||
let mut fut = T1::from_request(&self.req, self.cfg1.as_ref());
|
||||
match fut.poll() {
|
||||
Ok(Async::Ready(item1)) => {
|
||||
let mut fut = T2::from_request(&self.req, self.cfg2.as_ref());
|
||||
match fut.poll() {
|
||||
Ok(Async::Ready(item2)) => {
|
||||
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
|
||||
match (*hnd)(item1, item2).respond_to(self.req.drop_state())
|
||||
{
|
||||
Ok(item) => match item.into().into() {
|
||||
ReplyItem::Message(resp) => {
|
||||
return Ok(Async::Ready(resp))
|
||||
}
|
||||
ReplyItem::Future(fut) => {
|
||||
self.fut3 = Some(fut);
|
||||
return self.poll();
|
||||
}
|
||||
},
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
}
|
||||
Ok(Async::NotReady) => {
|
||||
self.item = Some(item1);
|
||||
self.fut2 = Some(Box::new(fut));
|
||||
return Ok(Async::NotReady);
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
let reply = T1::from_request(&self.req, self.cfg1.as_ref()).into();
|
||||
let item1 = match reply.into() {
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
AsyncResultItem::Ok(msg) => msg,
|
||||
AsyncResultItem::Future(fut) => {
|
||||
self.fut1 = Some(fut);
|
||||
return self.poll();
|
||||
}
|
||||
};
|
||||
|
||||
let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into();
|
||||
let item2 = match reply.into() {
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
AsyncResultItem::Ok(msg) => msg,
|
||||
AsyncResultItem::Future(fut) => {
|
||||
self.item = Some(item1);
|
||||
self.fut2 = Some(fut);
|
||||
return self.poll();
|
||||
}
|
||||
};
|
||||
|
||||
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
|
||||
match (*hnd)(item1, item2).respond_to(&self.req) {
|
||||
Ok(item) => match item.into().into() {
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
|
||||
AsyncResultItem::Future(fut) => {
|
||||
self.fut3 = Some(fut);
|
||||
return self.poll();
|
||||
}
|
||||
}
|
||||
Ok(Async::NotReady) => {
|
||||
self.fut1 = Some(Box::new(fut));
|
||||
return Ok(Async::NotReady);
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
},
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
if self.fut1.is_some() {
|
||||
match self.fut1.as_mut().unwrap().poll()? {
|
||||
Async::Ready(item) => {
|
||||
self.item = Some(item);
|
||||
self.fut1.take();
|
||||
self.fut2 = Some(Box::new(T2::from_request(
|
||||
&self.req,
|
||||
self.cfg2.as_ref(),
|
||||
)));
|
||||
let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into();
|
||||
let item2 = match reply.into() {
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
AsyncResultItem::Ok(msg) => msg,
|
||||
AsyncResultItem::Future(fut) => {
|
||||
self.item = Some(item);
|
||||
self.fut2 = Some(fut);
|
||||
return self.poll();
|
||||
}
|
||||
};
|
||||
|
||||
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
|
||||
match (*hnd)(item, item2).respond_to(&self.req) {
|
||||
Ok(item) => match item.into().into() {
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
|
||||
AsyncResultItem::Future(fut) => {
|
||||
self.fut3 = Some(fut);
|
||||
return self.poll();
|
||||
}
|
||||
},
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
}
|
||||
Async::NotReady => return Ok(Async::NotReady),
|
||||
}
|
||||
@ -322,16 +338,15 @@ where
|
||||
};
|
||||
|
||||
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
|
||||
let item = match (*hnd)(self.item.take().unwrap(), item)
|
||||
.respond_to(self.req.drop_state())
|
||||
{
|
||||
let item = match (*hnd)(self.item.take().unwrap(), item).respond_to(&self.req) {
|
||||
Ok(item) => item.into(),
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
|
||||
match item.into() {
|
||||
ReplyItem::Message(resp) => return Ok(Async::Ready(resp)),
|
||||
ReplyItem::Future(fut) => self.fut3 = Some(fut),
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
|
||||
AsyncResultItem::Future(fut) => self.fut3 = Some(fut),
|
||||
}
|
||||
|
||||
self.poll()
|
||||
@ -387,7 +402,7 @@ where
|
||||
T3: 'static,
|
||||
S: 'static,
|
||||
{
|
||||
type Result = Reply;
|
||||
type Result = AsyncResult<HttpResponse>;
|
||||
|
||||
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
|
||||
let mut fut = WithHandlerFut3 {
|
||||
@ -405,9 +420,9 @@ where
|
||||
fut4: None,
|
||||
};
|
||||
match fut.poll() {
|
||||
Ok(Async::Ready(resp)) => Reply::response(resp),
|
||||
Ok(Async::NotReady) => Reply::async(fut),
|
||||
Err(e) => Reply::response(e),
|
||||
Ok(Async::Ready(resp)) => AsyncResult::ok(resp),
|
||||
Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)),
|
||||
Err(e) => AsyncResult::err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -454,54 +469,50 @@ where
|
||||
|
||||
if !self.started {
|
||||
self.started = true;
|
||||
let mut fut = T1::from_request(&self.req, self.cfg1.as_ref());
|
||||
match fut.poll() {
|
||||
Ok(Async::Ready(item1)) => {
|
||||
let mut fut = T2::from_request(&self.req, self.cfg2.as_ref());
|
||||
match fut.poll() {
|
||||
Ok(Async::Ready(item2)) => {
|
||||
let mut fut =
|
||||
T3::from_request(&self.req, self.cfg3.as_ref());
|
||||
match fut.poll() {
|
||||
Ok(Async::Ready(item3)) => {
|
||||
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
|
||||
match (*hnd)(item1, item2, item3)
|
||||
.respond_to(self.req.drop_state())
|
||||
{
|
||||
Ok(item) => match item.into().into() {
|
||||
ReplyItem::Message(resp) => {
|
||||
return Ok(Async::Ready(resp))
|
||||
}
|
||||
ReplyItem::Future(fut) => {
|
||||
self.fut4 = Some(fut);
|
||||
return self.poll();
|
||||
}
|
||||
},
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
}
|
||||
Ok(Async::NotReady) => {
|
||||
self.item1 = Some(item1);
|
||||
self.item2 = Some(item2);
|
||||
self.fut3 = Some(Box::new(fut));
|
||||
return Ok(Async::NotReady);
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Ok(Async::NotReady) => {
|
||||
self.item1 = Some(item1);
|
||||
self.fut2 = Some(Box::new(fut));
|
||||
return Ok(Async::NotReady);
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
let reply = T1::from_request(&self.req, self.cfg1.as_ref()).into();
|
||||
let item1 = match reply.into() {
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
AsyncResultItem::Ok(msg) => msg,
|
||||
AsyncResultItem::Future(fut) => {
|
||||
self.fut1 = Some(fut);
|
||||
return self.poll();
|
||||
}
|
||||
};
|
||||
|
||||
let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into();
|
||||
let item2 = match reply.into() {
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
AsyncResultItem::Ok(msg) => msg,
|
||||
AsyncResultItem::Future(fut) => {
|
||||
self.item1 = Some(item1);
|
||||
self.fut2 = Some(fut);
|
||||
return self.poll();
|
||||
}
|
||||
};
|
||||
|
||||
let reply = T3::from_request(&self.req, self.cfg3.as_ref()).into();
|
||||
let item3 = match reply.into() {
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
AsyncResultItem::Ok(msg) => msg,
|
||||
AsyncResultItem::Future(fut) => {
|
||||
self.item1 = Some(item1);
|
||||
self.item2 = Some(item2);
|
||||
self.fut3 = Some(fut);
|
||||
return self.poll();
|
||||
}
|
||||
};
|
||||
|
||||
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
|
||||
match (*hnd)(item1, item2, item3).respond_to(&self.req) {
|
||||
Ok(item) => match item.into().into() {
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
|
||||
AsyncResultItem::Future(fut) => {
|
||||
self.fut4 = Some(fut);
|
||||
return self.poll();
|
||||
}
|
||||
}
|
||||
Ok(Async::NotReady) => {
|
||||
self.fut1 = Some(Box::new(fut));
|
||||
return Ok(Async::NotReady);
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
},
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -510,10 +521,40 @@ where
|
||||
Async::Ready(item) => {
|
||||
self.item1 = Some(item);
|
||||
self.fut1.take();
|
||||
self.fut2 = Some(Box::new(T2::from_request(
|
||||
&self.req,
|
||||
self.cfg2.as_ref(),
|
||||
)));
|
||||
let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into();
|
||||
let item2 = match reply.into() {
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
AsyncResultItem::Ok(msg) => msg,
|
||||
AsyncResultItem::Future(fut) => {
|
||||
self.fut2 = Some(fut);
|
||||
return self.poll();
|
||||
}
|
||||
};
|
||||
|
||||
let reply = T3::from_request(&self.req, self.cfg3.as_ref()).into();
|
||||
let item3 = match reply.into() {
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
AsyncResultItem::Ok(msg) => msg,
|
||||
AsyncResultItem::Future(fut) => {
|
||||
self.item2 = Some(item2);
|
||||
self.fut3 = Some(fut);
|
||||
return self.poll();
|
||||
}
|
||||
};
|
||||
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
|
||||
match (*hnd)(self.item1.take().unwrap(), item2, item3)
|
||||
.respond_to(&self.req)
|
||||
{
|
||||
Ok(item) => match item.into().into() {
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
|
||||
AsyncResultItem::Future(fut) => {
|
||||
self.fut4 = Some(fut);
|
||||
return self.poll();
|
||||
}
|
||||
},
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
}
|
||||
Async::NotReady => return Ok(Async::NotReady),
|
||||
}
|
||||
@ -522,12 +563,31 @@ where
|
||||
if self.fut2.is_some() {
|
||||
match self.fut2.as_mut().unwrap().poll()? {
|
||||
Async::Ready(item) => {
|
||||
self.item2 = Some(item);
|
||||
self.fut2.take();
|
||||
self.fut3 = Some(Box::new(T3::from_request(
|
||||
&self.req,
|
||||
self.cfg3.as_ref(),
|
||||
)));
|
||||
let reply = T3::from_request(&self.req, self.cfg3.as_ref()).into();
|
||||
let item3 = match reply.into() {
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
AsyncResultItem::Ok(msg) => msg,
|
||||
AsyncResultItem::Future(fut) => {
|
||||
self.item2 = Some(item);
|
||||
self.fut3 = Some(fut);
|
||||
return self.poll();
|
||||
}
|
||||
};
|
||||
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
|
||||
match (*hnd)(self.item1.take().unwrap(), item, item3)
|
||||
.respond_to(&self.req)
|
||||
{
|
||||
Ok(item) => match item.into().into() {
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
|
||||
AsyncResultItem::Future(fut) => {
|
||||
self.fut4 = Some(fut);
|
||||
return self.poll();
|
||||
}
|
||||
},
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
}
|
||||
Async::NotReady => return Ok(Async::NotReady),
|
||||
}
|
||||
@ -543,15 +603,16 @@ where
|
||||
self.item1.take().unwrap(),
|
||||
self.item2.take().unwrap(),
|
||||
item,
|
||||
).respond_to(self.req.drop_state())
|
||||
).respond_to(&self.req)
|
||||
{
|
||||
Ok(item) => item.into(),
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
|
||||
match item.into() {
|
||||
ReplyItem::Message(resp) => return Ok(Async::Ready(resp)),
|
||||
ReplyItem::Future(fut) => self.fut4 = Some(fut),
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
|
||||
AsyncResultItem::Future(fut) => self.fut4 = Some(fut),
|
||||
}
|
||||
|
||||
self.poll()
|
||||
|
@ -5,7 +5,6 @@ use std::time::Duration;
|
||||
use std::{fmt, io, str};
|
||||
|
||||
use base64;
|
||||
use byteorder::{ByteOrder, NetworkEndian};
|
||||
use bytes::Bytes;
|
||||
use cookie::Cookie;
|
||||
use futures::unsync::mpsc::{unbounded, UnboundedSender};
|
||||
@ -27,8 +26,8 @@ use client::{ClientConnector, ClientRequest, ClientRequestBuilder, ClientRespons
|
||||
HttpResponseParserError, SendRequest, SendRequestError};
|
||||
|
||||
use super::frame::Frame;
|
||||
use super::proto::{CloseCode, OpCode};
|
||||
use super::{Message, ProtocolError};
|
||||
use super::proto::{CloseReason, OpCode};
|
||||
use super::{Message, ProtocolError, WsWriter};
|
||||
|
||||
/// Websocket client error
|
||||
#[derive(Fail, Debug)]
|
||||
@ -122,7 +121,7 @@ impl Client {
|
||||
|
||||
/// Create new websocket connection with custom `ClientConnector`
|
||||
pub fn with_connector<S: AsRef<str>>(
|
||||
uri: S, conn: Addr<Unsync, ClientConnector>
|
||||
uri: S, conn: Addr<Unsync, ClientConnector>,
|
||||
) -> Client {
|
||||
let mut cl = Client {
|
||||
request: ClientRequest::build(),
|
||||
@ -259,7 +258,7 @@ struct Inner {
|
||||
|
||||
/// Future that implementes client websocket handshake process.
|
||||
///
|
||||
/// It resolves to a pair of `ClientReadr` and `ClientWriter` that
|
||||
/// It resolves to a pair of `ClientReader` and `ClientWriter` that
|
||||
/// can be used for reading and writing websocket frames.
|
||||
pub struct ClientHandshake {
|
||||
request: Option<SendRequest>,
|
||||
@ -468,10 +467,8 @@ impl Stream for ClientReader {
|
||||
}
|
||||
OpCode::Close => {
|
||||
inner.closed = true;
|
||||
let code = NetworkEndian::read_uint(payload.as_ref(), 2) as u16;
|
||||
Ok(Async::Ready(Some(Message::Close(CloseCode::from(
|
||||
code,
|
||||
)))))
|
||||
let close_reason = Frame::parse_close_payload(&payload);
|
||||
Ok(Async::Ready(Some(Message::Close(close_reason))))
|
||||
}
|
||||
OpCode::Ping => Ok(Async::Ready(Some(Message::Ping(
|
||||
String::from_utf8_lossy(payload.as_ref()).into(),
|
||||
@ -506,13 +503,6 @@ pub struct ClientWriter {
|
||||
inner: Rc<UnsafeCell<Inner>>,
|
||||
}
|
||||
|
||||
impl ClientWriter {
|
||||
#[inline]
|
||||
fn as_mut(&mut self) -> &mut Inner {
|
||||
unsafe { &mut *self.inner.get() }
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientWriter {
|
||||
/// Write payload
|
||||
#[inline]
|
||||
@ -524,21 +514,28 @@ impl ClientWriter {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn as_mut(&mut self) -> &mut Inner {
|
||||
unsafe { &mut *self.inner.get() }
|
||||
}
|
||||
}
|
||||
|
||||
impl WsWriter for ClientWriter {
|
||||
/// Send text frame
|
||||
#[inline]
|
||||
pub fn text<T: Into<Binary>>(&mut self, text: T) {
|
||||
fn text<T: Into<Binary>>(&mut self, text: T) {
|
||||
self.write(Frame::message(text.into(), OpCode::Text, true, true));
|
||||
}
|
||||
|
||||
/// Send binary frame
|
||||
#[inline]
|
||||
pub fn binary<B: Into<Binary>>(&mut self, data: B) {
|
||||
fn binary<B: Into<Binary>>(&mut self, data: B) {
|
||||
self.write(Frame::message(data, OpCode::Binary, true, true));
|
||||
}
|
||||
|
||||
/// Send ping frame
|
||||
#[inline]
|
||||
pub fn ping(&mut self, message: &str) {
|
||||
fn ping(&mut self, message: &str) {
|
||||
self.write(Frame::message(
|
||||
Vec::from(message),
|
||||
OpCode::Ping,
|
||||
@ -549,7 +546,7 @@ impl ClientWriter {
|
||||
|
||||
/// Send pong frame
|
||||
#[inline]
|
||||
pub fn pong(&mut self, message: &str) {
|
||||
fn pong(&mut self, message: &str) {
|
||||
self.write(Frame::message(
|
||||
Vec::from(message),
|
||||
OpCode::Pong,
|
||||
@ -560,7 +557,7 @@ impl ClientWriter {
|
||||
|
||||
/// Send close frame
|
||||
#[inline]
|
||||
pub fn close(&mut self, code: CloseCode, reason: &str) {
|
||||
self.write(Frame::close(code, reason, true));
|
||||
fn close(&mut self, reason: Option<CloseReason>) {
|
||||
self.write(Frame::close(reason, true));
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ use futures::sync::oneshot::Sender;
|
||||
use futures::unsync::oneshot;
|
||||
use futures::{Async, Poll};
|
||||
use smallvec::SmallVec;
|
||||
use std::mem;
|
||||
|
||||
use actix::dev::{ContextImpl, SyncEnvelope, ToEnvelope};
|
||||
use actix::fut::ActorFuture;
|
||||
@ -15,7 +14,8 @@ use error::{Error, ErrorInternalServerError};
|
||||
use httprequest::HttpRequest;
|
||||
|
||||
use ws::frame::Frame;
|
||||
use ws::proto::{CloseCode, OpCode};
|
||||
use ws::proto::{CloseReason, OpCode};
|
||||
use ws::WsWriter;
|
||||
|
||||
/// Execution context for `WebSockets` actors
|
||||
pub struct WebsocketContext<A, S = ()>
|
||||
@ -141,46 +141,6 @@ where
|
||||
&mut self.request
|
||||
}
|
||||
|
||||
/// Send text frame
|
||||
#[inline]
|
||||
pub fn text<T: Into<Binary>>(&mut self, text: T) {
|
||||
self.write(Frame::message(text.into(), OpCode::Text, true, false));
|
||||
}
|
||||
|
||||
/// Send binary frame
|
||||
#[inline]
|
||||
pub fn binary<B: Into<Binary>>(&mut self, data: B) {
|
||||
self.write(Frame::message(data, OpCode::Binary, true, false));
|
||||
}
|
||||
|
||||
/// Send ping frame
|
||||
#[inline]
|
||||
pub fn ping(&mut self, message: &str) {
|
||||
self.write(Frame::message(
|
||||
Vec::from(message),
|
||||
OpCode::Ping,
|
||||
true,
|
||||
false,
|
||||
));
|
||||
}
|
||||
|
||||
/// Send pong frame
|
||||
#[inline]
|
||||
pub fn pong(&mut self, message: &str) {
|
||||
self.write(Frame::message(
|
||||
Vec::from(message),
|
||||
OpCode::Pong,
|
||||
true,
|
||||
false,
|
||||
));
|
||||
}
|
||||
|
||||
/// Send close frame
|
||||
#[inline]
|
||||
pub fn close(&mut self, code: CloseCode, reason: &str) {
|
||||
self.write(Frame::close(code, reason, false));
|
||||
}
|
||||
|
||||
/// Returns drain future
|
||||
pub fn drain(&mut self) -> Drain<A> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
@ -200,7 +160,9 @@ where
|
||||
if self.stream.is_none() {
|
||||
self.stream = Some(SmallVec::new());
|
||||
}
|
||||
self.stream.as_mut().map(|s| s.push(frame));
|
||||
if let Some(s) = self.stream.as_mut() {
|
||||
s.push(frame)
|
||||
}
|
||||
self.inner.modify();
|
||||
}
|
||||
|
||||
@ -212,6 +174,52 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, S> WsWriter for WebsocketContext<A, S>
|
||||
where
|
||||
A: Actor<Context = Self>,
|
||||
S: 'static,
|
||||
{
|
||||
/// Send text frame
|
||||
#[inline]
|
||||
fn text<T: Into<Binary>>(&mut self, text: T) {
|
||||
self.write(Frame::message(text.into(), OpCode::Text, true, false));
|
||||
}
|
||||
|
||||
/// Send binary frame
|
||||
#[inline]
|
||||
fn binary<B: Into<Binary>>(&mut self, data: B) {
|
||||
self.write(Frame::message(data, OpCode::Binary, true, false));
|
||||
}
|
||||
|
||||
/// Send ping frame
|
||||
#[inline]
|
||||
fn ping(&mut self, message: &str) {
|
||||
self.write(Frame::message(
|
||||
Vec::from(message),
|
||||
OpCode::Ping,
|
||||
true,
|
||||
false,
|
||||
));
|
||||
}
|
||||
|
||||
/// Send pong frame
|
||||
#[inline]
|
||||
fn pong(&mut self, message: &str) {
|
||||
self.write(Frame::message(
|
||||
Vec::from(message),
|
||||
OpCode::Pong,
|
||||
true,
|
||||
false,
|
||||
));
|
||||
}
|
||||
|
||||
/// Send close frame
|
||||
#[inline]
|
||||
fn close(&mut self, reason: Option<CloseReason>) {
|
||||
self.write(Frame::close(reason, false));
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, S> ActorHttpContext for WebsocketContext<A, S>
|
||||
where
|
||||
A: Actor<Context = Self>,
|
||||
@ -224,8 +232,7 @@ where
|
||||
}
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<SmallVec<[ContextFrame; 4]>>, Error> {
|
||||
let ctx: &mut WebsocketContext<A, S> =
|
||||
unsafe { mem::transmute(self as &mut WebsocketContext<A, S>) };
|
||||
let ctx: &mut WebsocketContext<A, S> = unsafe { &mut *(self as *mut _) };
|
||||
|
||||
if self.inner.alive() && self.inner.poll(ctx).is_err() {
|
||||
return Err(ErrorInternalServerError("error"));
|
||||
|
@ -1,17 +1,17 @@
|
||||
#![cfg_attr(feature = "cargo-clippy", allow(cast_ptr_alignment))]
|
||||
use byteorder::{BigEndian, ByteOrder, NetworkEndian};
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use futures::{Async, Poll, Stream};
|
||||
use rand;
|
||||
use std::iter::FromIterator;
|
||||
use std::{fmt, mem, ptr};
|
||||
use std::{fmt, ptr};
|
||||
|
||||
use body::Binary;
|
||||
use error::PayloadError;
|
||||
use payload::PayloadHelper;
|
||||
|
||||
use ws::ProtocolError;
|
||||
use ws::mask::apply_mask;
|
||||
use ws::proto::{CloseCode, OpCode};
|
||||
use ws::proto::{CloseCode, CloseReason, OpCode};
|
||||
use ws::ProtocolError;
|
||||
|
||||
/// A struct representing a `WebSocket` frame.
|
||||
#[derive(Debug)]
|
||||
@ -29,21 +29,19 @@ impl Frame {
|
||||
|
||||
/// Create a new Close control frame.
|
||||
#[inline]
|
||||
pub fn close(code: CloseCode, reason: &str, genmask: bool) -> Binary {
|
||||
let raw: [u8; 2] = unsafe {
|
||||
let u: u16 = code.into();
|
||||
mem::transmute(u.to_be())
|
||||
};
|
||||
pub fn close(reason: Option<CloseReason>, genmask: bool) -> Binary {
|
||||
let payload = match reason {
|
||||
None => Vec::new(),
|
||||
Some(reason) => {
|
||||
let mut code_bytes = [0; 2];
|
||||
NetworkEndian::write_u16(&mut code_bytes, reason.code.into());
|
||||
|
||||
let payload = if let CloseCode::Empty = code {
|
||||
Vec::new()
|
||||
} else {
|
||||
Vec::from_iter(
|
||||
raw[..]
|
||||
.iter()
|
||||
.chain(reason.as_bytes().iter())
|
||||
.cloned(),
|
||||
)
|
||||
let mut payload = Vec::from(&code_bytes[..]);
|
||||
if let Some(description) = reason.description {
|
||||
payload.extend(description.as_bytes());
|
||||
}
|
||||
payload
|
||||
}
|
||||
};
|
||||
|
||||
Frame::message(payload, OpCode::Close, true, genmask)
|
||||
@ -51,7 +49,7 @@ impl Frame {
|
||||
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(type_complexity))]
|
||||
fn read_copy_md<S>(
|
||||
pl: &mut PayloadHelper<S>, server: bool, max_size: usize
|
||||
pl: &mut PayloadHelper<S>, server: bool, max_size: usize,
|
||||
) -> Poll<Option<(usize, bool, OpCode, usize, Option<u32>)>, ProtocolError>
|
||||
where
|
||||
S: Stream<Item = Bytes, Error = PayloadError>,
|
||||
@ -126,16 +124,12 @@ impl Frame {
|
||||
};
|
||||
|
||||
Ok(Async::Ready(Some((
|
||||
idx,
|
||||
finished,
|
||||
opcode,
|
||||
length,
|
||||
mask,
|
||||
idx, finished, opcode, length, mask,
|
||||
))))
|
||||
}
|
||||
|
||||
fn read_chunk_md(
|
||||
chunk: &[u8], server: bool, max_size: usize
|
||||
chunk: &[u8], server: bool, max_size: usize,
|
||||
) -> Poll<(usize, bool, OpCode, usize, Option<u32>), ProtocolError> {
|
||||
let chunk_len = chunk.len();
|
||||
|
||||
@ -206,7 +200,7 @@ impl Frame {
|
||||
|
||||
/// Parse the input stream into a frame.
|
||||
pub fn parse<S>(
|
||||
pl: &mut PayloadHelper<S>, server: bool, max_size: usize
|
||||
pl: &mut PayloadHelper<S>, server: bool, max_size: usize,
|
||||
) -> Poll<Option<Frame>, ProtocolError>
|
||||
where
|
||||
S: Stream<Item = Bytes, Error = PayloadError>,
|
||||
@ -266,10 +260,9 @@ impl Frame {
|
||||
|
||||
// unmask
|
||||
if let Some(mask) = mask {
|
||||
#[allow(mutable_transmutes)]
|
||||
let p: &mut [u8] = unsafe {
|
||||
let ptr: &[u8] = &data;
|
||||
mem::transmute(ptr)
|
||||
&mut *(ptr as *const _ as *mut _)
|
||||
};
|
||||
apply_mask(p, mask);
|
||||
}
|
||||
@ -281,9 +274,28 @@ impl Frame {
|
||||
})))
|
||||
}
|
||||
|
||||
/// Parse the payload of a close frame.
|
||||
pub fn parse_close_payload(payload: &Binary) -> Option<CloseReason> {
|
||||
if payload.len() >= 2 {
|
||||
let raw_code = NetworkEndian::read_uint(payload.as_ref(), 2) as u16;
|
||||
let code = CloseCode::from(raw_code);
|
||||
let description = if payload.len() > 2 {
|
||||
Some(String::from_utf8_lossy(&payload.as_ref()[2..]).into())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Some(CloseReason {
|
||||
code,
|
||||
description,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate binary representation
|
||||
pub fn message<B: Into<Binary>>(
|
||||
data: B, code: OpCode, finished: bool, genmask: bool
|
||||
data: B, code: OpCode, finished: bool, genmask: bool,
|
||||
) -> Binary {
|
||||
let payload = data.into();
|
||||
let one: u8 = if finished {
|
||||
@ -518,10 +530,17 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_close_frame() {
|
||||
let frame = Frame::close(CloseCode::Normal, "data", false);
|
||||
let reason = (CloseCode::Normal, "data");
|
||||
let frame = Frame::close(Some(reason.into()), false);
|
||||
|
||||
let mut v = vec![136u8, 6u8, 3u8, 232u8];
|
||||
v.extend(b"data");
|
||||
assert_eq!(frame, v.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_close_frame() {
|
||||
let frame = Frame::close(None, false);
|
||||
assert_eq!(frame, vec![0x88, 0x00].into());
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
//! This is code from [Tungstenite project](https://github.com/snapview/tungstenite-rs)
|
||||
#![cfg_attr(feature = "cargo-clippy", allow(cast_ptr_alignment))]
|
||||
use std::cmp::min;
|
||||
use std::mem::uninitialized;
|
||||
use std::ptr::copy_nonoverlapping;
|
||||
|
@ -43,7 +43,6 @@
|
||||
//! # .finish();
|
||||
//! # }
|
||||
//! ```
|
||||
use byteorder::{ByteOrder, NetworkEndian};
|
||||
use bytes::Bytes;
|
||||
use futures::{Async, Poll, Stream};
|
||||
use http::{header, Method, StatusCode};
|
||||
@ -66,8 +65,7 @@ mod proto;
|
||||
pub use self::client::{Client, ClientError, ClientHandshake, ClientReader, ClientWriter};
|
||||
pub use self::context::WebsocketContext;
|
||||
pub use self::frame::Frame;
|
||||
pub use self::proto::CloseCode;
|
||||
pub use self::proto::OpCode;
|
||||
pub use self::proto::{CloseCode, CloseReason, OpCode};
|
||||
|
||||
/// Websocket protocol errors
|
||||
#[derive(Fail, Debug)]
|
||||
@ -164,7 +162,7 @@ pub enum Message {
|
||||
Binary(Binary),
|
||||
Ping(String),
|
||||
Pong(String),
|
||||
Close(CloseCode),
|
||||
Close(Option<CloseReason>),
|
||||
}
|
||||
|
||||
/// Do websocket handshake and start actor
|
||||
@ -191,7 +189,7 @@ where
|
||||
// /// the returned response headers contain the first protocol in this list
|
||||
// /// which the server also knows.
|
||||
pub fn handshake<S>(
|
||||
req: &HttpRequest<S>
|
||||
req: &HttpRequest<S>,
|
||||
) -> Result<HttpResponseBuilder, HandshakeError> {
|
||||
// WebSocket accepts only GET
|
||||
if *req.method() != Method::GET {
|
||||
@ -310,10 +308,8 @@ where
|
||||
}
|
||||
OpCode::Close => {
|
||||
self.closed = true;
|
||||
let code = NetworkEndian::read_uint(payload.as_ref(), 2) as u16;
|
||||
Ok(Async::Ready(Some(Message::Close(CloseCode::from(
|
||||
code,
|
||||
)))))
|
||||
let close_reason = Frame::parse_close_payload(&payload);
|
||||
Ok(Async::Ready(Some(Message::Close(close_reason))))
|
||||
}
|
||||
OpCode::Ping => Ok(Async::Ready(Some(Message::Ping(
|
||||
String::from_utf8_lossy(payload.as_ref()).into(),
|
||||
@ -344,6 +340,20 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Common writing methods for a websocket.
|
||||
pub trait WsWriter {
|
||||
/// Send a text
|
||||
fn text<T: Into<Binary>>(&mut self, text: T);
|
||||
/// Send a binary
|
||||
fn binary<B: Into<Binary>>(&mut self, data: B);
|
||||
/// Send a ping message
|
||||
fn ping(&mut self, message: &str);
|
||||
/// Send a pong message
|
||||
fn pong(&mut self, message: &str);
|
||||
/// Close the connection
|
||||
fn close(&mut self, reason: Option<CloseReason>);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -90,10 +90,6 @@ pub enum CloseCode {
|
||||
/// endpoint that understands only text data MAY send this if it
|
||||
/// receives a binary message).
|
||||
Unsupported,
|
||||
/// Indicates that no status code was included in a closing frame. This
|
||||
/// close code makes it possible to use a single method, `on_close` to
|
||||
/// handle even cases where no close code was provided.
|
||||
Status,
|
||||
/// Indicates an abnormal closure. If the abnormal closure was due to an
|
||||
/// error, this close code will not be used. Instead, the `on_error` method
|
||||
/// of the handler will be called with the error. However, if the connection
|
||||
@ -138,8 +134,6 @@ pub enum CloseCode {
|
||||
#[doc(hidden)]
|
||||
Tls,
|
||||
#[doc(hidden)]
|
||||
Empty,
|
||||
#[doc(hidden)]
|
||||
Other(u16),
|
||||
}
|
||||
|
||||
@ -150,7 +144,6 @@ impl Into<u16> for CloseCode {
|
||||
Away => 1001,
|
||||
Protocol => 1002,
|
||||
Unsupported => 1003,
|
||||
Status => 1005,
|
||||
Abnormal => 1006,
|
||||
Invalid => 1007,
|
||||
Policy => 1008,
|
||||
@ -160,7 +153,6 @@ impl Into<u16> for CloseCode {
|
||||
Restart => 1012,
|
||||
Again => 1013,
|
||||
Tls => 1015,
|
||||
Empty => 0,
|
||||
Other(code) => code,
|
||||
}
|
||||
}
|
||||
@ -173,7 +165,6 @@ impl From<u16> for CloseCode {
|
||||
1001 => Away,
|
||||
1002 => Protocol,
|
||||
1003 => Unsupported,
|
||||
1005 => Status,
|
||||
1006 => Abnormal,
|
||||
1007 => Invalid,
|
||||
1008 => Policy,
|
||||
@ -183,12 +174,35 @@ impl From<u16> for CloseCode {
|
||||
1012 => Restart,
|
||||
1013 => Again,
|
||||
1015 => Tls,
|
||||
0 => Empty,
|
||||
_ => Other(code),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
pub struct CloseReason {
|
||||
pub code: CloseCode,
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
impl From<CloseCode> for CloseReason {
|
||||
fn from(code: CloseCode) -> Self {
|
||||
CloseReason {
|
||||
code,
|
||||
description: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<String>> From<(CloseCode, T)> for CloseReason {
|
||||
fn from(info: (CloseCode, T)) -> Self {
|
||||
CloseReason {
|
||||
code: info.0,
|
||||
description: Some(info.1.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static WS_GUID: &'static str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
|
||||
// TODO: hash is always same size, we dont need String
|
||||
@ -269,7 +283,6 @@ mod test {
|
||||
assert_eq!(CloseCode::from(1001u16), CloseCode::Away);
|
||||
assert_eq!(CloseCode::from(1002u16), CloseCode::Protocol);
|
||||
assert_eq!(CloseCode::from(1003u16), CloseCode::Unsupported);
|
||||
assert_eq!(CloseCode::from(1005u16), CloseCode::Status);
|
||||
assert_eq!(CloseCode::from(1006u16), CloseCode::Abnormal);
|
||||
assert_eq!(CloseCode::from(1007u16), CloseCode::Invalid);
|
||||
assert_eq!(CloseCode::from(1008u16), CloseCode::Policy);
|
||||
@ -279,7 +292,6 @@ mod test {
|
||||
assert_eq!(CloseCode::from(1012u16), CloseCode::Restart);
|
||||
assert_eq!(CloseCode::from(1013u16), CloseCode::Again);
|
||||
assert_eq!(CloseCode::from(1015u16), CloseCode::Tls);
|
||||
assert_eq!(CloseCode::from(0u16), CloseCode::Empty);
|
||||
assert_eq!(CloseCode::from(2000u16), CloseCode::Other(2000));
|
||||
}
|
||||
|
||||
@ -289,7 +301,6 @@ mod test {
|
||||
assert_eq!(1001u16, Into::<u16>::into(CloseCode::Away));
|
||||
assert_eq!(1002u16, Into::<u16>::into(CloseCode::Protocol));
|
||||
assert_eq!(1003u16, Into::<u16>::into(CloseCode::Unsupported));
|
||||
assert_eq!(1005u16, Into::<u16>::into(CloseCode::Status));
|
||||
assert_eq!(1006u16, Into::<u16>::into(CloseCode::Abnormal));
|
||||
assert_eq!(1007u16, Into::<u16>::into(CloseCode::Invalid));
|
||||
assert_eq!(1008u16, Into::<u16>::into(CloseCode::Policy));
|
||||
@ -299,7 +310,6 @@ mod test {
|
||||
assert_eq!(1012u16, Into::<u16>::into(CloseCode::Restart));
|
||||
assert_eq!(1013u16, Into::<u16>::into(CloseCode::Again));
|
||||
assert_eq!(1015u16, Into::<u16>::into(CloseCode::Tls));
|
||||
assert_eq!(0u16, Into::<u16>::into(CloseCode::Empty));
|
||||
assert_eq!(2000u16, Into::<u16>::into(CloseCode::Other(2000)));
|
||||
}
|
||||
}
|
||||
|
1
tests/test.binary
Normal file
1
tests/test.binary
Normal file
@ -0,0 +1 @@
|
||||
<EFBFBD>TǑɂV<EFBFBD>2<EFBFBD>vI<EFBFBD><EFBFBD><EFBFBD>\<5C>R˙<52><CB99><EFBFBD>e<EFBFBD><04>vD<76>:藽<>RV<03>Yp<59><70>;<3B><>G<><47>p!2<7F>C<EFBFBD>.<2E><0C><><EFBFBD><EFBFBD>pA!<21>ߦ<EFBFBD>x j+Uc<55><63><EFBFBD>X<13>c%<17>;<3B>"y<10><>AI
|
BIN
tests/test.png
Normal file
BIN
tests/test.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 168 B |
@ -1,3 +1,4 @@
|
||||
#![allow(deprecated)]
|
||||
extern crate actix;
|
||||
extern crate actix_web;
|
||||
extern crate bytes;
|
||||
@ -9,8 +10,8 @@ use std::io::Read;
|
||||
|
||||
use bytes::Bytes;
|
||||
use flate2::read::GzDecoder;
|
||||
use futures::Future;
|
||||
use futures::stream::once;
|
||||
use futures::Future;
|
||||
use rand::Rng;
|
||||
|
||||
use actix_web::*;
|
||||
|
@ -7,10 +7,17 @@ extern crate http;
|
||||
extern crate tokio_core;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate serde_json;
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use actix::*;
|
||||
use actix_web::*;
|
||||
use bytes::Bytes;
|
||||
use futures::Future;
|
||||
use http::StatusCode;
|
||||
use serde_json::Value;
|
||||
use tokio_core::reactor::Timeout;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct PParam {
|
||||
@ -67,6 +74,33 @@ fn test_query_extractor() {
|
||||
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_async_extractor_async() {
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
app.resource("/{username}/index.html", |r| {
|
||||
r.route().with(|data: Json<Value>| {
|
||||
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
|
||||
.unwrap()
|
||||
.and_then(move |_| Ok(format!("{}", data.0)))
|
||||
.responder()
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
// client request
|
||||
let request = srv.post()
|
||||
.uri(srv.url("/test1/index.html"))
|
||||
.header("content-type", "application/json")
|
||||
.body("{\"test\": 1}")
|
||||
.unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
// read response
|
||||
let bytes = srv.execute(response.body()).unwrap();
|
||||
assert_eq!(bytes, Bytes::from_static(b"{\"test\":1}"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_and_query_extractor() {
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
@ -130,6 +164,219 @@ fn test_path_and_query_extractor2() {
|
||||
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_and_query_extractor2_async() {
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
app.resource("/{username}/index.html", |r| {
|
||||
r.route().with3(
|
||||
|p: Path<PParam>, _: Query<PParam>, data: Json<Value>| {
|
||||
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
|
||||
.unwrap()
|
||||
.and_then(move |_| {
|
||||
Ok(format!("Welcome {} - {}!", p.username, data.0))
|
||||
})
|
||||
.responder()
|
||||
},
|
||||
)
|
||||
});
|
||||
});
|
||||
|
||||
// client request
|
||||
let request = srv.post()
|
||||
.uri(srv.url("/test1/index.html?username=test2"))
|
||||
.header("content-type", "application/json")
|
||||
.body("{\"test\": 1}")
|
||||
.unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
// read response
|
||||
let bytes = srv.execute(response.body()).unwrap();
|
||||
assert_eq!(
|
||||
bytes,
|
||||
Bytes::from_static(b"Welcome test1 - {\"test\":1}!")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_and_query_extractor3_async() {
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
app.resource("/{username}/index.html", |r| {
|
||||
r.route().with2(|p: Path<PParam>, data: Json<Value>| {
|
||||
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
|
||||
.unwrap()
|
||||
.and_then(move |_| {
|
||||
Ok(format!("Welcome {} - {}!", p.username, data.0))
|
||||
})
|
||||
.responder()
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
// client request
|
||||
let request = srv.post()
|
||||
.uri(srv.url("/test1/index.html"))
|
||||
.header("content-type", "application/json")
|
||||
.body("{\"test\": 1}")
|
||||
.unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_and_query_extractor4_async() {
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
app.resource("/{username}/index.html", |r| {
|
||||
r.route().with2(|data: Json<Value>, p: Path<PParam>| {
|
||||
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
|
||||
.unwrap()
|
||||
.and_then(move |_| {
|
||||
Ok(format!("Welcome {} - {}!", p.username, data.0))
|
||||
})
|
||||
.responder()
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
// client request
|
||||
let request = srv.post()
|
||||
.uri(srv.url("/test1/index.html"))
|
||||
.header("content-type", "application/json")
|
||||
.body("{\"test\": 1}")
|
||||
.unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_and_query_extractor2_async2() {
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
app.resource("/{username}/index.html", |r| {
|
||||
r.route().with3(
|
||||
|p: Path<PParam>, data: Json<Value>, _: Query<PParam>| {
|
||||
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
|
||||
.unwrap()
|
||||
.and_then(move |_| {
|
||||
Ok(format!("Welcome {} - {}!", p.username, data.0))
|
||||
})
|
||||
.responder()
|
||||
},
|
||||
)
|
||||
});
|
||||
});
|
||||
|
||||
// client request
|
||||
let request = srv.post()
|
||||
.uri(srv.url("/test1/index.html?username=test2"))
|
||||
.header("content-type", "application/json")
|
||||
.body("{\"test\": 1}")
|
||||
.unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
// read response
|
||||
let bytes = srv.execute(response.body()).unwrap();
|
||||
assert_eq!(
|
||||
bytes,
|
||||
Bytes::from_static(b"Welcome test1 - {\"test\":1}!")
|
||||
);
|
||||
|
||||
// client request
|
||||
let request = srv.get()
|
||||
.uri(srv.url("/test1/index.html"))
|
||||
.finish()
|
||||
.unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_and_query_extractor2_async3() {
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
app.resource("/{username}/index.html", |r| {
|
||||
r.route().with3(
|
||||
|data: Json<Value>, p: Path<PParam>, _: Query<PParam>| {
|
||||
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
|
||||
.unwrap()
|
||||
.and_then(move |_| {
|
||||
Ok(format!("Welcome {} - {}!", p.username, data.0))
|
||||
})
|
||||
.responder()
|
||||
},
|
||||
)
|
||||
});
|
||||
});
|
||||
|
||||
// client request
|
||||
let request = srv.post()
|
||||
.uri(srv.url("/test1/index.html?username=test2"))
|
||||
.header("content-type", "application/json")
|
||||
.body("{\"test\": 1}")
|
||||
.unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
// read response
|
||||
let bytes = srv.execute(response.body()).unwrap();
|
||||
assert_eq!(
|
||||
bytes,
|
||||
Bytes::from_static(b"Welcome test1 - {\"test\":1}!")
|
||||
);
|
||||
|
||||
// client request
|
||||
let request = srv.get()
|
||||
.uri(srv.url("/test1/index.html"))
|
||||
.finish()
|
||||
.unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_and_query_extractor2_async4() {
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
app.resource("/{username}/index.html", |r| {
|
||||
r.route()
|
||||
.with(|data: (Json<Value>, Path<PParam>, Query<PParam>)| {
|
||||
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
|
||||
.unwrap()
|
||||
.and_then(move |_| {
|
||||
Ok(format!(
|
||||
"Welcome {} - {}!",
|
||||
data.1.username,
|
||||
(data.0).0
|
||||
))
|
||||
})
|
||||
.responder()
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
// client request
|
||||
let request = srv.post()
|
||||
.uri(srv.url("/test1/index.html?username=test2"))
|
||||
.header("content-type", "application/json")
|
||||
.body("{\"test\": 1}")
|
||||
.unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
// read response
|
||||
let bytes = srv.execute(response.body()).unwrap();
|
||||
assert_eq!(
|
||||
bytes,
|
||||
Bytes::from_static(b"Welcome test1 - {\"test\":1}!")
|
||||
);
|
||||
|
||||
// client request
|
||||
let request = srv.get()
|
||||
.uri(srv.url("/test1/index.html"))
|
||||
.finish()
|
||||
.unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_non_ascii_route() {
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
|
705
tests/test_middleware.rs
Normal file
705
tests/test_middleware.rs
Normal file
@ -0,0 +1,705 @@
|
||||
extern crate actix;
|
||||
extern crate actix_web;
|
||||
extern crate futures;
|
||||
extern crate tokio_core;
|
||||
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use actix::*;
|
||||
use actix_web::*;
|
||||
use futures::{future, Future};
|
||||
use tokio_core::reactor::Timeout;
|
||||
|
||||
struct MiddlewareTest {
|
||||
start: Arc<AtomicUsize>,
|
||||
response: Arc<AtomicUsize>,
|
||||
finish: Arc<AtomicUsize>,
|
||||
}
|
||||
|
||||
impl<S> middleware::Middleware<S> for MiddlewareTest {
|
||||
fn start(&self, _: &mut HttpRequest<S>) -> Result<middleware::Started> {
|
||||
self.start.store(
|
||||
self.start.load(Ordering::Relaxed) + 1,
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
Ok(middleware::Started::Done)
|
||||
}
|
||||
|
||||
fn response(
|
||||
&self, _: &mut HttpRequest<S>, resp: HttpResponse,
|
||||
) -> Result<middleware::Response> {
|
||||
self.response.store(
|
||||
self.response.load(Ordering::Relaxed) + 1,
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
Ok(middleware::Response::Done(resp))
|
||||
}
|
||||
|
||||
fn finish(&self, _: &mut HttpRequest<S>, _: &HttpResponse) -> middleware::Finished {
|
||||
self.finish.store(
|
||||
self.finish.load(Ordering::Relaxed) + 1,
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
middleware::Finished::Done
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_middleware() {
|
||||
let num1 = Arc::new(AtomicUsize::new(0));
|
||||
let num2 = Arc::new(AtomicUsize::new(0));
|
||||
let num3 = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let act_num1 = Arc::clone(&num1);
|
||||
let act_num2 = Arc::clone(&num2);
|
||||
let act_num3 = Arc::clone(&num3);
|
||||
|
||||
let mut srv = test::TestServer::new(move |app| {
|
||||
app.middleware(MiddlewareTest {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
}).handler(|_| HttpResponse::Ok())
|
||||
});
|
||||
|
||||
let request = srv.get().finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
assert_eq!(num1.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(num2.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(num3.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_middleware_multiple() {
|
||||
let num1 = Arc::new(AtomicUsize::new(0));
|
||||
let num2 = Arc::new(AtomicUsize::new(0));
|
||||
let num3 = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let act_num1 = Arc::clone(&num1);
|
||||
let act_num2 = Arc::clone(&num2);
|
||||
let act_num3 = Arc::clone(&num3);
|
||||
|
||||
let mut srv = test::TestServer::new(move |app| {
|
||||
app.middleware(MiddlewareTest {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
}).middleware(MiddlewareTest {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
})
|
||||
.handler(|_| HttpResponse::Ok())
|
||||
});
|
||||
|
||||
let request = srv.get().finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
assert_eq!(num1.load(Ordering::Relaxed), 2);
|
||||
assert_eq!(num2.load(Ordering::Relaxed), 2);
|
||||
assert_eq!(num3.load(Ordering::Relaxed), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resource_middleware() {
|
||||
let num1 = Arc::new(AtomicUsize::new(0));
|
||||
let num2 = Arc::new(AtomicUsize::new(0));
|
||||
let num3 = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let act_num1 = Arc::clone(&num1);
|
||||
let act_num2 = Arc::clone(&num2);
|
||||
let act_num3 = Arc::clone(&num3);
|
||||
|
||||
let mut srv = test::TestServer::new(move |app| {
|
||||
app.middleware(MiddlewareTest {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
}).handler(|_| HttpResponse::Ok())
|
||||
});
|
||||
|
||||
let request = srv.get().finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
assert_eq!(num1.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(num2.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(num3.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resource_middleware_multiple() {
|
||||
let num1 = Arc::new(AtomicUsize::new(0));
|
||||
let num2 = Arc::new(AtomicUsize::new(0));
|
||||
let num3 = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let act_num1 = Arc::clone(&num1);
|
||||
let act_num2 = Arc::clone(&num2);
|
||||
let act_num3 = Arc::clone(&num3);
|
||||
|
||||
let mut srv = test::TestServer::new(move |app| {
|
||||
app.middleware(MiddlewareTest {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
}).middleware(MiddlewareTest {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
})
|
||||
.handler(|_| HttpResponse::Ok())
|
||||
});
|
||||
|
||||
let request = srv.get().finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
assert_eq!(num1.load(Ordering::Relaxed), 2);
|
||||
assert_eq!(num2.load(Ordering::Relaxed), 2);
|
||||
assert_eq!(num3.load(Ordering::Relaxed), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scope_middleware() {
|
||||
let num1 = Arc::new(AtomicUsize::new(0));
|
||||
let num2 = Arc::new(AtomicUsize::new(0));
|
||||
let num3 = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let act_num1 = Arc::clone(&num1);
|
||||
let act_num2 = Arc::clone(&num2);
|
||||
let act_num3 = Arc::clone(&num3);
|
||||
|
||||
let mut srv = test::TestServer::with_factory(move || {
|
||||
App::new().scope("/scope", |scope| {
|
||||
scope
|
||||
.middleware(MiddlewareTest {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
})
|
||||
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
|
||||
})
|
||||
});
|
||||
|
||||
let request = srv.get()
|
||||
.uri(srv.url("/scope/test"))
|
||||
.finish()
|
||||
.unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
assert_eq!(num1.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(num2.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(num3.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scope_middleware_multiple() {
|
||||
let num1 = Arc::new(AtomicUsize::new(0));
|
||||
let num2 = Arc::new(AtomicUsize::new(0));
|
||||
let num3 = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let act_num1 = Arc::clone(&num1);
|
||||
let act_num2 = Arc::clone(&num2);
|
||||
let act_num3 = Arc::clone(&num3);
|
||||
|
||||
let mut srv = test::TestServer::with_factory(move || {
|
||||
App::new().scope("/scope", |scope| {
|
||||
scope
|
||||
.middleware(MiddlewareTest {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
})
|
||||
.middleware(MiddlewareTest {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
})
|
||||
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
|
||||
})
|
||||
});
|
||||
|
||||
let request = srv.get()
|
||||
.uri(srv.url("/scope/test"))
|
||||
.finish()
|
||||
.unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
assert_eq!(num1.load(Ordering::Relaxed), 2);
|
||||
assert_eq!(num2.load(Ordering::Relaxed), 2);
|
||||
assert_eq!(num3.load(Ordering::Relaxed), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_middleware_async_handler() {
|
||||
let num1 = Arc::new(AtomicUsize::new(0));
|
||||
let num2 = Arc::new(AtomicUsize::new(0));
|
||||
let num3 = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let act_num1 = Arc::clone(&num1);
|
||||
let act_num2 = Arc::clone(&num2);
|
||||
let act_num3 = Arc::clone(&num3);
|
||||
|
||||
let mut srv = test::TestServer::with_factory(move || {
|
||||
App::new()
|
||||
.middleware(MiddlewareAsyncTest {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
})
|
||||
.resource("/", |r| {
|
||||
r.route().a(|_| {
|
||||
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
|
||||
.unwrap()
|
||||
.and_then(|_| Ok(HttpResponse::Ok()))
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
let request = srv.get().finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
assert_eq!(num1.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(num2.load(Ordering::Relaxed), 1);
|
||||
thread::sleep(Duration::from_millis(20));
|
||||
assert_eq!(num3.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resource_middleware_async_handler() {
|
||||
let num1 = Arc::new(AtomicUsize::new(0));
|
||||
let num2 = Arc::new(AtomicUsize::new(0));
|
||||
let num3 = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let act_num1 = Arc::clone(&num1);
|
||||
let act_num2 = Arc::clone(&num2);
|
||||
let act_num3 = Arc::clone(&num3);
|
||||
|
||||
let mut srv = test::TestServer::with_factory(move || {
|
||||
let mw = MiddlewareAsyncTest {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
};
|
||||
App::new().resource("/test", |r| {
|
||||
r.middleware(mw);
|
||||
r.route().a(|_| {
|
||||
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
|
||||
.unwrap()
|
||||
.and_then(|_| Ok(HttpResponse::Ok()))
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
assert_eq!(num1.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(num2.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(num3.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scope_middleware_async_handler() {
|
||||
let num1 = Arc::new(AtomicUsize::new(0));
|
||||
let num2 = Arc::new(AtomicUsize::new(0));
|
||||
let num3 = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let act_num1 = Arc::clone(&num1);
|
||||
let act_num2 = Arc::clone(&num2);
|
||||
let act_num3 = Arc::clone(&num3);
|
||||
|
||||
let mut srv = test::TestServer::with_factory(move || {
|
||||
App::new().scope("/scope", |scope| {
|
||||
scope
|
||||
.middleware(MiddlewareAsyncTest {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
})
|
||||
.resource("/test", |r| {
|
||||
r.route().a(|_| {
|
||||
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
|
||||
.unwrap()
|
||||
.and_then(|_| Ok(HttpResponse::Ok()))
|
||||
})
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
let request = srv.get()
|
||||
.uri(srv.url("/scope/test"))
|
||||
.finish()
|
||||
.unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
assert_eq!(num1.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(num2.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(num3.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
|
||||
fn index_test_middleware_async_error(_: HttpRequest) -> FutureResponse<HttpResponse> {
|
||||
future::result(Err(error::ErrorBadRequest("TEST"))).responder()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_middleware_async_error() {
|
||||
let req = Arc::new(AtomicUsize::new(0));
|
||||
let resp = Arc::new(AtomicUsize::new(0));
|
||||
let fin = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let act_req = Arc::clone(&req);
|
||||
let act_resp = Arc::clone(&resp);
|
||||
let act_fin = Arc::clone(&fin);
|
||||
|
||||
let mut srv = test::TestServer::new(move |app| {
|
||||
app.middleware(MiddlewareTest {
|
||||
start: Arc::clone(&act_req),
|
||||
response: Arc::clone(&act_resp),
|
||||
finish: Arc::clone(&act_fin),
|
||||
}).handler(index_test_middleware_async_error)
|
||||
});
|
||||
|
||||
let request = srv.get().finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert_eq!(response.status(), http::StatusCode::BAD_REQUEST);
|
||||
|
||||
assert_eq!(req.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(resp.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(fin.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scope_middleware_async_error() {
|
||||
let req = Arc::new(AtomicUsize::new(0));
|
||||
let resp = Arc::new(AtomicUsize::new(0));
|
||||
let fin = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let act_req = Arc::clone(&req);
|
||||
let act_resp = Arc::clone(&resp);
|
||||
let act_fin = Arc::clone(&fin);
|
||||
|
||||
let mut srv = test::TestServer::with_factory(move || {
|
||||
App::new().scope("/scope", |scope| {
|
||||
scope
|
||||
.middleware(MiddlewareAsyncTest {
|
||||
start: Arc::clone(&act_req),
|
||||
response: Arc::clone(&act_resp),
|
||||
finish: Arc::clone(&act_fin),
|
||||
})
|
||||
.resource("/test", |r| r.f(index_test_middleware_async_error))
|
||||
})
|
||||
});
|
||||
|
||||
let request = srv.get()
|
||||
.uri(srv.url("/scope/test"))
|
||||
.finish()
|
||||
.unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert_eq!(response.status(), http::StatusCode::BAD_REQUEST);
|
||||
|
||||
assert_eq!(req.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(resp.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(fin.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resource_middleware_async_error() {
|
||||
let req = Arc::new(AtomicUsize::new(0));
|
||||
let resp = Arc::new(AtomicUsize::new(0));
|
||||
let fin = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let act_req = Arc::clone(&req);
|
||||
let act_resp = Arc::clone(&resp);
|
||||
let act_fin = Arc::clone(&fin);
|
||||
|
||||
let mut srv = test::TestServer::with_factory(move || {
|
||||
let mw = MiddlewareAsyncTest {
|
||||
start: Arc::clone(&act_req),
|
||||
response: Arc::clone(&act_resp),
|
||||
finish: Arc::clone(&act_fin),
|
||||
};
|
||||
|
||||
App::new().resource("/test", move |r| {
|
||||
r.middleware(mw);
|
||||
r.h(index_test_middleware_async_error);
|
||||
})
|
||||
});
|
||||
|
||||
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert_eq!(response.status(), http::StatusCode::BAD_REQUEST);
|
||||
|
||||
assert_eq!(req.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(resp.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(fin.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
|
||||
struct MiddlewareAsyncTest {
|
||||
start: Arc<AtomicUsize>,
|
||||
response: Arc<AtomicUsize>,
|
||||
finish: Arc<AtomicUsize>,
|
||||
}
|
||||
|
||||
impl<S> middleware::Middleware<S> for MiddlewareAsyncTest {
|
||||
fn start(&self, _: &mut HttpRequest<S>) -> Result<middleware::Started> {
|
||||
let to = Timeout::new(Duration::from_millis(10), &Arbiter::handle()).unwrap();
|
||||
|
||||
let start = Arc::clone(&self.start);
|
||||
Ok(middleware::Started::Future(Box::new(
|
||||
to.from_err().and_then(move |_| {
|
||||
start.fetch_add(1, Ordering::Relaxed);
|
||||
Ok(None)
|
||||
}),
|
||||
)))
|
||||
}
|
||||
|
||||
fn response(
|
||||
&self, _: &mut HttpRequest<S>, resp: HttpResponse,
|
||||
) -> Result<middleware::Response> {
|
||||
let to = Timeout::new(Duration::from_millis(10), &Arbiter::handle()).unwrap();
|
||||
|
||||
let response = Arc::clone(&self.response);
|
||||
Ok(middleware::Response::Future(Box::new(
|
||||
to.from_err().and_then(move |_| {
|
||||
response.fetch_add(1, Ordering::Relaxed);
|
||||
Ok(resp)
|
||||
}),
|
||||
)))
|
||||
}
|
||||
|
||||
fn finish(&self, _: &mut HttpRequest<S>, _: &HttpResponse) -> middleware::Finished {
|
||||
let to = Timeout::new(Duration::from_millis(10), &Arbiter::handle()).unwrap();
|
||||
|
||||
let finish = Arc::clone(&self.finish);
|
||||
middleware::Finished::Future(Box::new(to.from_err().and_then(move |_| {
|
||||
finish.fetch_add(1, Ordering::Relaxed);
|
||||
Ok(())
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_async_middleware() {
|
||||
let num1 = Arc::new(AtomicUsize::new(0));
|
||||
let num2 = Arc::new(AtomicUsize::new(0));
|
||||
let num3 = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let act_num1 = Arc::clone(&num1);
|
||||
let act_num2 = Arc::clone(&num2);
|
||||
let act_num3 = Arc::clone(&num3);
|
||||
|
||||
let mut srv = test::TestServer::new(move |app| {
|
||||
app.middleware(MiddlewareAsyncTest {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
}).handler(|_| HttpResponse::Ok())
|
||||
});
|
||||
|
||||
let request = srv.get().finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
assert_eq!(num1.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(num2.load(Ordering::Relaxed), 1);
|
||||
|
||||
thread::sleep(Duration::from_millis(20));
|
||||
assert_eq!(num3.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_async_middleware_multiple() {
|
||||
let num1 = Arc::new(AtomicUsize::new(0));
|
||||
let num2 = Arc::new(AtomicUsize::new(0));
|
||||
let num3 = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let act_num1 = Arc::clone(&num1);
|
||||
let act_num2 = Arc::clone(&num2);
|
||||
let act_num3 = Arc::clone(&num3);
|
||||
|
||||
let mut srv = test::TestServer::with_factory(move || {
|
||||
App::new()
|
||||
.middleware(MiddlewareAsyncTest {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
})
|
||||
.middleware(MiddlewareAsyncTest {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
})
|
||||
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
|
||||
});
|
||||
|
||||
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
assert_eq!(num1.load(Ordering::Relaxed), 2);
|
||||
assert_eq!(num2.load(Ordering::Relaxed), 2);
|
||||
|
||||
thread::sleep(Duration::from_millis(50));
|
||||
assert_eq!(num3.load(Ordering::Relaxed), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_async_scope_middleware() {
|
||||
let num1 = Arc::new(AtomicUsize::new(0));
|
||||
let num2 = Arc::new(AtomicUsize::new(0));
|
||||
let num3 = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let act_num1 = Arc::clone(&num1);
|
||||
let act_num2 = Arc::clone(&num2);
|
||||
let act_num3 = Arc::clone(&num3);
|
||||
|
||||
let mut srv = test::TestServer::with_factory(move || {
|
||||
App::new().scope("/scope", |scope| {
|
||||
scope
|
||||
.middleware(MiddlewareAsyncTest {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
})
|
||||
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
|
||||
})
|
||||
});
|
||||
|
||||
let request = srv.get()
|
||||
.uri(srv.url("/scope/test"))
|
||||
.finish()
|
||||
.unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
assert_eq!(num1.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(num2.load(Ordering::Relaxed), 1);
|
||||
|
||||
thread::sleep(Duration::from_millis(20));
|
||||
assert_eq!(num3.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_async_scope_middleware_multiple() {
|
||||
let num1 = Arc::new(AtomicUsize::new(0));
|
||||
let num2 = Arc::new(AtomicUsize::new(0));
|
||||
let num3 = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let act_num1 = Arc::clone(&num1);
|
||||
let act_num2 = Arc::clone(&num2);
|
||||
let act_num3 = Arc::clone(&num3);
|
||||
|
||||
let mut srv = test::TestServer::with_factory(move || {
|
||||
App::new().scope("/scope", |scope| {
|
||||
scope
|
||||
.middleware(MiddlewareAsyncTest {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
})
|
||||
.middleware(MiddlewareAsyncTest {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
})
|
||||
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
|
||||
})
|
||||
});
|
||||
|
||||
let request = srv.get()
|
||||
.uri(srv.url("/scope/test"))
|
||||
.finish()
|
||||
.unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
assert_eq!(num1.load(Ordering::Relaxed), 2);
|
||||
assert_eq!(num2.load(Ordering::Relaxed), 2);
|
||||
|
||||
thread::sleep(Duration::from_millis(20));
|
||||
assert_eq!(num3.load(Ordering::Relaxed), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_async_resource_middleware() {
|
||||
let num1 = Arc::new(AtomicUsize::new(0));
|
||||
let num2 = Arc::new(AtomicUsize::new(0));
|
||||
let num3 = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let act_num1 = Arc::clone(&num1);
|
||||
let act_num2 = Arc::clone(&num2);
|
||||
let act_num3 = Arc::clone(&num3);
|
||||
|
||||
let mut srv = test::TestServer::with_factory(move || {
|
||||
let mw = MiddlewareAsyncTest {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
};
|
||||
App::new().resource("/test", move |r| {
|
||||
r.middleware(mw);
|
||||
r.h(|_| HttpResponse::Ok());
|
||||
})
|
||||
});
|
||||
|
||||
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
assert_eq!(num1.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(num2.load(Ordering::Relaxed), 1);
|
||||
|
||||
thread::sleep(Duration::from_millis(40));
|
||||
assert_eq!(num3.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_async_resource_middleware_multiple() {
|
||||
let num1 = Arc::new(AtomicUsize::new(0));
|
||||
let num2 = Arc::new(AtomicUsize::new(0));
|
||||
let num3 = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let act_num1 = Arc::clone(&num1);
|
||||
let act_num2 = Arc::clone(&num2);
|
||||
let act_num3 = Arc::clone(&num3);
|
||||
|
||||
let mut srv = test::TestServer::with_factory(move || {
|
||||
let mw1 = MiddlewareAsyncTest {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
};
|
||||
let mw2 = MiddlewareAsyncTest {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
};
|
||||
App::new().resource("/test", move |r| {
|
||||
r.middleware(mw1);
|
||||
r.middleware(mw2);
|
||||
r.h(|_| HttpResponse::Ok());
|
||||
})
|
||||
});
|
||||
|
||||
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
assert_eq!(num1.load(Ordering::Relaxed), 2);
|
||||
assert_eq!(num2.load(Ordering::Relaxed), 2);
|
||||
|
||||
thread::sleep(Duration::from_millis(40));
|
||||
assert_eq!(num3.load(Ordering::Relaxed), 2);
|
||||
}
|
@ -14,16 +14,15 @@ extern crate brotli2;
|
||||
#[cfg(feature = "brotli")]
|
||||
use brotli2::write::{BrotliDecoder, BrotliEncoder};
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use flate2::Compression;
|
||||
use flate2::read::GzDecoder;
|
||||
use flate2::write::{DeflateDecoder, DeflateEncoder, GzEncoder};
|
||||
use flate2::Compression;
|
||||
use futures::stream::once;
|
||||
use futures::{future, Future, Stream};
|
||||
use futures::{Future, Stream};
|
||||
use h2::client as h2client;
|
||||
use modhttp::Request;
|
||||
use rand::Rng;
|
||||
use std::io::{Read, Write};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::{mpsc, Arc};
|
||||
use std::{net, thread, time};
|
||||
use tokio_core::net::TcpStream;
|
||||
@ -62,11 +61,9 @@ fn test_start() {
|
||||
thread::spawn(move || {
|
||||
let sys = System::new("test");
|
||||
let srv = server::new(|| {
|
||||
vec![
|
||||
App::new().resource("/", |r| {
|
||||
r.method(http::Method::GET).f(|_| HttpResponse::Ok())
|
||||
}),
|
||||
]
|
||||
vec![App::new().resource("/", |r| {
|
||||
r.method(http::Method::GET).f(|_| HttpResponse::Ok())
|
||||
})]
|
||||
});
|
||||
|
||||
let srv = srv.bind("127.0.0.1:0").unwrap();
|
||||
@ -113,11 +110,9 @@ fn test_shutdown() {
|
||||
thread::spawn(move || {
|
||||
let sys = System::new("test");
|
||||
let srv = server::new(|| {
|
||||
vec![
|
||||
App::new().resource("/", |r| {
|
||||
r.method(http::Method::GET).f(|_| HttpResponse::Ok())
|
||||
}),
|
||||
]
|
||||
vec![App::new().resource("/", |r| {
|
||||
r.method(http::Method::GET).f(|_| HttpResponse::Ok())
|
||||
})]
|
||||
});
|
||||
|
||||
let srv = srv.bind("127.0.0.1:0").unwrap();
|
||||
@ -827,122 +822,3 @@ fn test_application() {
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
}
|
||||
|
||||
struct MiddlewareTest {
|
||||
start: Arc<AtomicUsize>,
|
||||
response: Arc<AtomicUsize>,
|
||||
finish: Arc<AtomicUsize>,
|
||||
}
|
||||
|
||||
impl<S> middleware::Middleware<S> for MiddlewareTest {
|
||||
fn start(&self, _: &mut HttpRequest<S>) -> Result<middleware::Started> {
|
||||
self.start.store(
|
||||
self.start.load(Ordering::Relaxed) + 1,
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
Ok(middleware::Started::Done)
|
||||
}
|
||||
|
||||
fn response(
|
||||
&self, _: &mut HttpRequest<S>, resp: HttpResponse
|
||||
) -> Result<middleware::Response> {
|
||||
self.response.store(
|
||||
self.response.load(Ordering::Relaxed) + 1,
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
Ok(middleware::Response::Done(resp))
|
||||
}
|
||||
|
||||
fn finish(&self, _: &mut HttpRequest<S>, _: &HttpResponse) -> middleware::Finished {
|
||||
self.finish.store(
|
||||
self.finish.load(Ordering::Relaxed) + 1,
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
middleware::Finished::Done
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_middlewares() {
|
||||
let num1 = Arc::new(AtomicUsize::new(0));
|
||||
let num2 = Arc::new(AtomicUsize::new(0));
|
||||
let num3 = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let act_num1 = Arc::clone(&num1);
|
||||
let act_num2 = Arc::clone(&num2);
|
||||
let act_num3 = Arc::clone(&num3);
|
||||
|
||||
let mut srv = test::TestServer::new(move |app| {
|
||||
app.middleware(MiddlewareTest {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
}).handler(|_| HttpResponse::Ok())
|
||||
});
|
||||
|
||||
let request = srv.get().finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
assert_eq!(num1.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(num2.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(num3.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resource_middlewares() {
|
||||
let num1 = Arc::new(AtomicUsize::new(0));
|
||||
let num2 = Arc::new(AtomicUsize::new(0));
|
||||
let num3 = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let act_num1 = Arc::clone(&num1);
|
||||
let act_num2 = Arc::clone(&num2);
|
||||
let act_num3 = Arc::clone(&num3);
|
||||
|
||||
let mut srv = test::TestServer::new(move |app| {
|
||||
app.middleware(MiddlewareTest {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
}).handler(|_| HttpResponse::Ok())
|
||||
});
|
||||
|
||||
let request = srv.get().finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
assert_eq!(num1.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(num2.load(Ordering::Relaxed), 1);
|
||||
// assert_eq!(num3.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
|
||||
fn index_test_middleware_async_error(_: HttpRequest) -> FutureResponse<HttpResponse> {
|
||||
future::result(Err(error::ErrorBadRequest("TEST"))).responder()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_middleware_async_error() {
|
||||
let req = Arc::new(AtomicUsize::new(0));
|
||||
let resp = Arc::new(AtomicUsize::new(0));
|
||||
let fin = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let act_req = Arc::clone(&req);
|
||||
let act_resp = Arc::clone(&resp);
|
||||
let act_fin = Arc::clone(&fin);
|
||||
|
||||
let mut srv = test::TestServer::new(move |app| {
|
||||
app.middleware(MiddlewareTest {
|
||||
start: Arc::clone(&act_req),
|
||||
response: Arc::clone(&act_resp),
|
||||
finish: Arc::clone(&act_fin),
|
||||
}).handler(index_test_middleware_async_error)
|
||||
});
|
||||
|
||||
let request = srv.get().finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert_eq!(response.status(), http::StatusCode::BAD_REQUEST);
|
||||
|
||||
assert_eq!(req.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(resp.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(fin.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ impl StreamHandler<ws::Message, ws::ProtocolError> for Ws {
|
||||
ws::Message::Ping(msg) => ctx.pong(&msg),
|
||||
ws::Message::Text(text) => ctx.text(text),
|
||||
ws::Message::Binary(bin) => ctx.binary(bin),
|
||||
ws::Message::Close(reason) => ctx.close(reason, ""),
|
||||
ws::Message::Close(reason) => ctx.close(reason),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
@ -55,9 +55,34 @@ fn test_simple() {
|
||||
let (item, reader) = srv.execute(reader.into_future()).unwrap();
|
||||
assert_eq!(item, Some(ws::Message::Pong("ping".to_owned())));
|
||||
|
||||
writer.close(ws::CloseCode::Normal, "");
|
||||
writer.close(Some(ws::CloseCode::Normal.into()));
|
||||
let (item, _) = srv.execute(reader.into_future()).unwrap();
|
||||
assert_eq!(item, Some(ws::Message::Close(ws::CloseCode::Normal)));
|
||||
assert_eq!(
|
||||
item,
|
||||
Some(ws::Message::Close(Some(ws::CloseCode::Normal.into())))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_close_code() {
|
||||
let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws)));
|
||||
let (reader, mut writer) = srv.ws().unwrap();
|
||||
|
||||
writer.close(None);
|
||||
let (item, _) = srv.execute(reader.into_future()).unwrap();
|
||||
assert_eq!(item, Some(ws::Message::Close(None)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_close_description() {
|
||||
let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws)));
|
||||
let (reader, mut writer) = srv.ws().unwrap();
|
||||
|
||||
let close_reason: ws::CloseReason =
|
||||
(ws::CloseCode::Normal, "close description").into();
|
||||
writer.close(Some(close_reason.clone()));
|
||||
let (item, _) = srv.execute(reader.into_future()).unwrap();
|
||||
assert_eq!(item, Some(ws::Message::Close(Some(close_reason))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -137,7 +162,7 @@ impl StreamHandler<ws::Message, ws::ProtocolError> for Ws2 {
|
||||
ws::Message::Ping(msg) => ctx.pong(&msg),
|
||||
ws::Message::Text(text) => ctx.text(text),
|
||||
ws::Message::Binary(bin) => ctx.binary(bin),
|
||||
ws::Message::Close(reason) => ctx.close(reason, ""),
|
||||
ws::Message::Close(reason) => ctx.close(reason),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
@ -14,8 +14,8 @@ extern crate url;
|
||||
|
||||
use futures::Future;
|
||||
use rand::{thread_rng, Rng};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use actix::prelude::*;
|
||||
@ -259,7 +259,7 @@ impl StreamHandler<ws::Message, ws::ProtocolError> for ChatClient {
|
||||
ctx.stop();
|
||||
}
|
||||
} else {
|
||||
println!("not eaqual");
|
||||
println!("not equal");
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
|
Reference in New Issue
Block a user