mirror of
https://github.com/fafhrd91/actix-web
synced 2024-11-30 18:44:35 +01:00
Merge branch 'master' into ranges
This commit is contained in:
commit
a5692d4ecf
@ -31,13 +31,13 @@ environment:
|
|||||||
CHANNEL: beta
|
CHANNEL: beta
|
||||||
# Nightly channel
|
# Nightly channel
|
||||||
- TARGET: i686-pc-windows-gnu
|
- TARGET: i686-pc-windows-gnu
|
||||||
CHANNEL: nightly-2017-12-21
|
CHANNEL: nightly
|
||||||
- TARGET: i686-pc-windows-msvc
|
- TARGET: i686-pc-windows-msvc
|
||||||
CHANNEL: nightly-2017-12-21
|
CHANNEL: nightly
|
||||||
- TARGET: x86_64-pc-windows-gnu
|
- TARGET: x86_64-pc-windows-gnu
|
||||||
CHANNEL: nightly-2017-12-21
|
CHANNEL: nightly
|
||||||
- TARGET: x86_64-pc-windows-msvc
|
- TARGET: x86_64-pc-windows-msvc
|
||||||
CHANNEL: nightly-2017-12-21
|
CHANNEL: nightly
|
||||||
|
|
||||||
# Install Rust and Cargo
|
# Install Rust and Cargo
|
||||||
# (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml)
|
# (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml)
|
||||||
|
17
.travis.yml
17
.travis.yml
@ -31,8 +31,17 @@ before_script:
|
|||||||
|
|
||||||
script:
|
script:
|
||||||
- |
|
- |
|
||||||
|
if [[ "$TRAVIS_RUST_VERSION" != "beta" ]]; then
|
||||||
cargo clean
|
cargo clean
|
||||||
cargo test --features="alpn,tls" -- --nocapture
|
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 --no-count
|
||||||
|
bash <(curl -s https://codecov.io/bash)
|
||||||
|
echo "Uploaded code coverage"
|
||||||
|
fi
|
||||||
|
|
||||||
# Upload docs
|
# Upload docs
|
||||||
after_success:
|
after_success:
|
||||||
@ -44,11 +53,3 @@ after_success:
|
|||||||
./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc &&
|
./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"
|
echo "Uploaded documentation"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- |
|
|
||||||
if [[ "$TRAVIS_OS_NAME" == "linux" && "$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
|
|
||||||
|
46
CHANGES.md
46
CHANGES.md
@ -1,6 +1,50 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## 0.6.0 (...)
|
## 0.6.6 (2018-05-17)
|
||||||
|
|
||||||
|
* Panic during middleware execution #226
|
||||||
|
|
||||||
|
* Add support for listen_tls/listen_ssl #224
|
||||||
|
|
||||||
|
* Implement extractor for `Session`
|
||||||
|
|
||||||
|
|
||||||
|
## 0.6.5 (2018-05-15)
|
||||||
|
|
||||||
|
* Fix error handling during request decoding #222
|
||||||
|
|
||||||
|
|
||||||
|
## 0.6.4 (2018-05-11)
|
||||||
|
|
||||||
|
* Fix segfault in ServerSettings::get_response_builder()
|
||||||
|
|
||||||
|
|
||||||
|
## 0.6.3 (2018-05-10)
|
||||||
|
|
||||||
|
* Add `Router::with_async()` method for async handler registration.
|
||||||
|
|
||||||
|
* Added error response functions for 501,502,503,504
|
||||||
|
|
||||||
|
* Fix client request timeout handling
|
||||||
|
|
||||||
|
|
||||||
|
## 0.6.2 (2018-05-09)
|
||||||
|
|
||||||
|
* WsWriter trait is optional.
|
||||||
|
|
||||||
|
|
||||||
|
## 0.6.1 (2018-05-08)
|
||||||
|
|
||||||
|
* Fix http/2 payload streaming #215
|
||||||
|
|
||||||
|
* Fix connector's default `keep-alive` and `lifetime` settings #212
|
||||||
|
|
||||||
|
* Send `ErrorNotFound` instead of `ErrorBadRequest` when path extractor fails #214
|
||||||
|
|
||||||
|
* Allow to exclude certain endpoints from logging #211
|
||||||
|
|
||||||
|
|
||||||
|
## 0.6.0 (2018-05-08)
|
||||||
|
|
||||||
* Add route scopes #202
|
* Add route scopes #202
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-web"
|
name = "actix-web"
|
||||||
version = "0.6.0-dev"
|
version = "0.6.6"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
|
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
18
MIGRATION.md
18
MIGRATION.md
@ -1,5 +1,7 @@
|
|||||||
## Migration from 0.5 to 0.6
|
## Migration from 0.5 to 0.6
|
||||||
|
|
||||||
|
* `Path<T>` extractor return `ErrorNotFound` on failure instead of `ErrorBadRequest`
|
||||||
|
|
||||||
* `ws::Message::Close` now includes optional close reason.
|
* `ws::Message::Close` now includes optional close reason.
|
||||||
`ws::CloseCode::Status` and `ws::CloseCode::Empty` have been removed.
|
`ws::CloseCode::Status` and `ws::CloseCode::Empty` have been removed.
|
||||||
|
|
||||||
@ -11,6 +13,17 @@
|
|||||||
* `HttpRequest::extensions()` returns read only reference to the request's Extension
|
* `HttpRequest::extensions()` returns read only reference to the request's Extension
|
||||||
`HttpRequest::extensions_mut()` returns mutable reference.
|
`HttpRequest::extensions_mut()` returns mutable reference.
|
||||||
|
|
||||||
|
* Instead of
|
||||||
|
|
||||||
|
`use actix_web::middleware::{
|
||||||
|
CookieSessionBackend, CookieSessionError, RequestSession,
|
||||||
|
Session, SessionBackend, SessionImpl, SessionStorage};`
|
||||||
|
|
||||||
|
use `actix_web::middleware::session`
|
||||||
|
|
||||||
|
`use actix_web::middleware::session{CookieSessionBackend, CookieSessionError,
|
||||||
|
RequestSession, Session, SessionBackend, SessionImpl, SessionStorage};`
|
||||||
|
|
||||||
* `FromRequest::from_request()` accepts mutable reference to a request
|
* `FromRequest::from_request()` accepts mutable reference to a request
|
||||||
|
|
||||||
* `FromRequest::Result` has to implement `Into<Reply<Self>>`
|
* `FromRequest::Result` has to implement `Into<Reply<Self>>`
|
||||||
@ -19,7 +32,7 @@
|
|||||||
https://actix.rs/actix-web/actix_web/trait.Responder.html#tymethod.respond_to)
|
https://actix.rs/actix-web/actix_web/trait.Responder.html#tymethod.respond_to)
|
||||||
is generic over `S`
|
is generic over `S`
|
||||||
|
|
||||||
* `HttpRequest::query()` is deprecated. Use `Query` extractor.
|
* Use `Query` extractor instead of HttpRequest::query()`.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn index(q: Query<HashMap<String, String>>) -> Result<..> {
|
fn index(q: Query<HashMap<String, String>>) -> Result<..> {
|
||||||
@ -33,6 +46,9 @@
|
|||||||
let q = Query::<HashMap<String, String>>::extract(req);
|
let q = Query::<HashMap<String, String>>::extract(req);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
* Websocket operations are implemented as `WsWriter` trait.
|
||||||
|
you need to use `use actix_web::ws::WsWriter`
|
||||||
|
|
||||||
|
|
||||||
## Migration from 0.4 to 0.5
|
## Migration from 0.4 to 0.5
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust.
|
|||||||
[DefaultHeaders](https://actix.rs/book/actix-web/sec-9-middlewares.html#default-headers),
|
[DefaultHeaders](https://actix.rs/book/actix-web/sec-9-middlewares.html#default-headers),
|
||||||
[CORS](https://actix.rs/actix-web/actix_web/middleware/cors/index.html),
|
[CORS](https://actix.rs/actix-web/actix_web/middleware/cors/index.html),
|
||||||
[CSRF](https://actix.rs/actix-web/actix_web/middleware/csrf/index.html))
|
[CSRF](https://actix.rs/actix-web/actix_web/middleware/csrf/index.html))
|
||||||
|
* Includes an asynchronous [HTTP client](https://github.com/actix/actix-web/blob/master/src/client/mod.rs)
|
||||||
* Built on top of [Actix actor framework](https://github.com/actix/actix)
|
* Built on top of [Actix actor framework](https://github.com/actix/actix)
|
||||||
|
|
||||||
## Documentation & community resources
|
## Documentation & community resources
|
||||||
@ -36,7 +37,7 @@ extern crate actix_web;
|
|||||||
use actix_web::{http, server, App, Path};
|
use actix_web::{http, server, App, Path};
|
||||||
|
|
||||||
fn index(info: Path<(u32, String)>) -> String {
|
fn index(info: Path<(u32, String)>) -> String {
|
||||||
format!("Hello {}! id:{}", info.0, info.1)
|
format!("Hello {}! id:{}", info.1, info.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
9
build.rs
9
build.rs
@ -1,8 +1,15 @@
|
|||||||
extern crate version_check;
|
extern crate version_check;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
match version_check::is_min_version("1.26.0") {
|
||||||
|
Some((true, _)) => println!("cargo:rustc-cfg=actix_impl_trait"),
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
match version_check::is_nightly() {
|
match version_check::is_nightly() {
|
||||||
Some(true) => println!("cargo:rustc-cfg=actix_nightly"),
|
Some(true) => {
|
||||||
|
println!("cargo:rustc-cfg=actix_nightly");
|
||||||
|
println!("cargo:rustc-cfg=actix_impl_trait");
|
||||||
|
}
|
||||||
Some(false) => (),
|
Some(false) => (),
|
||||||
None => (),
|
None => (),
|
||||||
};
|
};
|
||||||
|
@ -9,6 +9,7 @@ use httprequest::HttpRequest;
|
|||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
use middleware::Middleware;
|
use middleware::Middleware;
|
||||||
use pipeline::{HandlerType, Pipeline, PipelineHandler};
|
use pipeline::{HandlerType, Pipeline, PipelineHandler};
|
||||||
|
use pred::Predicate;
|
||||||
use resource::ResourceHandler;
|
use resource::ResourceHandler;
|
||||||
use router::{Resource, Router};
|
use router::{Resource, Router};
|
||||||
use scope::Scope;
|
use scope::Scope;
|
||||||
@ -29,7 +30,12 @@ pub(crate) struct Inner<S> {
|
|||||||
default: ResourceHandler<S>,
|
default: ResourceHandler<S>,
|
||||||
encoding: ContentEncoding,
|
encoding: ContentEncoding,
|
||||||
resources: Vec<ResourceHandler<S>>,
|
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> {
|
impl<S: 'static> PipelineHandler<S> for Inner<S> {
|
||||||
@ -44,7 +50,10 @@ impl<S: 'static> PipelineHandler<S> for Inner<S> {
|
|||||||
HandlerType::Normal(idx) => {
|
HandlerType::Normal(idx) => {
|
||||||
self.resources[idx].handle(req, Some(&mut self.default))
|
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),
|
HandlerType::Default => self.default.handle(req, None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,12 +71,15 @@ impl<S: 'static> HttpApplication<S> {
|
|||||||
HandlerType::Normal(idx)
|
HandlerType::Normal(idx)
|
||||||
} else {
|
} else {
|
||||||
let inner = self.as_ref();
|
let inner = self.as_ref();
|
||||||
for idx in 0..inner.handlers.len() {
|
let path: &'static str =
|
||||||
let &(ref prefix, _) = &inner.handlers[idx];
|
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 = {
|
let m = {
|
||||||
let path = &req.path()[inner.prefix..];
|
|
||||||
path.starts_with(prefix)
|
path.starts_with(prefix)
|
||||||
&& (path.len() == prefix.len()
|
&& (path_len == prefix.len()
|
||||||
|| path.split_at(prefix.len()).1.starts_with('/'))
|
|| path.split_at(prefix.len()).1.starts_with('/'))
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -85,6 +97,31 @@ impl<S: 'static> HttpApplication<S> {
|
|||||||
return HandlerType::Handler(idx);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
HandlerType::Default
|
HandlerType::Default
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,7 +168,7 @@ struct ApplicationParts<S> {
|
|||||||
settings: ServerSettings,
|
settings: ServerSettings,
|
||||||
default: ResourceHandler<S>,
|
default: ResourceHandler<S>,
|
||||||
resources: Vec<(Resource, Option<ResourceHandler<S>>)>,
|
resources: Vec<(Resource, Option<ResourceHandler<S>>)>,
|
||||||
handlers: Vec<(String, Box<RouteHandler<S>>)>,
|
handlers: Vec<PrefixHandlerType<S>>,
|
||||||
external: HashMap<String, Resource>,
|
external: HashMap<String, Resource>,
|
||||||
encoding: ContentEncoding,
|
encoding: ContentEncoding,
|
||||||
middlewares: Vec<Box<Middleware<S>>>,
|
middlewares: Vec<Box<Middleware<S>>>,
|
||||||
@ -305,7 +342,7 @@ where
|
|||||||
/// Configure scope for common root path.
|
/// Configure scope for common root path.
|
||||||
///
|
///
|
||||||
/// Scopes collect multiple paths under a common path prefix.
|
/// Scopes collect multiple paths under a common path prefix.
|
||||||
/// Scope path can not contain variable path segments as resources.
|
/// Scope path can contain variable path segments as resources.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate actix_web;
|
/// # extern crate actix_web;
|
||||||
@ -313,7 +350,7 @@ where
|
|||||||
///
|
///
|
||||||
/// fn main() {
|
/// fn main() {
|
||||||
/// let app = App::new()
|
/// let app = App::new()
|
||||||
/// .scope("/app", |scope| {
|
/// .scope("/{project_id}", |scope| {
|
||||||
/// scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
|
/// scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
|
||||||
/// .resource("/path2", |r| r.f(|_| HttpResponse::Ok()))
|
/// .resource("/path2", |r| r.f(|_| HttpResponse::Ok()))
|
||||||
/// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed()))
|
/// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed()))
|
||||||
@ -322,24 +359,32 @@ where
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// In the above example, three routes get added:
|
/// In the above example, three routes get added:
|
||||||
/// * /app/path1
|
/// * /{project_id}/path1
|
||||||
/// * /app/path2
|
/// * /{project_id}/path2
|
||||||
/// * /app/path3
|
/// * /{project_id}/path3
|
||||||
///
|
///
|
||||||
pub fn scope<F>(mut self, path: &str, f: F) -> App<S>
|
pub fn scope<F>(mut self, path: &str, f: F) -> App<S>
|
||||||
where
|
where
|
||||||
F: FnOnce(Scope<S>) -> Scope<S>,
|
F: FnOnce(Scope<S>) -> Scope<S>,
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
let scope = Box::new(f(Scope::new()));
|
let mut scope = Box::new(f(Scope::new()));
|
||||||
|
|
||||||
let mut path = path.trim().trim_right_matches('/').to_owned();
|
let mut path = path.trim().trim_right_matches('/').to_owned();
|
||||||
if !path.is_empty() && !path.starts_with('/') {
|
if !path.is_empty() && !path.starts_with('/') {
|
||||||
path.insert(0, '/')
|
path.insert(0, '/')
|
||||||
}
|
}
|
||||||
|
if !path.ends_with('/') {
|
||||||
|
path.push('/');
|
||||||
|
}
|
||||||
let parts = self.parts.as_mut().expect("Use after finish");
|
let parts = self.parts.as_mut().expect("Use after finish");
|
||||||
|
|
||||||
parts.handlers.push((path, scope));
|
let filters = scope.take_filters();
|
||||||
|
parts.handlers.push(PrefixHandlerType::Scope(
|
||||||
|
Resource::prefix("", &path),
|
||||||
|
scope,
|
||||||
|
filters,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -496,11 +541,15 @@ where
|
|||||||
if !path.is_empty() && !path.starts_with('/') {
|
if !path.is_empty() && !path.starts_with('/') {
|
||||||
path.insert(0, '/')
|
path.insert(0, '/')
|
||||||
}
|
}
|
||||||
|
if path.len() > 1 && path.ends_with('/') {
|
||||||
|
path.pop();
|
||||||
|
}
|
||||||
let parts = self.parts.as_mut().expect("Use after finish");
|
let parts = self.parts.as_mut().expect("Use after finish");
|
||||||
|
|
||||||
parts
|
parts.handlers.push(PrefixHandlerType::Handler(
|
||||||
.handlers
|
path,
|
||||||
.push((path, Box::new(WrapHandler::new(handler))));
|
Box::new(WrapHandler::new(handler)),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
18
src/body.rs
18
src/body.rs
@ -62,10 +62,28 @@ impl Body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Is this binary empy.
|
||||||
|
#[inline]
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
match *self {
|
||||||
|
Body::Empty => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Create body from slice (copy)
|
/// Create body from slice (copy)
|
||||||
pub fn from_slice(s: &[u8]) -> Body {
|
pub fn from_slice(s: &[u8]) -> Body {
|
||||||
Body::Binary(Binary::Bytes(Bytes::from(s)))
|
Body::Binary(Binary::Bytes(Bytes::from(s)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Is this binary body.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn binary(self) -> Binary {
|
||||||
|
match self {
|
||||||
|
Body::Binary(b) => b,
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for Body {
|
impl PartialEq for Body {
|
||||||
|
@ -223,8 +223,8 @@ impl Default for ClientConnector {
|
|||||||
pool: Rc::new(Pool::new(Rc::clone(&_modified))),
|
pool: Rc::new(Pool::new(Rc::clone(&_modified))),
|
||||||
pool_modified: _modified,
|
pool_modified: _modified,
|
||||||
connector: builder.build().unwrap(),
|
connector: builder.build().unwrap(),
|
||||||
conn_lifetime: Duration::from_secs(15),
|
conn_lifetime: Duration::from_secs(75),
|
||||||
conn_keep_alive: Duration::from_secs(75),
|
conn_keep_alive: Duration::from_secs(15),
|
||||||
limit: 100,
|
limit: 100,
|
||||||
limit_per_host: 0,
|
limit_per_host: 0,
|
||||||
acquired: 0,
|
acquired: 0,
|
||||||
@ -243,8 +243,8 @@ impl Default for ClientConnector {
|
|||||||
subscriber: None,
|
subscriber: None,
|
||||||
pool: Rc::new(Pool::new(Rc::clone(&_modified))),
|
pool: Rc::new(Pool::new(Rc::clone(&_modified))),
|
||||||
pool_modified: _modified,
|
pool_modified: _modified,
|
||||||
conn_lifetime: Duration::from_secs(15),
|
conn_lifetime: Duration::from_secs(75),
|
||||||
conn_keep_alive: Duration::from_secs(75),
|
conn_keep_alive: Duration::from_secs(15),
|
||||||
limit: 100,
|
limit: 100,
|
||||||
limit_per_host: 0,
|
limit_per_host: 0,
|
||||||
acquired: 0,
|
acquired: 0,
|
||||||
@ -347,6 +347,7 @@ impl ClientConnector {
|
|||||||
/// Keep-alive period is the period between connection usage. If
|
/// Keep-alive period is the period between connection usage. If
|
||||||
/// the delay between repeated usages of the same connection
|
/// the delay between repeated usages of the same connection
|
||||||
/// exceeds this period, the connection is closed.
|
/// exceeds this period, the connection is closed.
|
||||||
|
/// Default keep-alive period is 15 seconds.
|
||||||
pub fn conn_keep_alive(mut self, dur: Duration) -> Self {
|
pub fn conn_keep_alive(mut self, dur: Duration) -> Self {
|
||||||
self.conn_keep_alive = dur;
|
self.conn_keep_alive = dur;
|
||||||
self
|
self
|
||||||
@ -356,6 +357,7 @@ impl ClientConnector {
|
|||||||
///
|
///
|
||||||
/// Connection lifetime is max lifetime of any opened connection
|
/// Connection lifetime is max lifetime of any opened connection
|
||||||
/// until it is closed regardless of keep-alive period.
|
/// until it is closed regardless of keep-alive period.
|
||||||
|
/// Default lifetime period is 75 seconds.
|
||||||
pub fn conn_lifetime(mut self, dur: Duration) -> Self {
|
pub fn conn_lifetime(mut self, dur: Duration) -> Self {
|
||||||
self.conn_lifetime = dur;
|
self.conn_lifetime = dur;
|
||||||
self
|
self
|
||||||
|
@ -49,6 +49,7 @@ use httpresponse::HttpResponse;
|
|||||||
impl ResponseError for SendRequestError {
|
impl ResponseError for SendRequestError {
|
||||||
fn error_response(&self) -> HttpResponse {
|
fn error_response(&self) -> HttpResponse {
|
||||||
match *self {
|
match *self {
|
||||||
|
SendRequestError::Timeout => HttpResponse::GatewayTimeout(),
|
||||||
SendRequestError::Connector(_) => HttpResponse::BadGateway(),
|
SendRequestError::Connector(_) => HttpResponse::BadGateway(),
|
||||||
_ => HttpResponse::InternalServerError(),
|
_ => HttpResponse::InternalServerError(),
|
||||||
}.into()
|
}.into()
|
||||||
|
@ -194,6 +194,7 @@ impl Future for SendRequest {
|
|||||||
self.state = State::Send(pl);
|
self.state = State::Send(pl);
|
||||||
}
|
}
|
||||||
State::Send(mut pl) => {
|
State::Send(mut pl) => {
|
||||||
|
pl.poll_timeout()?;
|
||||||
pl.poll_write().map_err(|e| {
|
pl.poll_write().map_err(|e| {
|
||||||
io::Error::new(io::ErrorKind::Other, format!("{}", e).as_str())
|
io::Error::new(io::ErrorKind::Other, format!("{}", e).as_str())
|
||||||
})?;
|
})?;
|
||||||
@ -315,7 +316,7 @@ impl Pipeline {
|
|||||||
{
|
{
|
||||||
Async::NotReady => need_run = true,
|
Async::NotReady => need_run = true,
|
||||||
Async::Ready(_) => {
|
Async::Ready(_) => {
|
||||||
let _ = self.poll_timeout().map_err(|e| {
|
self.poll_timeout().map_err(|e| {
|
||||||
io::Error::new(io::ErrorKind::Other, format!("{}", e))
|
io::Error::new(io::ErrorKind::Other, format!("{}", e))
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
@ -371,16 +372,15 @@ impl Pipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll_timeout(&mut self) -> Poll<(), SendRequestError> {
|
fn poll_timeout(&mut self) -> Result<(), SendRequestError> {
|
||||||
if self.timeout.is_some() {
|
if self.timeout.is_some() {
|
||||||
match self.timeout.as_mut().unwrap().poll() {
|
match self.timeout.as_mut().unwrap().poll() {
|
||||||
Ok(Async::Ready(())) => Err(SendRequestError::Timeout),
|
Ok(Async::Ready(())) => return Err(SendRequestError::Timeout),
|
||||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
Ok(Async::NotReady) => (),
|
||||||
Err(_) => unreachable!(),
|
Err(_) => unreachable!(),
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Ok(Async::NotReady)
|
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
95
src/error.rs
95
src/error.rs
@ -88,7 +88,7 @@ impl fmt::Debug for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `HttpResponse` for `Error`
|
/// Convert `Error` to a `HttpResponse` instance
|
||||||
impl From<Error> for HttpResponse {
|
impl From<Error> for HttpResponse {
|
||||||
fn from(err: Error) -> Self {
|
fn from(err: Error) -> Self {
|
||||||
HttpResponse::from_error(err)
|
HttpResponse::from_error(err)
|
||||||
@ -317,10 +317,7 @@ pub enum HttpRangeError {
|
|||||||
/// Return `BadRequest` for `HttpRangeError`
|
/// Return `BadRequest` for `HttpRangeError`
|
||||||
impl ResponseError for HttpRangeError {
|
impl ResponseError for HttpRangeError {
|
||||||
fn error_response(&self) -> HttpResponse {
|
fn error_response(&self) -> HttpResponse {
|
||||||
HttpResponse::with_body(
|
HttpResponse::with_body(StatusCode::BAD_REQUEST, "Invalid Range header provided")
|
||||||
StatusCode::BAD_REQUEST,
|
|
||||||
"Invalid Range header provided",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -757,6 +754,46 @@ where
|
|||||||
InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR).into()
|
InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and
|
||||||
|
/// generate *NOT IMPLEMENTED* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorNotImplemented<T>(err: T) -> Error
|
||||||
|
where
|
||||||
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
|
{
|
||||||
|
InternalError::new(err, StatusCode::NOT_IMPLEMENTED).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and
|
||||||
|
/// generate *BAD GATEWAY* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorBadGateway<T>(err: T) -> Error
|
||||||
|
where
|
||||||
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
|
{
|
||||||
|
InternalError::new(err, StatusCode::BAD_GATEWAY).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and
|
||||||
|
/// generate *SERVICE UNAVAILABLE* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorServiceUnavailable<T>(err: T) -> Error
|
||||||
|
where
|
||||||
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
|
{
|
||||||
|
InternalError::new(err, StatusCode::SERVICE_UNAVAILABLE).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and
|
||||||
|
/// generate *GATEWAY TIMEOUT* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorGatewayTimeout<T>(err: T) -> Error
|
||||||
|
where
|
||||||
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
|
{
|
||||||
|
InternalError::new(err, StatusCode::GATEWAY_TIMEOUT).into()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -912,4 +949,52 @@ mod tests {
|
|||||||
let resp: HttpResponse = err.error_response();
|
let resp: HttpResponse = err.error_response();
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_error_helpers() {
|
||||||
|
let r: HttpResponse = ErrorBadRequest("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::BAD_REQUEST);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorUnauthorized("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::UNAUTHORIZED);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorForbidden("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::FORBIDDEN);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorNotFound("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorMethodNotAllowed("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::METHOD_NOT_ALLOWED);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorRequestTimeout("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::REQUEST_TIMEOUT);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorConflict("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::CONFLICT);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorGone("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::GONE);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorPreconditionFailed("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::PRECONDITION_FAILED);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorExpectationFailed("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::EXPECTATION_FAILED);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorInternalServerError("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorNotImplemented("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::NOT_IMPLEMENTED);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorBadGateway("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::BAD_GATEWAY);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorServiceUnavailable("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::SERVICE_UNAVAILABLE);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorGatewayTimeout("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::GATEWAY_TIMEOUT);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ use serde::de::{self, DeserializeOwned};
|
|||||||
use serde_urlencoded;
|
use serde_urlencoded;
|
||||||
|
|
||||||
use de::PathDeserializer;
|
use de::PathDeserializer;
|
||||||
use error::{Error, ErrorBadRequest};
|
use error::{Error, ErrorNotFound, ErrorBadRequest};
|
||||||
use handler::{AsyncResult, FromRequest};
|
use handler::{AsyncResult, FromRequest};
|
||||||
use httpmessage::{HttpMessage, MessageBody, UrlEncoded};
|
use httpmessage::{HttpMessage, MessageBody, UrlEncoded};
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
@ -108,7 +108,7 @@ where
|
|||||||
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
|
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
|
||||||
let req = req.clone();
|
let req = req.clone();
|
||||||
de::Deserialize::deserialize(PathDeserializer::new(&req))
|
de::Deserialize::deserialize(PathDeserializer::new(&req))
|
||||||
.map_err(|e| e.into())
|
.map_err(ErrorNotFound)
|
||||||
.map(|inner| Path { inner })
|
.map(|inner| Path { inner })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -492,14 +492,15 @@ where
|
|||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// /// extract path info using serde
|
/// /// extract path info using serde
|
||||||
/// fn index(state: State<MyApp>, info: Path<Info>) -> String {
|
/// fn index(data: (State<MyApp>, Path<Info>)) -> String {
|
||||||
/// format!("{} {}!", state.msg, info.username)
|
/// let (state, path) = data;
|
||||||
|
/// format!("{} {}!", state.msg, path.username)
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// fn main() {
|
/// fn main() {
|
||||||
/// let app = App::with_state(MyApp{msg: "Welcome"}).resource(
|
/// let app = App::with_state(MyApp{msg: "Welcome"}).resource(
|
||||||
/// "/{username}/index.html", // <- define path parameters
|
/// "/{username}/index.html", // <- define path parameters
|
||||||
/// |r| r.method(http::Method::GET).with2(index)); // <- use `with` extractor
|
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub struct State<S>(HttpRequest<S>);
|
pub struct State<S>(HttpRequest<S>);
|
||||||
|
@ -27,7 +27,6 @@ use uri::Url as InnerUrl;
|
|||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
pub(crate) struct MessageFlags: u8 {
|
pub(crate) struct MessageFlags: u8 {
|
||||||
const QUERY = 0b0000_0001;
|
|
||||||
const KEEPALIVE = 0b0000_0010;
|
const KEEPALIVE = 0b0000_0010;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -40,8 +39,6 @@ pub struct HttpInnerMessage {
|
|||||||
pub headers: HeaderMap,
|
pub headers: HeaderMap,
|
||||||
pub extensions: Extensions,
|
pub extensions: Extensions,
|
||||||
pub params: Params<'static>,
|
pub params: Params<'static>,
|
||||||
pub cookies: Option<Vec<Cookie<'static>>>,
|
|
||||||
pub query: Params<'static>,
|
|
||||||
pub addr: Option<SocketAddr>,
|
pub addr: Option<SocketAddr>,
|
||||||
pub payload: Option<Payload>,
|
pub payload: Option<Payload>,
|
||||||
pub info: Option<ConnectionInfo<'static>>,
|
pub info: Option<ConnectionInfo<'static>>,
|
||||||
@ -49,6 +46,9 @@ pub struct HttpInnerMessage {
|
|||||||
resource: RouterResource,
|
resource: RouterResource,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Query(Params<'static>);
|
||||||
|
struct Cookies(Vec<Cookie<'static>>);
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
enum RouterResource {
|
enum RouterResource {
|
||||||
Notset,
|
Notset,
|
||||||
@ -64,9 +64,7 @@ impl Default for HttpInnerMessage {
|
|||||||
headers: HeaderMap::with_capacity(16),
|
headers: HeaderMap::with_capacity(16),
|
||||||
flags: MessageFlags::empty(),
|
flags: MessageFlags::empty(),
|
||||||
params: Params::new(),
|
params: Params::new(),
|
||||||
query: Params::new(),
|
|
||||||
addr: None,
|
addr: None,
|
||||||
cookies: None,
|
|
||||||
payload: None,
|
payload: None,
|
||||||
extensions: Extensions::new(),
|
extensions: Extensions::new(),
|
||||||
info: None,
|
info: None,
|
||||||
@ -91,7 +89,6 @@ impl HttpInnerMessage {
|
|||||||
self.addr = None;
|
self.addr = None;
|
||||||
self.info = None;
|
self.info = None;
|
||||||
self.flags = MessageFlags::empty();
|
self.flags = MessageFlags::empty();
|
||||||
self.cookies = None;
|
|
||||||
self.payload = None;
|
self.payload = None;
|
||||||
self.prefix = 0;
|
self.prefix = 0;
|
||||||
self.resource = RouterResource::Notset;
|
self.resource = RouterResource::Notset;
|
||||||
@ -109,7 +106,10 @@ impl HttpRequest<()> {
|
|||||||
/// Construct a new Request.
|
/// Construct a new Request.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
method: Method, uri: Uri, version: Version, headers: HeaderMap,
|
method: Method,
|
||||||
|
uri: Uri,
|
||||||
|
version: Version,
|
||||||
|
headers: HeaderMap,
|
||||||
payload: Option<Payload>,
|
payload: Option<Payload>,
|
||||||
) -> HttpRequest {
|
) -> HttpRequest {
|
||||||
let url = InnerUrl::new(uri);
|
let url = InnerUrl::new(uri);
|
||||||
@ -121,9 +121,7 @@ impl HttpRequest<()> {
|
|||||||
headers,
|
headers,
|
||||||
payload,
|
payload,
|
||||||
params: Params::new(),
|
params: Params::new(),
|
||||||
query: Params::new(),
|
|
||||||
extensions: Extensions::new(),
|
extensions: Extensions::new(),
|
||||||
cookies: None,
|
|
||||||
addr: None,
|
addr: None,
|
||||||
info: None,
|
info: None,
|
||||||
prefix: 0,
|
prefix: 0,
|
||||||
@ -306,7 +304,9 @@ impl<S> HttpRequest<S> {
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn url_for<U, I>(
|
pub fn url_for<U, I>(
|
||||||
&self, name: &str, elements: U,
|
&self,
|
||||||
|
name: &str,
|
||||||
|
elements: U,
|
||||||
) -> Result<Url, UrlGenerationError>
|
) -> Result<Url, UrlGenerationError>
|
||||||
where
|
where
|
||||||
U: IntoIterator<Item = I>,
|
U: IntoIterator<Item = I>,
|
||||||
@ -369,20 +369,20 @@ impl<S> HttpRequest<S> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[deprecated(since = "0.6.0", note = "please use `Query<T>` extractor")]
|
|
||||||
/// Get a reference to the Params object.
|
/// Get a reference to the Params object.
|
||||||
/// Params is a container for url query parameters.
|
/// Params is a container for url query parameters.
|
||||||
pub fn query(&self) -> &Params {
|
pub fn query<'a>(&'a self) -> &'a Params {
|
||||||
if !self.as_ref().flags.contains(MessageFlags::QUERY) {
|
if let None = self.extensions().get::<Query>() {
|
||||||
self.as_mut().flags.insert(MessageFlags::QUERY);
|
let mut params: Params<'a> = Params::new();
|
||||||
let params: &mut Params =
|
|
||||||
unsafe { mem::transmute(&mut self.as_mut().query) };
|
|
||||||
params.clear();
|
|
||||||
for (key, val) in form_urlencoded::parse(self.query_string().as_ref()) {
|
for (key, val) in form_urlencoded::parse(self.query_string().as_ref()) {
|
||||||
params.add(key, val);
|
params.add(key, val);
|
||||||
}
|
}
|
||||||
|
let params: Params<'static> = unsafe { mem::transmute(params) };
|
||||||
|
self.as_mut().extensions.insert(Query(params));
|
||||||
}
|
}
|
||||||
unsafe { mem::transmute(&self.as_ref().query) }
|
let params: &Params<'a> =
|
||||||
|
unsafe { mem::transmute(&self.extensions().get::<Query>().unwrap().0) };
|
||||||
|
params
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The query string in the URL.
|
/// The query string in the URL.
|
||||||
@ -399,7 +399,7 @@ impl<S> HttpRequest<S> {
|
|||||||
|
|
||||||
/// Load request cookies.
|
/// Load request cookies.
|
||||||
pub fn cookies(&self) -> Result<&Vec<Cookie<'static>>, CookieParseError> {
|
pub fn cookies(&self) -> Result<&Vec<Cookie<'static>>, CookieParseError> {
|
||||||
if self.as_ref().cookies.is_none() {
|
if let None = self.extensions().get::<Query>() {
|
||||||
let msg = self.as_mut();
|
let msg = self.as_mut();
|
||||||
let mut cookies = Vec::new();
|
let mut cookies = Vec::new();
|
||||||
for hdr in msg.headers.get_all(header::COOKIE) {
|
for hdr in msg.headers.get_all(header::COOKIE) {
|
||||||
@ -410,9 +410,9 @@ impl<S> HttpRequest<S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
msg.cookies = Some(cookies);
|
msg.extensions.insert(Cookies(cookies));
|
||||||
}
|
}
|
||||||
Ok(&self.as_ref().cookies.as_ref().unwrap())
|
Ok(&self.extensions().get::<Cookies>().unwrap().0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return request cookie.
|
/// Return request cookie.
|
||||||
@ -427,6 +427,12 @@ impl<S> HttpRequest<S> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_cookies(&mut self, cookies: Option<Vec<Cookie<'static>>>) {
|
||||||
|
if let Some(cookies) = cookies {
|
||||||
|
self.extensions_mut().insert(Cookies(cookies));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get a reference to the Params object.
|
/// Get a reference to the Params object.
|
||||||
///
|
///
|
||||||
/// Params is a container for url parameters.
|
/// Params is a container for url parameters.
|
||||||
@ -664,10 +670,8 @@ mod tests {
|
|||||||
|
|
||||||
let mut resource = ResourceHandler::<()>::default();
|
let mut resource = ResourceHandler::<()>::default();
|
||||||
resource.name("index");
|
resource.name("index");
|
||||||
let routes = vec![(
|
let routes =
|
||||||
Resource::new("index", "/user/{name}.{ext}"),
|
vec![(Resource::new("index", "/user/{name}.{ext}"), Some(resource))];
|
||||||
Some(resource),
|
|
||||||
)];
|
|
||||||
let (router, _) = Router::new("/", ServerSettings::default(), routes);
|
let (router, _) = Router::new("/", ServerSettings::default(), routes);
|
||||||
assert!(router.has_route("/user/test.html"));
|
assert!(router.has_route("/user/test.html"));
|
||||||
assert!(!router.has_route("/test/unknown"));
|
assert!(!router.has_route("/test/unknown"));
|
||||||
@ -696,10 +700,8 @@ mod tests {
|
|||||||
|
|
||||||
let mut resource = ResourceHandler::<()>::default();
|
let mut resource = ResourceHandler::<()>::default();
|
||||||
resource.name("index");
|
resource.name("index");
|
||||||
let routes = vec![(
|
let routes =
|
||||||
Resource::new("index", "/user/{name}.{ext}"),
|
vec![(Resource::new("index", "/user/{name}.{ext}"), Some(resource))];
|
||||||
Some(resource),
|
|
||||||
)];
|
|
||||||
let (router, _) = Router::new("/prefix/", ServerSettings::default(), routes);
|
let (router, _) = Router::new("/prefix/", ServerSettings::default(), routes);
|
||||||
assert!(router.has_route("/user/test.html"));
|
assert!(router.has_route("/user/test.html"));
|
||||||
assert!(!router.has_route("/prefix/user/test.html"));
|
assert!(!router.has_route("/prefix/user/test.html"));
|
||||||
|
@ -466,10 +466,7 @@ impl HttpResponseBuilder {
|
|||||||
jar.add(cookie.into_owned());
|
jar.add(cookie.into_owned());
|
||||||
self.cookies = Some(jar)
|
self.cookies = Some(jar)
|
||||||
} else {
|
} else {
|
||||||
self.cookies
|
self.cookies.as_mut().unwrap().add(cookie.into_owned());
|
||||||
.as_mut()
|
|
||||||
.unwrap()
|
|
||||||
.add(cookie.into_owned());
|
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -534,9 +531,7 @@ impl HttpResponseBuilder {
|
|||||||
if let Some(e) = self.err.take() {
|
if let Some(e) = self.err.take() {
|
||||||
return Error::from(e).into();
|
return Error::from(e).into();
|
||||||
}
|
}
|
||||||
let mut response = self.response
|
let mut response = self.response.take().expect("cannot reuse response builder");
|
||||||
.take()
|
|
||||||
.expect("cannot reuse response builder");
|
|
||||||
if let Some(ref jar) = self.cookies {
|
if let Some(ref jar) = self.cookies {
|
||||||
for cookie in jar.delta() {
|
for cookie in jar.delta() {
|
||||||
match HeaderValue::from_str(&cookie.to_string()) {
|
match HeaderValue::from_str(&cookie.to_string()) {
|
||||||
@ -558,9 +553,7 @@ impl HttpResponseBuilder {
|
|||||||
S: Stream<Item = Bytes, Error = E> + 'static,
|
S: Stream<Item = Bytes, Error = E> + 'static,
|
||||||
E: Into<Error>,
|
E: Into<Error>,
|
||||||
{
|
{
|
||||||
self.body(Body::Streaming(Box::new(
|
self.body(Body::Streaming(Box::new(stream.map_err(|e| e.into()))))
|
||||||
stream.map_err(|e| e.into()),
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a json body and generate `HttpResponse`
|
/// Set a json body and generate `HttpResponse`
|
||||||
@ -607,7 +600,8 @@ impl HttpResponseBuilder {
|
|||||||
#[inline]
|
#[inline]
|
||||||
#[cfg_attr(feature = "cargo-clippy", allow(borrowed_box))]
|
#[cfg_attr(feature = "cargo-clippy", allow(borrowed_box))]
|
||||||
fn parts<'a>(
|
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>> {
|
) -> Option<&'a mut Box<InnerHttpResponse>> {
|
||||||
if err.is_some() {
|
if err.is_some() {
|
||||||
return None;
|
return None;
|
||||||
@ -822,14 +816,15 @@ thread_local!(static POOL: Rc<UnsafeCell<HttpResponsePool>> = HttpResponsePool::
|
|||||||
|
|
||||||
impl HttpResponsePool {
|
impl HttpResponsePool {
|
||||||
pub fn pool() -> Rc<UnsafeCell<HttpResponsePool>> {
|
pub fn pool() -> Rc<UnsafeCell<HttpResponsePool>> {
|
||||||
Rc::new(UnsafeCell::new(HttpResponsePool(
|
Rc::new(UnsafeCell::new(HttpResponsePool(VecDeque::with_capacity(
|
||||||
VecDeque::with_capacity(128),
|
128,
|
||||||
)))
|
))))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_builder(
|
pub fn get_builder(
|
||||||
pool: &Rc<UnsafeCell<HttpResponsePool>>, status: StatusCode,
|
pool: &Rc<UnsafeCell<HttpResponsePool>>,
|
||||||
|
status: StatusCode,
|
||||||
) -> HttpResponseBuilder {
|
) -> HttpResponseBuilder {
|
||||||
let p = unsafe { &mut *pool.as_ref().get() };
|
let p = unsafe { &mut *pool.as_ref().get() };
|
||||||
if let Some(mut msg) = p.0.pop_front() {
|
if let Some(mut msg) = p.0.pop_front() {
|
||||||
@ -853,7 +848,9 @@ impl HttpResponsePool {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_response(
|
pub fn get_response(
|
||||||
pool: &Rc<UnsafeCell<HttpResponsePool>>, status: StatusCode, body: Body,
|
pool: &Rc<UnsafeCell<HttpResponsePool>>,
|
||||||
|
status: StatusCode,
|
||||||
|
body: Body,
|
||||||
) -> HttpResponse {
|
) -> HttpResponse {
|
||||||
let p = unsafe { &mut *pool.as_ref().get() };
|
let p = unsafe { &mut *pool.as_ref().get() };
|
||||||
if let Some(mut msg) = p.0.pop_front() {
|
if let Some(mut msg) = p.0.pop_front() {
|
||||||
@ -879,7 +876,8 @@ impl HttpResponsePool {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[cfg_attr(feature = "cargo-clippy", allow(boxed_local, inline_always))]
|
#[cfg_attr(feature = "cargo-clippy", allow(boxed_local, inline_always))]
|
||||||
fn release(
|
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() };
|
let pool = unsafe { &mut *pool.as_ref().get() };
|
||||||
if pool.0.len() < 128 {
|
if pool.0.len() < 128 {
|
||||||
@ -975,9 +973,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_force_close() {
|
fn test_force_close() {
|
||||||
let resp = HttpResponse::build(StatusCode::OK)
|
let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
|
||||||
.force_close()
|
|
||||||
.finish();
|
|
||||||
assert!(!resp.keep_alive().unwrap())
|
assert!(!resp.keep_alive().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -986,10 +982,7 @@ mod tests {
|
|||||||
let resp = HttpResponse::build(StatusCode::OK)
|
let resp = HttpResponse::build(StatusCode::OK)
|
||||||
.content_type("text/plain")
|
.content_type("text/plain")
|
||||||
.body(Body::Empty);
|
.body(Body::Empty);
|
||||||
assert_eq!(
|
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain")
|
||||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
|
||||||
"text/plain"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -1036,10 +1029,10 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Body {
|
impl Body {
|
||||||
pub(crate) fn binary(&self) -> Option<&Binary> {
|
pub(crate) fn bin_ref(&self) -> &Binary {
|
||||||
match *self {
|
match *self {
|
||||||
Body::Binary(ref bin) => Some(bin),
|
Body::Binary(ref bin) => bin,
|
||||||
_ => None,
|
_ => panic!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1055,7 +1048,7 @@ mod tests {
|
|||||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||||
);
|
);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(resp.body().binary().unwrap(), &Binary::from("test"));
|
assert_eq!(resp.body().bin_ref(), &Binary::from("test"));
|
||||||
|
|
||||||
let resp: HttpResponse = "test".respond_to(&req).ok().unwrap();
|
let resp: HttpResponse = "test".respond_to(&req).ok().unwrap();
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
@ -1064,7 +1057,7 @@ mod tests {
|
|||||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||||
);
|
);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(resp.body().binary().unwrap(), &Binary::from("test"));
|
assert_eq!(resp.body().bin_ref(), &Binary::from("test"));
|
||||||
|
|
||||||
let resp: HttpResponse = b"test".as_ref().into();
|
let resp: HttpResponse = b"test".as_ref().into();
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
@ -1073,10 +1066,7 @@ mod tests {
|
|||||||
HeaderValue::from_static("application/octet-stream")
|
HeaderValue::from_static("application/octet-stream")
|
||||||
);
|
);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(
|
assert_eq!(resp.body().bin_ref(), &Binary::from(b"test".as_ref()));
|
||||||
resp.body().binary().unwrap(),
|
|
||||||
&Binary::from(b"test".as_ref())
|
|
||||||
);
|
|
||||||
|
|
||||||
let resp: HttpResponse = b"test".as_ref().respond_to(&req).ok().unwrap();
|
let resp: HttpResponse = b"test".as_ref().respond_to(&req).ok().unwrap();
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
@ -1085,10 +1075,7 @@ mod tests {
|
|||||||
HeaderValue::from_static("application/octet-stream")
|
HeaderValue::from_static("application/octet-stream")
|
||||||
);
|
);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(
|
assert_eq!(resp.body().bin_ref(), &Binary::from(b"test".as_ref()));
|
||||||
resp.body().binary().unwrap(),
|
|
||||||
&Binary::from(b"test".as_ref())
|
|
||||||
);
|
|
||||||
|
|
||||||
let resp: HttpResponse = "test".to_owned().into();
|
let resp: HttpResponse = "test".to_owned().into();
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
@ -1097,10 +1084,7 @@ mod tests {
|
|||||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||||
);
|
);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(
|
assert_eq!(resp.body().bin_ref(), &Binary::from("test".to_owned()));
|
||||||
resp.body().binary().unwrap(),
|
|
||||||
&Binary::from("test".to_owned())
|
|
||||||
);
|
|
||||||
|
|
||||||
let resp: HttpResponse = "test".to_owned().respond_to(&req).ok().unwrap();
|
let resp: HttpResponse = "test".to_owned().respond_to(&req).ok().unwrap();
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
@ -1109,10 +1093,7 @@ mod tests {
|
|||||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||||
);
|
);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(
|
assert_eq!(resp.body().bin_ref(), &Binary::from("test".to_owned()));
|
||||||
resp.body().binary().unwrap(),
|
|
||||||
&Binary::from("test".to_owned())
|
|
||||||
);
|
|
||||||
|
|
||||||
let resp: HttpResponse = (&"test".to_owned()).into();
|
let resp: HttpResponse = (&"test".to_owned()).into();
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
@ -1121,10 +1102,7 @@ mod tests {
|
|||||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||||
);
|
);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(
|
assert_eq!(resp.body().bin_ref(), &Binary::from(&"test".to_owned()));
|
||||||
resp.body().binary().unwrap(),
|
|
||||||
&Binary::from(&"test".to_owned())
|
|
||||||
);
|
|
||||||
|
|
||||||
let resp: HttpResponse = (&"test".to_owned()).respond_to(&req).ok().unwrap();
|
let resp: HttpResponse = (&"test".to_owned()).respond_to(&req).ok().unwrap();
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
@ -1133,10 +1111,7 @@ mod tests {
|
|||||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||||
);
|
);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(
|
assert_eq!(resp.body().bin_ref(), &Binary::from(&"test".to_owned()));
|
||||||
resp.body().binary().unwrap(),
|
|
||||||
&Binary::from(&"test".to_owned())
|
|
||||||
);
|
|
||||||
|
|
||||||
let b = Bytes::from_static(b"test");
|
let b = Bytes::from_static(b"test");
|
||||||
let resp: HttpResponse = b.into();
|
let resp: HttpResponse = b.into();
|
||||||
@ -1147,7 +1122,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.body().binary().unwrap(),
|
resp.body().bin_ref(),
|
||||||
&Binary::from(Bytes::from_static(b"test"))
|
&Binary::from(Bytes::from_static(b"test"))
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1160,7 +1135,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.body().binary().unwrap(),
|
resp.body().bin_ref(),
|
||||||
&Binary::from(Bytes::from_static(b"test"))
|
&Binary::from(Bytes::from_static(b"test"))
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1172,10 +1147,7 @@ mod tests {
|
|||||||
HeaderValue::from_static("application/octet-stream")
|
HeaderValue::from_static("application/octet-stream")
|
||||||
);
|
);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(
|
assert_eq!(resp.body().bin_ref(), &Binary::from(BytesMut::from("test")));
|
||||||
resp.body().binary().unwrap(),
|
|
||||||
&Binary::from(BytesMut::from("test"))
|
|
||||||
);
|
|
||||||
|
|
||||||
let b = BytesMut::from("test");
|
let b = BytesMut::from("test");
|
||||||
let resp: HttpResponse = b.respond_to(&req).ok().unwrap();
|
let resp: HttpResponse = b.respond_to(&req).ok().unwrap();
|
||||||
@ -1185,10 +1157,7 @@ mod tests {
|
|||||||
HeaderValue::from_static("application/octet-stream")
|
HeaderValue::from_static("application/octet-stream")
|
||||||
);
|
);
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(
|
assert_eq!(resp.body().bin_ref(), &Binary::from(BytesMut::from("test")));
|
||||||
resp.body().binary().unwrap(),
|
|
||||||
&Binary::from(BytesMut::from("test"))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
24
src/lib.rs
24
src/lib.rs
@ -60,7 +60,21 @@
|
|||||||
//! * Middlewares (`Logger`, `Session`, `CORS`, `CSRF`, `DefaultHeaders`)
|
//! * Middlewares (`Logger`, `Session`, `CORS`, `CSRF`, `DefaultHeaders`)
|
||||||
//! * Built on top of [Actix actor framework](https://github.com/actix/actix)
|
//! * Built on top of [Actix actor framework](https://github.com/actix/actix)
|
||||||
//! * Supported Rust version: 1.24 or later
|
//! * Supported Rust version: 1.24 or later
|
||||||
|
//!
|
||||||
|
//! ## Package feature
|
||||||
|
//!
|
||||||
|
//! * `tls` - enables ssl support via `native-tls` crate
|
||||||
|
//! * `alpn` - enables ssl support via `openssl` crate, require for `http/2`
|
||||||
|
//! support
|
||||||
|
//! * `session` - enables session support, includes `ring` crate as
|
||||||
|
//! dependency
|
||||||
|
//! * `brotli` - enables `brotli` compression support, requires `c`
|
||||||
|
//! compiler
|
||||||
|
//! * `flate-c` - enables `gzip`, `deflate` compression support, requires
|
||||||
|
//! `c` compiler
|
||||||
|
//! * `flate-rust` - experimental rust based implementation for
|
||||||
|
//! `gzip`, `deflate` compression.
|
||||||
|
//!
|
||||||
#![cfg_attr(actix_nightly, feature(
|
#![cfg_attr(actix_nightly, feature(
|
||||||
specialization, // for impl ErrorResponse for std::error::Error
|
specialization, // for impl ErrorResponse for std::error::Error
|
||||||
))]
|
))]
|
||||||
@ -169,12 +183,17 @@ pub use body::{Binary, Body};
|
|||||||
pub use context::HttpContext;
|
pub use context::HttpContext;
|
||||||
pub use error::{Error, ResponseError, Result};
|
pub use error::{Error, ResponseError, Result};
|
||||||
pub use extractor::{Form, Path, Query};
|
pub use extractor::{Form, Path, Query};
|
||||||
pub use handler::{AsyncResponder, Either, FromRequest, FutureResponse, Responder, State};
|
pub use handler::{
|
||||||
|
AsyncResponder, Either, FromRequest, FutureResponse, Responder, State,
|
||||||
|
};
|
||||||
pub use httpmessage::HttpMessage;
|
pub use httpmessage::HttpMessage;
|
||||||
pub use httprequest::HttpRequest;
|
pub use httprequest::HttpRequest;
|
||||||
pub use httpresponse::HttpResponse;
|
pub use httpresponse::HttpResponse;
|
||||||
pub use json::Json;
|
pub use json::Json;
|
||||||
pub use scope::Scope;
|
pub use scope::Scope;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[deprecated(since = "0.6.2", note = "please use `use actix_web::ws::WsWriter`")]
|
||||||
pub use ws::WsWriter;
|
pub use ws::WsWriter;
|
||||||
|
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
@ -210,6 +229,7 @@ pub mod dev {
|
|||||||
pub use resource::ResourceHandler;
|
pub use resource::ResourceHandler;
|
||||||
pub use route::Route;
|
pub use route::Route;
|
||||||
pub use router::{Resource, ResourceType, Router};
|
pub use router::{Resource, ResourceType, Router};
|
||||||
|
pub use with::ExtractorConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod http {
|
pub mod http {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
//! Request logging middleware
|
//! Request logging middleware
|
||||||
|
use std::collections::HashSet;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fmt;
|
use std::fmt::{self, Display, Formatter};
|
||||||
use std::fmt::{Display, Formatter};
|
|
||||||
|
|
||||||
use libc;
|
use libc;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
@ -74,6 +74,7 @@ use middleware::{Finished, Middleware, Started};
|
|||||||
///
|
///
|
||||||
pub struct Logger {
|
pub struct Logger {
|
||||||
format: Format,
|
format: Format,
|
||||||
|
exclude: HashSet<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Logger {
|
impl Logger {
|
||||||
@ -81,8 +82,15 @@ impl Logger {
|
|||||||
pub fn new(format: &str) -> Logger {
|
pub fn new(format: &str) -> Logger {
|
||||||
Logger {
|
Logger {
|
||||||
format: Format::new(format),
|
format: Format::new(format),
|
||||||
|
exclude: HashSet::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ignore and do not log access info for specified path.
|
||||||
|
pub fn exclude<T: Into<String>>(mut self, path: T) -> Self {
|
||||||
|
self.exclude.insert(path.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Logger {
|
impl Default for Logger {
|
||||||
@ -94,6 +102,7 @@ impl Default for Logger {
|
|||||||
fn default() -> Logger {
|
fn default() -> Logger {
|
||||||
Logger {
|
Logger {
|
||||||
format: Format::default(),
|
format: Format::default(),
|
||||||
|
exclude: HashSet::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -102,21 +111,23 @@ struct StartTime(time::Tm);
|
|||||||
|
|
||||||
impl Logger {
|
impl Logger {
|
||||||
fn log<S>(&self, req: &mut HttpRequest<S>, resp: &HttpResponse) {
|
fn log<S>(&self, req: &mut HttpRequest<S>, resp: &HttpResponse) {
|
||||||
let entry_time = req.extensions().get::<StartTime>().unwrap().0;
|
if let Some(entry_time) = req.extensions().get::<StartTime>() {
|
||||||
|
|
||||||
let render = |fmt: &mut Formatter| {
|
let render = |fmt: &mut Formatter| {
|
||||||
for unit in &self.format.0 {
|
for unit in &self.format.0 {
|
||||||
unit.render(fmt, req, resp, entry_time)?;
|
unit.render(fmt, req, resp, entry_time.0)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
info!("{}", FormatDisplay(&render));
|
info!("{}", FormatDisplay(&render));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<S> Middleware<S> for Logger {
|
impl<S> Middleware<S> for Logger {
|
||||||
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
|
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
|
||||||
|
if !self.exclude.contains(req.path()) {
|
||||||
req.extensions_mut().insert(StartTime(time::now()));
|
req.extensions_mut().insert(StartTime(time::now()));
|
||||||
|
}
|
||||||
Ok(Started::Done)
|
Ok(Started::Done)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,6 +80,7 @@ use serde_json::error::Error as JsonError;
|
|||||||
use time::Duration;
|
use time::Duration;
|
||||||
|
|
||||||
use error::{Error, ResponseError, Result};
|
use error::{Error, ResponseError, Result};
|
||||||
|
use handler::FromRequest;
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
use middleware::{Middleware, Response, Started};
|
use middleware::{Middleware, Response, Started};
|
||||||
@ -190,6 +191,34 @@ impl Session {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extractor implementation for Session type.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use actix_web::*;
|
||||||
|
/// use actix_web::middleware::session::Session;
|
||||||
|
///
|
||||||
|
/// fn index(session: Session) -> Result<&'static str> {
|
||||||
|
/// // access session data
|
||||||
|
/// if let Some(count) = session.get::<i32>("counter")? {
|
||||||
|
/// session.set("counter", count+1)?;
|
||||||
|
/// } else {
|
||||||
|
/// session.set("counter", 1)?;
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// Ok("Welcome!")
|
||||||
|
/// }
|
||||||
|
/// # fn main() {}
|
||||||
|
/// ```
|
||||||
|
impl<S> FromRequest<S> for Session {
|
||||||
|
type Config = ();
|
||||||
|
type Result = Session;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
|
||||||
|
req.session()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct SessionImplCell(RefCell<Box<SessionImpl>>);
|
struct SessionImplCell(RefCell<Box<SessionImpl>>);
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
@ -226,13 +255,10 @@ impl<S: 'static, T: SessionBackend<S>> Middleware<S> for SessionStorage<T, S> {
|
|||||||
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
|
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
|
||||||
let mut req = req.clone();
|
let mut req = req.clone();
|
||||||
|
|
||||||
let fut = self.0
|
let fut = self.0.from_request(&mut req).then(move |res| match res {
|
||||||
.from_request(&mut req)
|
|
||||||
.then(move |res| match res {
|
|
||||||
Ok(sess) => {
|
Ok(sess) => {
|
||||||
req.extensions_mut().insert(Arc::new(SessionImplCell(
|
req.extensions_mut()
|
||||||
RefCell::new(Box::new(sess)),
|
.insert(Arc::new(SessionImplCell(RefCell::new(Box::new(sess)))));
|
||||||
)));
|
|
||||||
FutOk(None)
|
FutOk(None)
|
||||||
}
|
}
|
||||||
Err(err) => FutErr(err),
|
Err(err) => FutErr(err),
|
||||||
@ -241,7 +267,9 @@ impl<S: 'static, T: SessionBackend<S>> Middleware<S> for SessionStorage<T, S> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn response(
|
fn response(
|
||||||
&self, req: &mut HttpRequest<S>, resp: HttpResponse,
|
&self,
|
||||||
|
req: &mut HttpRequest<S>,
|
||||||
|
resp: HttpResponse,
|
||||||
) -> Result<Response> {
|
) -> Result<Response> {
|
||||||
if let Some(s_box) = req.extensions_mut().remove::<Arc<SessionImplCell>>() {
|
if let Some(s_box) = req.extensions_mut().remove::<Arc<SessionImplCell>>() {
|
||||||
s_box.0.borrow_mut().write(resp)
|
s_box.0.borrow_mut().write(resp)
|
||||||
@ -357,7 +385,9 @@ impl CookieSessionInner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn set_cookie(
|
fn set_cookie(
|
||||||
&self, resp: &mut HttpResponse, state: &HashMap<String, String>,
|
&self,
|
||||||
|
resp: &mut HttpResponse,
|
||||||
|
state: &HashMap<String, String>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let value =
|
let value =
|
||||||
serde_json::to_string(&state).map_err(CookieSessionError::Serialize)?;
|
serde_json::to_string(&state).map_err(CookieSessionError::Serialize)?;
|
||||||
@ -551,4 +581,24 @@ mod tests {
|
|||||||
let response = srv.execute(request.send()).unwrap();
|
let response = srv.execute(request.send()).unwrap();
|
||||||
assert!(response.cookie("actix-session").is_some());
|
assert!(response.cookie("actix-session").is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cookie_session_extractor() {
|
||||||
|
let mut srv = test::TestServer::with_factory(|| {
|
||||||
|
App::new()
|
||||||
|
.middleware(SessionStorage::new(
|
||||||
|
CookieSessionBackend::signed(&[0; 32]).secure(false),
|
||||||
|
))
|
||||||
|
.resource("/", |r| {
|
||||||
|
r.with(|ses: Session| {
|
||||||
|
let _ = ses.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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,9 @@ pub(crate) trait PipelineHandler<S> {
|
|||||||
fn encoding(&self) -> ContentEncoding;
|
fn encoding(&self) -> ContentEncoding;
|
||||||
|
|
||||||
fn handle(
|
fn handle(
|
||||||
&mut self, req: HttpRequest<S>, htype: HandlerType,
|
&mut self,
|
||||||
|
req: HttpRequest<S>,
|
||||||
|
htype: HandlerType,
|
||||||
) -> AsyncResult<HttpResponse>;
|
) -> AsyncResult<HttpResponse>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,8 +122,10 @@ impl<S> PipelineInfo<S> {
|
|||||||
|
|
||||||
impl<S: 'static, H: PipelineHandler<S>> Pipeline<S, H> {
|
impl<S: 'static, H: PipelineHandler<S>> Pipeline<S, H> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>,
|
req: HttpRequest<S>,
|
||||||
handler: Rc<UnsafeCell<H>>, htype: HandlerType,
|
mws: Rc<Vec<Box<Middleware<S>>>>,
|
||||||
|
handler: Rc<UnsafeCell<H>>,
|
||||||
|
htype: HandlerType,
|
||||||
) -> Pipeline<S, H> {
|
) -> Pipeline<S, H> {
|
||||||
let mut info = PipelineInfo {
|
let mut info = PipelineInfo {
|
||||||
mws,
|
mws,
|
||||||
@ -148,6 +152,7 @@ impl Pipeline<(), Inner<()>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<S: 'static, H> Pipeline<S, H> {
|
impl<S: 'static, H> Pipeline<S, H> {
|
||||||
|
#[inline]
|
||||||
fn is_done(&self) -> bool {
|
fn is_done(&self) -> bool {
|
||||||
match self.1 {
|
match self.1 {
|
||||||
PipelineState::None
|
PipelineState::None
|
||||||
@ -192,7 +197,9 @@ impl<S: 'static, H: PipelineHandler<S>> HttpHandlerTask for Pipeline<S, H> {
|
|||||||
match self.1 {
|
match self.1 {
|
||||||
PipelineState::None => return Ok(Async::Ready(true)),
|
PipelineState::None => return Ok(Async::Ready(true)),
|
||||||
PipelineState::Error => {
|
PipelineState::Error => {
|
||||||
return Err(io::Error::new(io::ErrorKind::Other, "Internal error").into())
|
return Err(
|
||||||
|
io::Error::new(io::ErrorKind::Other, "Internal error").into()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
@ -236,7 +243,9 @@ struct StartMiddlewares<S, H> {
|
|||||||
|
|
||||||
impl<S: 'static, H: PipelineHandler<S>> StartMiddlewares<S, H> {
|
impl<S: 'static, H: PipelineHandler<S>> StartMiddlewares<S, H> {
|
||||||
fn init(
|
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> {
|
) -> PipelineState<S, H> {
|
||||||
// execute middlewares, we need this stage because middlewares could be
|
// execute middlewares, we need this stage because middlewares could be
|
||||||
// non-async and we can move to next state immediately
|
// non-async and we can move to next state immediately
|
||||||
@ -251,8 +260,7 @@ impl<S: 'static, H: PipelineHandler<S>> StartMiddlewares<S, H> {
|
|||||||
Ok(Started::Response(resp)) => {
|
Ok(Started::Response(resp)) => {
|
||||||
return RunMiddlewares::init(info, resp)
|
return RunMiddlewares::init(info, resp)
|
||||||
}
|
}
|
||||||
Ok(Started::Future(mut fut)) => match fut.poll() {
|
Ok(Started::Future(fut)) => {
|
||||||
Ok(Async::NotReady) => {
|
|
||||||
return PipelineState::Starting(StartMiddlewares {
|
return PipelineState::Starting(StartMiddlewares {
|
||||||
hnd,
|
hnd,
|
||||||
htype,
|
htype,
|
||||||
@ -260,14 +268,6 @@ impl<S: 'static, H: PipelineHandler<S>> StartMiddlewares<S, H> {
|
|||||||
_s: PhantomData,
|
_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()),
|
|
||||||
},
|
|
||||||
Err(err) => return ProcessResponse::init(err.into()),
|
Err(err) => return ProcessResponse::init(err.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -284,12 +284,12 @@ impl<S: 'static, H: PipelineHandler<S>> StartMiddlewares<S, H> {
|
|||||||
if let Some(resp) = resp {
|
if let Some(resp) = resp {
|
||||||
return Some(RunMiddlewares::init(info, resp));
|
return Some(RunMiddlewares::init(info, resp));
|
||||||
}
|
}
|
||||||
|
loop {
|
||||||
if info.count == len {
|
if info.count == len {
|
||||||
let reply = unsafe { &mut *self.hnd.get() }
|
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));
|
return Some(WaitingResponse::init(info, reply));
|
||||||
} else {
|
} else {
|
||||||
loop {
|
|
||||||
match info.mws[info.count as usize].start(info.req_mut()) {
|
match info.mws[info.count as usize].start(info.req_mut()) {
|
||||||
Ok(Started::Done) => info.count += 1,
|
Ok(Started::Done) => info.count += 1,
|
||||||
Ok(Started::Response(resp)) => {
|
Ok(Started::Response(resp)) => {
|
||||||
@ -322,7 +322,8 @@ struct WaitingResponse<S, H> {
|
|||||||
impl<S: 'static, H> WaitingResponse<S, H> {
|
impl<S: 'static, H> WaitingResponse<S, H> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn init(
|
fn init(
|
||||||
info: &mut PipelineInfo<S>, reply: AsyncResult<HttpResponse>,
|
info: &mut PipelineInfo<S>,
|
||||||
|
reply: AsyncResult<HttpResponse>,
|
||||||
) -> PipelineState<S, H> {
|
) -> PipelineState<S, H> {
|
||||||
match reply.into() {
|
match reply.into() {
|
||||||
AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()),
|
AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()),
|
||||||
@ -353,6 +354,7 @@ struct RunMiddlewares<S, H> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<S: 'static, H> RunMiddlewares<S, H> {
|
impl<S: 'static, H> RunMiddlewares<S, H> {
|
||||||
|
#[inline]
|
||||||
fn init(info: &mut PipelineInfo<S>, mut resp: HttpResponse) -> PipelineState<S, H> {
|
fn init(info: &mut PipelineInfo<S>, mut resp: HttpResponse) -> PipelineState<S, H> {
|
||||||
if info.count == 0 {
|
if info.count == 0 {
|
||||||
return ProcessResponse::init(resp);
|
return ProcessResponse::init(resp);
|
||||||
@ -473,7 +475,9 @@ impl<S: 'static, H> ProcessResponse<S, H> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn poll_io(
|
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>> {
|
) -> Result<PipelineState<S, H>, PipelineState<S, H>> {
|
||||||
loop {
|
loop {
|
||||||
if self.drain.is_none() && self.running != RunningState::Paused {
|
if self.drain.is_none() && self.running != RunningState::Paused {
|
||||||
@ -501,8 +505,8 @@ impl<S: 'static, H> ProcessResponse<S, H> {
|
|||||||
if let Some(err) = self.resp.error() {
|
if let Some(err) = self.resp.error() {
|
||||||
if self.resp.status().is_server_error() {
|
if self.resp.status().is_server_error() {
|
||||||
error!(
|
error!(
|
||||||
"Error occured during request handling: {}",
|
"Error occured during request handling, status: {} {}",
|
||||||
err
|
self.resp.status(), err
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
warn!(
|
warn!(
|
||||||
@ -685,6 +689,7 @@ struct FinishingMiddlewares<S, H> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<S: 'static, H> FinishingMiddlewares<S, H> {
|
impl<S: 'static, H> FinishingMiddlewares<S, H> {
|
||||||
|
#[inline]
|
||||||
fn init(info: &mut PipelineInfo<S>, resp: HttpResponse) -> PipelineState<S, H> {
|
fn init(info: &mut PipelineInfo<S>, resp: HttpResponse) -> PipelineState<S, H> {
|
||||||
if info.count == 0 {
|
if info.count == 0 {
|
||||||
Completed::init(info)
|
Completed::init(info)
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use futures::Future;
|
||||||
use http::{Method, StatusCode};
|
use http::{Method, StatusCode};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
use error::Error;
|
||||||
use handler::{AsyncResult, FromRequest, Handler, Responder};
|
use handler::{AsyncResult, FromRequest, Handler, Responder};
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
@ -183,6 +185,25 @@ impl<S: 'static> ResourceHandler<S> {
|
|||||||
self.routes.last_mut().unwrap().with(handler);
|
self.routes.last_mut().unwrap().with(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Register a new route and add async handler.
|
||||||
|
///
|
||||||
|
/// This is shortcut for:
|
||||||
|
///
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// Application::resource("/", |r| r.route().with_async(index)
|
||||||
|
/// ```
|
||||||
|
pub fn with_async<T, F, R, I, E>(&mut self, handler: F)
|
||||||
|
where
|
||||||
|
F: Fn(T) -> R + 'static,
|
||||||
|
R: Future<Item = I, Error = E> + 'static,
|
||||||
|
I: Responder + 'static,
|
||||||
|
E: Into<Error> + 'static,
|
||||||
|
T: FromRequest<S> + 'static,
|
||||||
|
{
|
||||||
|
self.routes.push(Route::default());
|
||||||
|
self.routes.last_mut().unwrap().with_async(handler);
|
||||||
|
}
|
||||||
|
|
||||||
/// Register a resource middleware
|
/// Register a resource middleware
|
||||||
///
|
///
|
||||||
/// This is similar to `App's` middlewares, but
|
/// This is similar to `App's` middlewares, but
|
||||||
|
124
src/route.rs
124
src/route.rs
@ -5,15 +5,19 @@ use std::rc::Rc;
|
|||||||
use futures::{Async, Future, Poll};
|
use futures::{Async, Future, Poll};
|
||||||
|
|
||||||
use error::Error;
|
use error::Error;
|
||||||
use handler::{AsyncHandler, AsyncResult, AsyncResultItem, FromRequest, Handler,
|
use handler::{
|
||||||
Responder, RouteHandler, WrapHandler};
|
AsyncHandler, AsyncResult, AsyncResultItem, FromRequest, Handler, Responder,
|
||||||
|
RouteHandler, WrapHandler,
|
||||||
|
};
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
use middleware::{Finished as MiddlewareFinished, Middleware,
|
use middleware::{
|
||||||
Response as MiddlewareResponse, Started as MiddlewareStarted};
|
Finished as MiddlewareFinished, Middleware, Response as MiddlewareResponse,
|
||||||
|
Started as MiddlewareStarted,
|
||||||
|
};
|
||||||
use pred::Predicate;
|
use pred::Predicate;
|
||||||
use with::{ExtractorConfig, With, With2, With3};
|
use with::{ExtractorConfig, With, With2, With3, WithAsync};
|
||||||
|
|
||||||
/// Resource route definition
|
/// Resource route definition
|
||||||
///
|
///
|
||||||
@ -51,7 +55,9 @@ impl<S: 'static> Route<S> {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn compose(
|
pub(crate) fn compose(
|
||||||
&mut self, req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>,
|
&mut self,
|
||||||
|
req: HttpRequest<S>,
|
||||||
|
mws: Rc<Vec<Box<Middleware<S>>>>,
|
||||||
) -> AsyncResult<HttpResponse> {
|
) -> AsyncResult<HttpResponse> {
|
||||||
AsyncResult::async(Box::new(Compose::new(req, mws, self.handler.clone())))
|
AsyncResult::async(Box::new(Compose::new(req, mws, self.handler.clone())))
|
||||||
}
|
}
|
||||||
@ -129,6 +135,34 @@ impl<S: 'static> Route<S> {
|
|||||||
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
|
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
///
|
||||||
|
/// It is possible to use tuples for specifing multiple extractors for one
|
||||||
|
/// handler function.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate bytes;
|
||||||
|
/// # extern crate actix_web;
|
||||||
|
/// # extern crate futures;
|
||||||
|
/// #[macro_use] extern crate serde_derive;
|
||||||
|
/// # use std::collections::HashMap;
|
||||||
|
/// use actix_web::{http, App, Query, Path, Result, Json};
|
||||||
|
///
|
||||||
|
/// #[derive(Deserialize)]
|
||||||
|
/// struct Info {
|
||||||
|
/// username: String,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// /// extract path info using serde
|
||||||
|
/// fn index(info: (Path<Info>, Query<HashMap<String, String>>, Json<Info>)) -> Result<String> {
|
||||||
|
/// Ok(format!("Welcome {}!", info.0.username))
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let app = App::new().resource(
|
||||||
|
/// "/{username}/index.html", // <- define path parameters
|
||||||
|
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
pub fn with<T, F, R>(&mut self, handler: F) -> ExtractorConfig<S, T>
|
pub fn with<T, F, R>(&mut self, handler: F) -> ExtractorConfig<S, T>
|
||||||
where
|
where
|
||||||
F: Fn(T) -> R + 'static,
|
F: Fn(T) -> R + 'static,
|
||||||
@ -140,6 +174,49 @@ impl<S: 'static> Route<S> {
|
|||||||
cfg
|
cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set async handler function, use request extractor for parameters.
|
||||||
|
/// Also this method needs to be used if your handler function returns
|
||||||
|
/// `impl Future<>`
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate bytes;
|
||||||
|
/// # extern crate actix_web;
|
||||||
|
/// # extern crate futures;
|
||||||
|
/// #[macro_use] extern crate serde_derive;
|
||||||
|
/// use actix_web::{App, Path, Error, http};
|
||||||
|
/// use futures::Future;
|
||||||
|
///
|
||||||
|
/// #[derive(Deserialize)]
|
||||||
|
/// struct Info {
|
||||||
|
/// username: String,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// /// extract path info using serde
|
||||||
|
/// fn index(info: Path<Info>) -> Box<Future<Item=&'static str, Error=Error>> {
|
||||||
|
/// unimplemented!()
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let app = App::new().resource(
|
||||||
|
/// "/{username}/index.html", // <- define path parameters
|
||||||
|
/// |r| r.method(http::Method::GET)
|
||||||
|
/// .with_async(index)); // <- use `with` extractor
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn with_async<T, F, R, I, E>(&mut self, handler: F) -> ExtractorConfig<S, T>
|
||||||
|
where
|
||||||
|
F: Fn(T) -> R + 'static,
|
||||||
|
R: Future<Item = I, Error = E> + 'static,
|
||||||
|
I: Responder + 'static,
|
||||||
|
E: Into<Error> + 'static,
|
||||||
|
T: FromRequest<S> + 'static,
|
||||||
|
{
|
||||||
|
let cfg = ExtractorConfig::default();
|
||||||
|
self.h(WithAsync::new(handler, Clone::clone(&cfg)));
|
||||||
|
cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
/// Set handler function, use request extractor for both parameters.
|
/// Set handler function, use request extractor for both parameters.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
@ -171,7 +248,8 @@ impl<S: 'static> Route<S> {
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn with2<T1, T2, F, R>(
|
pub fn with2<T1, T2, F, R>(
|
||||||
&mut self, handler: F,
|
&mut self,
|
||||||
|
handler: F,
|
||||||
) -> (ExtractorConfig<S, T1>, ExtractorConfig<S, T2>)
|
) -> (ExtractorConfig<S, T1>, ExtractorConfig<S, T2>)
|
||||||
where
|
where
|
||||||
F: Fn(T1, T2) -> R + 'static,
|
F: Fn(T1, T2) -> R + 'static,
|
||||||
@ -189,9 +267,11 @@ impl<S: 'static> Route<S> {
|
|||||||
(cfg1, cfg2)
|
(cfg1, cfg2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
/// Set handler function, use request extractor for all parameters.
|
/// Set handler function, use request extractor for all parameters.
|
||||||
pub fn with3<T1, T2, T3, F, R>(
|
pub fn with3<T1, T2, T3, F, R>(
|
||||||
&mut self, handler: F,
|
&mut self,
|
||||||
|
handler: F,
|
||||||
) -> (
|
) -> (
|
||||||
ExtractorConfig<S, T1>,
|
ExtractorConfig<S, T1>,
|
||||||
ExtractorConfig<S, T2>,
|
ExtractorConfig<S, T2>,
|
||||||
@ -224,9 +304,7 @@ struct InnerHandler<S>(Rc<UnsafeCell<Box<RouteHandler<S>>>>);
|
|||||||
impl<S: 'static> InnerHandler<S> {
|
impl<S: 'static> InnerHandler<S> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn new<H: Handler<S>>(h: H) -> Self {
|
fn new<H: Handler<S>>(h: H) -> Self {
|
||||||
InnerHandler(Rc::new(UnsafeCell::new(Box::new(WrapHandler::new(
|
InnerHandler(Rc::new(UnsafeCell::new(Box::new(WrapHandler::new(h)))))
|
||||||
h,
|
|
||||||
)))))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -237,9 +315,7 @@ impl<S: 'static> InnerHandler<S> {
|
|||||||
R: Responder + 'static,
|
R: Responder + 'static,
|
||||||
E: Into<Error> + 'static,
|
E: Into<Error> + 'static,
|
||||||
{
|
{
|
||||||
InnerHandler(Rc::new(UnsafeCell::new(Box::new(AsyncHandler::new(
|
InnerHandler(Rc::new(UnsafeCell::new(Box::new(AsyncHandler::new(h)))))
|
||||||
h,
|
|
||||||
)))))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -292,7 +368,9 @@ impl<S: 'static> ComposeState<S> {
|
|||||||
|
|
||||||
impl<S: 'static> Compose<S> {
|
impl<S: 'static> Compose<S> {
|
||||||
fn new(
|
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 {
|
) -> Self {
|
||||||
let mut info = ComposeInfo {
|
let mut info = ComposeInfo {
|
||||||
count: 0,
|
count: 0,
|
||||||
@ -346,21 +424,12 @@ impl<S: 'static> StartMiddlewares<S> {
|
|||||||
Ok(MiddlewareStarted::Response(resp)) => {
|
Ok(MiddlewareStarted::Response(resp)) => {
|
||||||
return RunMiddlewares::init(info, resp)
|
return RunMiddlewares::init(info, resp)
|
||||||
}
|
}
|
||||||
Ok(MiddlewareStarted::Future(mut fut)) => match fut.poll() {
|
Ok(MiddlewareStarted::Future(fut)) => {
|
||||||
Ok(Async::NotReady) => {
|
|
||||||
return ComposeState::Starting(StartMiddlewares {
|
return ComposeState::Starting(StartMiddlewares {
|
||||||
fut: Some(fut),
|
fut: Some(fut),
|
||||||
_s: PhantomData,
|
_s: PhantomData,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Ok(Async::Ready(resp)) => {
|
|
||||||
if let Some(resp) = resp {
|
|
||||||
return RunMiddlewares::init(info, resp);
|
|
||||||
}
|
|
||||||
info.count += 1;
|
|
||||||
}
|
|
||||||
Err(err) => return FinishingMiddlewares::init(info, err.into()),
|
|
||||||
},
|
|
||||||
Err(err) => return FinishingMiddlewares::init(info, err.into()),
|
Err(err) => return FinishingMiddlewares::init(info, err.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -377,11 +446,11 @@ impl<S: 'static> StartMiddlewares<S> {
|
|||||||
if let Some(resp) = resp {
|
if let Some(resp) = resp {
|
||||||
return Some(RunMiddlewares::init(info, resp));
|
return Some(RunMiddlewares::init(info, resp));
|
||||||
}
|
}
|
||||||
|
loop {
|
||||||
if info.count == len {
|
if info.count == len {
|
||||||
let reply = info.handler.handle(info.req.clone());
|
let reply = info.handler.handle(info.req.clone());
|
||||||
return Some(WaitingResponse::init(info, reply));
|
return Some(WaitingResponse::init(info, reply));
|
||||||
} else {
|
} else {
|
||||||
loop {
|
|
||||||
match info.mws[info.count].start(&mut info.req) {
|
match info.mws[info.count].start(&mut info.req) {
|
||||||
Ok(MiddlewareStarted::Done) => info.count += 1,
|
Ok(MiddlewareStarted::Done) => info.count += 1,
|
||||||
Ok(MiddlewareStarted::Response(resp)) => {
|
Ok(MiddlewareStarted::Response(resp)) => {
|
||||||
@ -416,7 +485,8 @@ struct WaitingResponse<S> {
|
|||||||
impl<S: 'static> WaitingResponse<S> {
|
impl<S: 'static> WaitingResponse<S> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn init(
|
fn init(
|
||||||
info: &mut ComposeInfo<S>, reply: AsyncResult<HttpResponse>,
|
info: &mut ComposeInfo<S>,
|
||||||
|
reply: AsyncResult<HttpResponse>,
|
||||||
) -> ComposeState<S> {
|
) -> ComposeState<S> {
|
||||||
match reply.into() {
|
match reply.into() {
|
||||||
AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()),
|
AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()),
|
||||||
|
112
src/router.rs
112
src/router.rs
@ -142,7 +142,8 @@ enum PatternElement {
|
|||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
enum PatternType {
|
enum PatternType {
|
||||||
Static(String),
|
Static(String),
|
||||||
Dynamic(Regex, Vec<String>),
|
Prefix(String),
|
||||||
|
Dynamic(Regex, Vec<String>, usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
@ -173,14 +174,23 @@ impl Resource {
|
|||||||
///
|
///
|
||||||
/// Panics if path pattern is wrong.
|
/// Panics if path pattern is wrong.
|
||||||
pub fn new(name: &str, path: &str) -> Self {
|
pub fn new(name: &str, path: &str) -> Self {
|
||||||
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
|
/// Construct external resource
|
||||||
///
|
///
|
||||||
/// Panics if path pattern is wrong.
|
/// Panics if path pattern is wrong.
|
||||||
pub fn external(name: &str, path: &str) -> Self {
|
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.rtp = ResourceType::External;
|
||||||
resource
|
resource
|
||||||
}
|
}
|
||||||
@ -197,8 +207,9 @@ impl Resource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parse path pattern and create new `Resource` instance with custom prefix
|
/// Parse path pattern and create new `Resource` instance with custom prefix
|
||||||
pub fn with_prefix(name: &str, path: &str, prefix: &str) -> Self {
|
pub fn with_prefix(name: &str, path: &str, prefix: &str, for_prefix: bool) -> Self {
|
||||||
let (pattern, elements, is_dynamic) = Resource::parse(path, prefix);
|
let (pattern, elements, is_dynamic, len) =
|
||||||
|
Resource::parse(path, prefix, for_prefix);
|
||||||
|
|
||||||
let tp = if is_dynamic {
|
let tp = if is_dynamic {
|
||||||
let re = match Regex::new(&pattern) {
|
let re = match Regex::new(&pattern) {
|
||||||
@ -208,7 +219,9 @@ impl Resource {
|
|||||||
let names = re.capture_names()
|
let names = re.capture_names()
|
||||||
.filter_map(|name| name.map(|name| name.to_owned()))
|
.filter_map(|name| name.map(|name| name.to_owned()))
|
||||||
.collect();
|
.collect();
|
||||||
PatternType::Dynamic(re, names)
|
PatternType::Dynamic(re, names, len)
|
||||||
|
} else if for_prefix {
|
||||||
|
PatternType::Prefix(pattern.clone())
|
||||||
} else {
|
} else {
|
||||||
PatternType::Static(pattern.clone())
|
PatternType::Static(pattern.clone())
|
||||||
};
|
};
|
||||||
@ -240,7 +253,8 @@ impl Resource {
|
|||||||
pub fn is_match(&self, path: &str) -> bool {
|
pub fn is_match(&self, path: &str) -> bool {
|
||||||
match self.tp {
|
match self.tp {
|
||||||
PatternType::Static(ref s) => s == path,
|
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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,7 +263,7 @@ impl Resource {
|
|||||||
) -> bool {
|
) -> bool {
|
||||||
match self.tp {
|
match self.tp {
|
||||||
PatternType::Static(ref s) => s == path,
|
PatternType::Static(ref s) => s == path,
|
||||||
PatternType::Dynamic(ref re, ref names) => {
|
PatternType::Dynamic(ref re, ref names, _) => {
|
||||||
if let Some(captures) = re.captures(path) {
|
if let Some(captures) = re.captures(path) {
|
||||||
let mut idx = 0;
|
let mut idx = 0;
|
||||||
for capture in captures.iter() {
|
for capture in captures.iter() {
|
||||||
@ -265,6 +279,42 @@ impl Resource {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
PatternType::Prefix(ref s) => path.starts_with(s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,7 +347,9 @@ impl Resource {
|
|||||||
Ok(path)
|
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 = "[^/]+";
|
const DEFAULT_PATTERN: &str = "[^/]+";
|
||||||
|
|
||||||
let mut re1 = String::from("^") + prefix;
|
let mut re1 = String::from("^") + prefix;
|
||||||
@ -309,6 +361,7 @@ impl Resource {
|
|||||||
let mut param_pattern = String::from(DEFAULT_PATTERN);
|
let mut param_pattern = String::from(DEFAULT_PATTERN);
|
||||||
let mut is_dynamic = false;
|
let mut is_dynamic = false;
|
||||||
let mut elems = Vec::new();
|
let mut elems = Vec::new();
|
||||||
|
let mut len = 0;
|
||||||
|
|
||||||
for (index, ch) in pattern.chars().enumerate() {
|
for (index, ch) in pattern.chars().enumerate() {
|
||||||
// All routes must have a leading slash so its optional to have one
|
// All routes must have a leading slash so its optional to have one
|
||||||
@ -325,6 +378,7 @@ impl Resource {
|
|||||||
param_name.clear();
|
param_name.clear();
|
||||||
param_pattern = String::from(DEFAULT_PATTERN);
|
param_pattern = String::from(DEFAULT_PATTERN);
|
||||||
|
|
||||||
|
len = 0;
|
||||||
in_param_pattern = false;
|
in_param_pattern = false;
|
||||||
in_param = false;
|
in_param = false;
|
||||||
} else if ch == ':' {
|
} else if ch == ':' {
|
||||||
@ -348,16 +402,19 @@ impl Resource {
|
|||||||
re1.push_str(escape(&ch.to_string()).as_str());
|
re1.push_str(escape(&ch.to_string()).as_str());
|
||||||
re2.push(ch);
|
re2.push(ch);
|
||||||
el.push(ch);
|
el.push(ch);
|
||||||
|
len += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let re = if is_dynamic {
|
let re = if is_dynamic {
|
||||||
|
if !for_prefix {
|
||||||
re1.push('$');
|
re1.push('$');
|
||||||
|
}
|
||||||
re1
|
re1
|
||||||
} else {
|
} else {
|
||||||
re2
|
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");
|
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]
|
#[test]
|
||||||
fn test_request_resource() {
|
fn test_request_resource() {
|
||||||
let routes = vec![
|
let routes = vec![
|
||||||
|
372
src/scope.rs
372
src/scope.rs
@ -1,5 +1,6 @@
|
|||||||
use std::cell::UnsafeCell;
|
use std::cell::UnsafeCell;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
use std::mem;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use futures::{Async, Future, Poll};
|
use futures::{Async, Future, Poll};
|
||||||
@ -9,19 +10,28 @@ use handler::{AsyncResult, AsyncResultItem, FromRequest, Responder, RouteHandler
|
|||||||
use http::Method;
|
use http::Method;
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
use middleware::{Finished as MiddlewareFinished, Middleware,
|
use middleware::{
|
||||||
Response as MiddlewareResponse, Started as MiddlewareStarted};
|
Finished as MiddlewareFinished, Middleware, Response as MiddlewareResponse,
|
||||||
|
Started as MiddlewareStarted,
|
||||||
|
};
|
||||||
|
use pred::Predicate;
|
||||||
use resource::ResourceHandler;
|
use resource::ResourceHandler;
|
||||||
use router::Resource;
|
use router::Resource;
|
||||||
|
|
||||||
type Route<S> = UnsafeCell<Box<RouteHandler<S>>>;
|
type Route<S> = UnsafeCell<Box<RouteHandler<S>>>;
|
||||||
type ScopeResources<S> = Rc<Vec<(Resource, Rc<UnsafeCell<ResourceHandler<S>>>)>>;
|
type ScopeResources<S> = Rc<Vec<(Resource, Rc<UnsafeCell<ResourceHandler<S>>>)>>;
|
||||||
|
type NestedInfo<S> = (Resource, Route<S>, Vec<Box<Predicate<S>>>);
|
||||||
|
|
||||||
/// Resources scope
|
/// Resources scope
|
||||||
///
|
///
|
||||||
/// Scope is a set of resources with common root path.
|
/// Scope is a set of resources with common root path.
|
||||||
/// Scopes collect multiple paths under a common path prefix.
|
/// Scopes collect multiple paths under a common path prefix.
|
||||||
/// Scope path can not contain variable path segments as resources.
|
/// Scope path can contain variable path segments as resources.
|
||||||
|
/// Scope prefix is always complete path segment, i.e `/app` would
|
||||||
|
/// be converted to a `/app/` and it would not match `/app` path.
|
||||||
|
///
|
||||||
|
/// You can get variable path segments from `HttpRequest::match_info()`.
|
||||||
|
/// `Path` extractor also is able to extract scope level variable segments.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate actix_web;
|
/// # extern crate actix_web;
|
||||||
@ -29,7 +39,7 @@ type ScopeResources<S> = Rc<Vec<(Resource, Rc<UnsafeCell<ResourceHandler<S>>>)>>
|
|||||||
///
|
///
|
||||||
/// fn main() {
|
/// fn main() {
|
||||||
/// let app = App::new()
|
/// let app = App::new()
|
||||||
/// .scope("/app", |scope| {
|
/// .scope("/{project_id}/", |scope| {
|
||||||
/// scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
|
/// scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
|
||||||
/// .resource("/path2", |r| r.f(|_| HttpResponse::Ok()))
|
/// .resource("/path2", |r| r.f(|_| HttpResponse::Ok()))
|
||||||
/// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed()))
|
/// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed()))
|
||||||
@ -38,26 +48,24 @@ type ScopeResources<S> = Rc<Vec<(Resource, Rc<UnsafeCell<ResourceHandler<S>>>)>>
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// In the above example three routes get registered:
|
/// In the above example three routes get registered:
|
||||||
/// * /app/path1 - reponds to all http method
|
/// * /{project_id}/path1 - reponds to all http method
|
||||||
/// * /app/path2 - `GET` requests
|
/// * /{project_id}/path2 - `GET` requests
|
||||||
/// * /app/path3 - `HEAD` requests
|
/// * /{project_id}/path3 - `HEAD` requests
|
||||||
///
|
///
|
||||||
|
#[derive(Default)]
|
||||||
pub struct Scope<S: 'static> {
|
pub struct Scope<S: 'static> {
|
||||||
nested: Vec<(String, Route<S>)>,
|
filters: Vec<Box<Predicate<S>>>,
|
||||||
|
nested: Vec<NestedInfo<S>>,
|
||||||
middlewares: Rc<Vec<Box<Middleware<S>>>>,
|
middlewares: Rc<Vec<Box<Middleware<S>>>>,
|
||||||
default: Rc<UnsafeCell<ResourceHandler<S>>>,
|
default: Rc<UnsafeCell<ResourceHandler<S>>>,
|
||||||
resources: ScopeResources<S>,
|
resources: ScopeResources<S>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: 'static> Default for Scope<S> {
|
#[cfg_attr(feature = "cargo-clippy", allow(new_without_default_derive))]
|
||||||
fn default() -> Scope<S> {
|
|
||||||
Scope::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S: 'static> Scope<S> {
|
impl<S: 'static> Scope<S> {
|
||||||
pub fn new() -> Scope<S> {
|
pub fn new() -> Scope<S> {
|
||||||
Scope {
|
Scope {
|
||||||
|
filters: Vec::new(),
|
||||||
nested: Vec::new(),
|
nested: Vec::new(),
|
||||||
resources: Rc::new(Vec::new()),
|
resources: Rc::new(Vec::new()),
|
||||||
middlewares: Rc::new(Vec::new()),
|
middlewares: Rc::new(Vec::new()),
|
||||||
@ -65,6 +73,36 @@ impl<S: 'static> Scope<S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn take_filters(&mut self) -> Vec<Box<Predicate<S>>> {
|
||||||
|
mem::replace(&mut self.filters, Vec::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add match predicate to scope.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate actix_web;
|
||||||
|
/// use actix_web::{http, pred, App, HttpRequest, HttpResponse, Path};
|
||||||
|
///
|
||||||
|
/// fn index(data: Path<(String, String)>) -> &'static str {
|
||||||
|
/// "Welcome!"
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let app = App::new()
|
||||||
|
/// .scope("/app", |scope| {
|
||||||
|
/// scope.filter(pred::Header("content-type", "text/plain"))
|
||||||
|
/// .route("/test1", http::Method::GET, index)
|
||||||
|
/// .route("/test2", http::Method::POST,
|
||||||
|
/// |_: HttpRequest| HttpResponse::MethodNotAllowed())
|
||||||
|
/// });
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn filter<T: Predicate<S> + 'static>(mut self, p: T) -> Self {
|
||||||
|
self.filters.push(Box::new(p));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Create nested scope with new state.
|
/// Create nested scope with new state.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
@ -91,23 +129,30 @@ impl<S: 'static> Scope<S> {
|
|||||||
F: FnOnce(Scope<T>) -> Scope<T>,
|
F: FnOnce(Scope<T>) -> Scope<T>,
|
||||||
{
|
{
|
||||||
let scope = Scope {
|
let scope = Scope {
|
||||||
|
filters: Vec::new(),
|
||||||
nested: Vec::new(),
|
nested: Vec::new(),
|
||||||
resources: Rc::new(Vec::new()),
|
resources: Rc::new(Vec::new()),
|
||||||
middlewares: Rc::new(Vec::new()),
|
middlewares: Rc::new(Vec::new()),
|
||||||
default: Rc::new(UnsafeCell::new(ResourceHandler::default_not_found())),
|
default: Rc::new(UnsafeCell::new(ResourceHandler::default_not_found())),
|
||||||
};
|
};
|
||||||
let scope = f(scope);
|
let mut scope = f(scope);
|
||||||
|
|
||||||
let mut path = path.trim().trim_right_matches('/').to_owned();
|
let mut path = path.trim().trim_right_matches('/').to_owned();
|
||||||
if !path.is_empty() && !path.starts_with('/') {
|
if !path.is_empty() && !path.starts_with('/') {
|
||||||
path.insert(0, '/')
|
path.insert(0, '/')
|
||||||
}
|
}
|
||||||
|
if !path.ends_with('/') {
|
||||||
|
path.push('/');
|
||||||
|
}
|
||||||
|
|
||||||
let handler = UnsafeCell::new(Box::new(Wrapper {
|
let state = Rc::new(state);
|
||||||
scope,
|
let filters: Vec<Box<Predicate<S>>> = vec![Box::new(FiltersWrapper {
|
||||||
state: Rc::new(state),
|
state: Rc::clone(&state),
|
||||||
}));
|
filters: scope.take_filters(),
|
||||||
self.nested.push((path, handler));
|
})];
|
||||||
|
let handler = UnsafeCell::new(Box::new(Wrapper { scope, state }));
|
||||||
|
self.nested
|
||||||
|
.push((Resource::prefix("", &path), handler, filters));
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -138,20 +183,28 @@ impl<S: 'static> Scope<S> {
|
|||||||
F: FnOnce(Scope<S>) -> Scope<S>,
|
F: FnOnce(Scope<S>) -> Scope<S>,
|
||||||
{
|
{
|
||||||
let scope = Scope {
|
let scope = Scope {
|
||||||
|
filters: Vec::new(),
|
||||||
nested: Vec::new(),
|
nested: Vec::new(),
|
||||||
resources: Rc::new(Vec::new()),
|
resources: Rc::new(Vec::new()),
|
||||||
middlewares: Rc::new(Vec::new()),
|
middlewares: Rc::new(Vec::new()),
|
||||||
default: Rc::new(UnsafeCell::new(ResourceHandler::default_not_found())),
|
default: Rc::new(UnsafeCell::new(ResourceHandler::default_not_found())),
|
||||||
};
|
};
|
||||||
let scope = f(scope);
|
let mut scope = f(scope);
|
||||||
|
|
||||||
let mut path = path.trim().trim_right_matches('/').to_owned();
|
let mut path = path.trim().trim_right_matches('/').to_owned();
|
||||||
if !path.is_empty() && !path.starts_with('/') {
|
if !path.is_empty() && !path.starts_with('/') {
|
||||||
path.insert(0, '/')
|
path.insert(0, '/')
|
||||||
}
|
}
|
||||||
|
if !path.ends_with('/') {
|
||||||
|
path.push('/');
|
||||||
|
}
|
||||||
|
|
||||||
self.nested
|
let filters = scope.take_filters();
|
||||||
.push((path, UnsafeCell::new(Box::new(scope))));
|
self.nested.push((
|
||||||
|
Resource::prefix("", &path),
|
||||||
|
UnsafeCell::new(Box::new(scope)),
|
||||||
|
filters,
|
||||||
|
));
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -264,7 +317,7 @@ impl<S: 'static> Scope<S> {
|
|||||||
/// middlewares get invoked on scope level.
|
/// middlewares get invoked on scope level.
|
||||||
///
|
///
|
||||||
/// *Note* `Middleware::finish()` fires right after response get
|
/// *Note* `Middleware::finish()` fires right after response get
|
||||||
/// prepared. It does not wait until body get sent to peer.
|
/// prepared. It does not wait until body get sent to the peer.
|
||||||
pub fn middleware<M: Middleware<S>>(mut self, mw: M) -> Scope<S> {
|
pub fn middleware<M: Middleware<S>>(mut self, mw: M) -> Scope<S> {
|
||||||
Rc::get_mut(&mut self.middlewares)
|
Rc::get_mut(&mut self.middlewares)
|
||||||
.expect("Can not use after configuration")
|
.expect("Can not use after configuration")
|
||||||
@ -278,7 +331,7 @@ impl<S: 'static> RouteHandler<S> for Scope<S> {
|
|||||||
let path = unsafe { &*(&req.match_info()["tail"] as *const _) };
|
let path = unsafe { &*(&req.match_info()["tail"] as *const _) };
|
||||||
let path = if path == "" { "/" } else { path };
|
let path = if path == "" { "/" } else { path };
|
||||||
|
|
||||||
// recognize paths
|
// recognize resources
|
||||||
for &(ref pattern, ref resource) in self.resources.iter() {
|
for &(ref pattern, ref resource) in self.resources.iter() {
|
||||||
if pattern.match_with_params(path, req.match_info_mut()) {
|
if pattern.match_with_params(path, req.match_info_mut()) {
|
||||||
let default = unsafe { &mut *self.default.as_ref().get() };
|
let default = unsafe { &mut *self.default.as_ref().get() };
|
||||||
@ -298,17 +351,19 @@ impl<S: 'static> RouteHandler<S> for Scope<S> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// nested scopes
|
// nested scopes
|
||||||
for &(ref prefix, ref handler) in &self.nested {
|
|
||||||
let len = req.prefix_len() as usize;
|
let len = req.prefix_len() as usize;
|
||||||
let m = {
|
let path: &'static str = unsafe { &*(&req.path()[len..] as *const _) };
|
||||||
let path = &req.path()[len..];
|
|
||||||
path.starts_with(prefix)
|
|
||||||
&& (path.len() == prefix.len()
|
|
||||||
|| path.split_at(prefix.len()).1.starts_with('/'))
|
|
||||||
};
|
|
||||||
|
|
||||||
if m {
|
'outer: for &(ref prefix, ref handler, ref filters) in &self.nested {
|
||||||
let prefix_len = len + prefix.len();
|
if let Some(prefix_len) =
|
||||||
|
prefix.match_prefix_with_params(path, req.match_info_mut())
|
||||||
|
{
|
||||||
|
for filter in filters {
|
||||||
|
if !filter.check(&mut req) {
|
||||||
|
continue 'outer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let prefix_len = len + prefix_len - 1;
|
||||||
let path: &'static str =
|
let path: &'static str =
|
||||||
unsafe { &*(&req.path()[prefix_len..] as *const _) };
|
unsafe { &*(&req.path()[prefix_len..] as *const _) };
|
||||||
|
|
||||||
@ -347,8 +402,24 @@ struct Wrapper<S: 'static> {
|
|||||||
|
|
||||||
impl<S: 'static, S2: 'static> RouteHandler<S2> for Wrapper<S> {
|
impl<S: 'static, S2: 'static> RouteHandler<S2> for Wrapper<S> {
|
||||||
fn handle(&mut self, req: HttpRequest<S2>) -> AsyncResult<HttpResponse> {
|
fn handle(&mut self, req: HttpRequest<S2>) -> AsyncResult<HttpResponse> {
|
||||||
self.scope
|
self.scope.handle(req.change_state(Rc::clone(&self.state)))
|
||||||
.handle(req.change_state(Rc::clone(&self.state)))
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FiltersWrapper<S: 'static> {
|
||||||
|
state: Rc<S>,
|
||||||
|
filters: Vec<Box<Predicate<S>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: 'static, S2: 'static> Predicate<S2> for FiltersWrapper<S> {
|
||||||
|
fn check(&self, req: &mut HttpRequest<S2>) -> bool {
|
||||||
|
let mut req = req.change_state(Rc::clone(&self.state));
|
||||||
|
for filter in &self.filters {
|
||||||
|
if !filter.check(&mut req) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -388,7 +459,8 @@ impl<S: 'static> ComposeState<S> {
|
|||||||
|
|
||||||
impl<S: 'static> Compose<S> {
|
impl<S: 'static> Compose<S> {
|
||||||
fn new(
|
fn new(
|
||||||
req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>,
|
req: HttpRequest<S>,
|
||||||
|
mws: Rc<Vec<Box<Middleware<S>>>>,
|
||||||
resource: Rc<UnsafeCell<ResourceHandler<S>>>,
|
resource: Rc<UnsafeCell<ResourceHandler<S>>>,
|
||||||
default: Option<Rc<UnsafeCell<ResourceHandler<S>>>>,
|
default: Option<Rc<UnsafeCell<ResourceHandler<S>>>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@ -451,21 +523,12 @@ impl<S: 'static> StartMiddlewares<S> {
|
|||||||
Ok(MiddlewareStarted::Response(resp)) => {
|
Ok(MiddlewareStarted::Response(resp)) => {
|
||||||
return RunMiddlewares::init(info, resp)
|
return RunMiddlewares::init(info, resp)
|
||||||
}
|
}
|
||||||
Ok(MiddlewareStarted::Future(mut fut)) => match fut.poll() {
|
Ok(MiddlewareStarted::Future(fut)) => {
|
||||||
Ok(Async::NotReady) => {
|
|
||||||
return ComposeState::Starting(StartMiddlewares {
|
return ComposeState::Starting(StartMiddlewares {
|
||||||
fut: Some(fut),
|
fut: Some(fut),
|
||||||
_s: PhantomData,
|
_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()),
|
Err(err) => return Response::init(err.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -482,6 +545,7 @@ impl<S: 'static> StartMiddlewares<S> {
|
|||||||
if let Some(resp) = resp {
|
if let Some(resp) = resp {
|
||||||
return Some(RunMiddlewares::init(info, resp));
|
return Some(RunMiddlewares::init(info, resp));
|
||||||
}
|
}
|
||||||
|
loop {
|
||||||
if info.count == len {
|
if info.count == len {
|
||||||
let resource = unsafe { &mut *info.resource.get() };
|
let resource = unsafe { &mut *info.resource.get() };
|
||||||
let reply = if let Some(ref default) = info.default {
|
let reply = if let Some(ref default) = info.default {
|
||||||
@ -492,7 +556,6 @@ impl<S: 'static> StartMiddlewares<S> {
|
|||||||
};
|
};
|
||||||
return Some(WaitingResponse::init(info, reply));
|
return Some(WaitingResponse::init(info, reply));
|
||||||
} else {
|
} else {
|
||||||
loop {
|
|
||||||
match info.mws[info.count].start(&mut info.req) {
|
match info.mws[info.count].start(&mut info.req) {
|
||||||
Ok(MiddlewareStarted::Done) => info.count += 1,
|
Ok(MiddlewareStarted::Done) => info.count += 1,
|
||||||
Ok(MiddlewareStarted::Response(resp)) => {
|
Ok(MiddlewareStarted::Response(resp)) => {
|
||||||
@ -522,7 +585,8 @@ struct WaitingResponse<S> {
|
|||||||
impl<S: 'static> WaitingResponse<S> {
|
impl<S: 'static> WaitingResponse<S> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn init(
|
fn init(
|
||||||
info: &mut ComposeInfo<S>, reply: AsyncResult<HttpResponse>,
|
info: &mut ComposeInfo<S>,
|
||||||
|
reply: AsyncResult<HttpResponse>,
|
||||||
) -> ComposeState<S> {
|
) -> ComposeState<S> {
|
||||||
match reply.into() {
|
match reply.into() {
|
||||||
AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, resp),
|
AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, resp),
|
||||||
@ -698,9 +762,14 @@ impl<S: 'static> Response<S> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use bytes::Bytes;
|
||||||
|
|
||||||
use application::App;
|
use application::App;
|
||||||
use http::StatusCode;
|
use body::Body;
|
||||||
|
use http::{Method, StatusCode};
|
||||||
|
use httprequest::HttpRequest;
|
||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
|
use pred;
|
||||||
use test::TestRequest;
|
use test::TestRequest;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -716,6 +785,90 @@ mod tests {
|
|||||||
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_scope_route() {
|
||||||
|
let mut app = App::new()
|
||||||
|
.scope("app", |scope| {
|
||||||
|
scope
|
||||||
|
.route("/path1", Method::GET, |_: HttpRequest<_>| {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
})
|
||||||
|
.route("/path1", Method::DELETE, |_: HttpRequest<_>| {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/app/path1").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/app/path1")
|
||||||
|
.method(Method::DELETE)
|
||||||
|
.finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/app/path1")
|
||||||
|
.method(Method::POST)
|
||||||
|
.finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_scope_filter() {
|
||||||
|
let mut app = App::new()
|
||||||
|
.scope("/app", |scope| {
|
||||||
|
scope
|
||||||
|
.filter(pred::Get())
|
||||||
|
.resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
|
||||||
|
})
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/app/path1")
|
||||||
|
.method(Method::POST)
|
||||||
|
.finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/app/path1")
|
||||||
|
.method(Method::GET)
|
||||||
|
.finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_scope_variable_segment() {
|
||||||
|
let mut app = App::new()
|
||||||
|
.scope("/ab-{project}", |scope| {
|
||||||
|
scope.resource("/path1", |r| {
|
||||||
|
r.f(|r| {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
.body(format!("project: {}", &r.match_info()["project"]))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/ab-project1/path1").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
||||||
|
|
||||||
|
match resp.as_msg().body() {
|
||||||
|
&Body::Binary(ref b) => {
|
||||||
|
let bytes: Bytes = b.clone().into();
|
||||||
|
assert_eq!(bytes, Bytes::from_static(b"project: project1"));
|
||||||
|
}
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/aa-project1/path1").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_scope_with_state() {
|
fn test_scope_with_state() {
|
||||||
struct State;
|
struct State;
|
||||||
@ -733,6 +886,33 @@ mod tests {
|
|||||||
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
|
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_scope_with_state_filter() {
|
||||||
|
struct State;
|
||||||
|
|
||||||
|
let mut app = App::new()
|
||||||
|
.scope("/app", |scope| {
|
||||||
|
scope.with_state("/t1", State, |scope| {
|
||||||
|
scope
|
||||||
|
.filter(pred::Get())
|
||||||
|
.resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/app/t1/path1")
|
||||||
|
.method(Method::POST)
|
||||||
|
.finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/app/t1/path1")
|
||||||
|
.method(Method::GET)
|
||||||
|
.finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_nested_scope() {
|
fn test_nested_scope() {
|
||||||
let mut app = App::new()
|
let mut app = App::new()
|
||||||
@ -748,6 +928,98 @@ mod tests {
|
|||||||
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
|
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nested_scope_filter() {
|
||||||
|
let mut app = App::new()
|
||||||
|
.scope("/app", |scope| {
|
||||||
|
scope.nested("/t1", |scope| {
|
||||||
|
scope
|
||||||
|
.filter(pred::Get())
|
||||||
|
.resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/app/t1/path1")
|
||||||
|
.method(Method::POST)
|
||||||
|
.finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/app/t1/path1")
|
||||||
|
.method(Method::GET)
|
||||||
|
.finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nested_scope_with_variable_segment() {
|
||||||
|
let mut app = App::new()
|
||||||
|
.scope("/app", |scope| {
|
||||||
|
scope.nested("/{project_id}", |scope| {
|
||||||
|
scope.resource("/path1", |r| {
|
||||||
|
r.f(|r| {
|
||||||
|
HttpResponse::Created().body(format!(
|
||||||
|
"project: {}",
|
||||||
|
&r.match_info()["project_id"]
|
||||||
|
))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/app/project_1/path1").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
|
||||||
|
|
||||||
|
match resp.as_msg().body() {
|
||||||
|
&Body::Binary(ref b) => {
|
||||||
|
let bytes: Bytes = b.clone().into();
|
||||||
|
assert_eq!(bytes, Bytes::from_static(b"project: project_1"));
|
||||||
|
}
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nested2_scope_with_variable_segment() {
|
||||||
|
let mut app = App::new()
|
||||||
|
.scope("/app", |scope| {
|
||||||
|
scope.nested("/{project}", |scope| {
|
||||||
|
scope.nested("/{id}", |scope| {
|
||||||
|
scope.resource("/path1", |r| {
|
||||||
|
r.f(|r| {
|
||||||
|
HttpResponse::Created().body(format!(
|
||||||
|
"project: {} - {}",
|
||||||
|
&r.match_info()["project"],
|
||||||
|
&r.match_info()["id"],
|
||||||
|
))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/app/test/1/path1").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
|
||||||
|
|
||||||
|
match resp.as_msg().body() {
|
||||||
|
&Body::Binary(ref b) => {
|
||||||
|
let bytes: Bytes = b.clone().into();
|
||||||
|
assert_eq!(bytes, Bytes::from_static(b"project: test - 1"));
|
||||||
|
}
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/app/test/1/path2").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_default_resource() {
|
fn test_default_resource() {
|
||||||
let mut app = App::new()
|
let mut app = App::new()
|
||||||
|
@ -38,7 +38,9 @@ where
|
|||||||
H: HttpHandler + 'static,
|
H: HttpHandler + 'static,
|
||||||
{
|
{
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
settings: Rc<WorkerSettings<H>>, mut io: T, peer: Option<SocketAddr>,
|
settings: Rc<WorkerSettings<H>>,
|
||||||
|
mut io: T,
|
||||||
|
peer: Option<SocketAddr>,
|
||||||
http2: bool,
|
http2: bool,
|
||||||
) -> HttpChannel<T, H> {
|
) -> HttpChannel<T, H> {
|
||||||
settings.add_channel();
|
settings.add_channel();
|
||||||
@ -61,7 +63,7 @@ where
|
|||||||
settings,
|
settings,
|
||||||
peer,
|
peer,
|
||||||
io,
|
io,
|
||||||
BytesMut::with_capacity(4096),
|
BytesMut::with_capacity(8192),
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -93,12 +95,12 @@ where
|
|||||||
let el = self as *mut _;
|
let el = self as *mut _;
|
||||||
self.node = Some(Node::new(el));
|
self.node = Some(Node::new(el));
|
||||||
let _ = match self.proto {
|
let _ = match self.proto {
|
||||||
Some(HttpProtocol::H1(ref mut h1)) => self.node
|
Some(HttpProtocol::H1(ref mut h1)) => {
|
||||||
.as_ref()
|
self.node.as_ref().map(|n| h1.settings().head().insert(n))
|
||||||
.map(|n| h1.settings().head().insert(n)),
|
}
|
||||||
Some(HttpProtocol::H2(ref mut h2)) => self.node
|
Some(HttpProtocol::H2(ref mut h2)) => {
|
||||||
.as_ref()
|
self.node.as_ref().map(|n| h2.settings().head().insert(n))
|
||||||
.map(|n| h2.settings().head().insert(n)),
|
}
|
||||||
Some(HttpProtocol::Unknown(ref mut settings, _, _, _)) => {
|
Some(HttpProtocol::Unknown(ref mut settings, _, _, _)) => {
|
||||||
self.node.as_ref().map(|n| settings.head().insert(n))
|
self.node.as_ref().map(|n| settings.head().insert(n))
|
||||||
}
|
}
|
||||||
@ -168,9 +170,8 @@ where
|
|||||||
if let Some(HttpProtocol::Unknown(settings, addr, io, buf)) = self.proto.take() {
|
if let Some(HttpProtocol::Unknown(settings, addr, io, buf)) = self.proto.take() {
|
||||||
match kind {
|
match kind {
|
||||||
ProtocolKind::Http1 => {
|
ProtocolKind::Http1 => {
|
||||||
self.proto = Some(HttpProtocol::H1(h1::Http1::new(
|
self.proto =
|
||||||
settings, io, addr, buf,
|
Some(HttpProtocol::H1(h1::Http1::new(settings, io, addr, buf)));
|
||||||
)));
|
|
||||||
return self.poll();
|
return self.poll();
|
||||||
}
|
}
|
||||||
ProtocolKind::Http2 => {
|
ProtocolKind::Http2 => {
|
||||||
|
@ -12,8 +12,10 @@ use flate2::read::GzDecoder;
|
|||||||
use flate2::write::{DeflateDecoder, DeflateEncoder, GzEncoder};
|
use flate2::write::{DeflateDecoder, DeflateEncoder, GzEncoder};
|
||||||
#[cfg(feature = "flate2")]
|
#[cfg(feature = "flate2")]
|
||||||
use flate2::Compression;
|
use flate2::Compression;
|
||||||
use http::header::{HeaderMap, HeaderValue, ACCEPT_ENCODING, CONTENT_ENCODING,
|
use http::header::{
|
||||||
CONTENT_LENGTH, TRANSFER_ENCODING};
|
HeaderMap, HeaderValue, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH,
|
||||||
|
TRANSFER_ENCODING,
|
||||||
|
};
|
||||||
use http::{HttpTryFrom, Method, Version};
|
use http::{HttpTryFrom, Method, Version};
|
||||||
|
|
||||||
use body::{Binary, Body};
|
use body::{Binary, Body};
|
||||||
@ -378,16 +380,19 @@ impl ContentEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn for_server(
|
pub fn for_server(
|
||||||
buf: SharedBytes, req: &HttpInnerMessage, resp: &mut HttpResponse,
|
buf: SharedBytes,
|
||||||
|
req: &HttpInnerMessage,
|
||||||
|
resp: &mut HttpResponse,
|
||||||
response_encoding: ContentEncoding,
|
response_encoding: ContentEncoding,
|
||||||
) -> ContentEncoder {
|
) -> ContentEncoder {
|
||||||
let version = resp.version().unwrap_or_else(|| req.version);
|
let version = resp.version().unwrap_or_else(|| req.version);
|
||||||
let is_head = req.method == Method::HEAD;
|
let is_head = req.method == Method::HEAD;
|
||||||
let mut body = resp.replace_body(Body::Empty);
|
let mut len = 0;
|
||||||
let has_body = match body {
|
let has_body = match resp.body() {
|
||||||
Body::Empty => false,
|
&Body::Empty => false,
|
||||||
Body::Binary(ref bin) => {
|
&Body::Binary(ref bin) => {
|
||||||
!(response_encoding == ContentEncoding::Auto && bin.len() < 96)
|
len = bin.len();
|
||||||
|
!(response_encoding == ContentEncoding::Auto && len < 96)
|
||||||
}
|
}
|
||||||
_ => true,
|
_ => true,
|
||||||
};
|
};
|
||||||
@ -421,14 +426,14 @@ impl ContentEncoder {
|
|||||||
ContentEncoding::Identity
|
ContentEncoding::Identity
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut transfer = match body {
|
let mut transfer = match resp.body() {
|
||||||
Body::Empty => {
|
&Body::Empty => {
|
||||||
if req.method != Method::HEAD {
|
if req.method != Method::HEAD {
|
||||||
resp.headers_mut().remove(CONTENT_LENGTH);
|
resp.headers_mut().remove(CONTENT_LENGTH);
|
||||||
}
|
}
|
||||||
TransferEncoding::length(0, buf)
|
TransferEncoding::length(0, buf)
|
||||||
}
|
}
|
||||||
Body::Binary(ref mut bytes) => {
|
&Body::Binary(_) => {
|
||||||
if !(encoding == ContentEncoding::Identity
|
if !(encoding == ContentEncoding::Identity
|
||||||
|| encoding == ContentEncoding::Auto)
|
|| encoding == ContentEncoding::Auto)
|
||||||
{
|
{
|
||||||
@ -448,19 +453,26 @@ impl ContentEncoder {
|
|||||||
ContentEncoding::Br => {
|
ContentEncoding::Br => {
|
||||||
ContentEncoder::Br(BrotliEncoder::new(transfer, 3))
|
ContentEncoder::Br(BrotliEncoder::new(transfer, 3))
|
||||||
}
|
}
|
||||||
ContentEncoding::Identity => ContentEncoder::Identity(transfer),
|
ContentEncoding::Identity | ContentEncoding::Auto => {
|
||||||
ContentEncoding::Auto => unreachable!(),
|
unreachable!()
|
||||||
};
|
|
||||||
// TODO return error!
|
|
||||||
let _ = enc.write(bytes.clone());
|
|
||||||
let _ = enc.write_eof();
|
|
||||||
|
|
||||||
*bytes = Binary::from(tmp.take());
|
|
||||||
encoding = ContentEncoding::Identity;
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let bin = resp.replace_body(Body::Empty).binary();
|
||||||
|
|
||||||
|
// TODO return error!
|
||||||
|
let _ = enc.write(bin);
|
||||||
|
let _ = enc.write_eof();
|
||||||
|
let body = tmp.take();
|
||||||
|
len = body.len();
|
||||||
|
|
||||||
|
encoding = ContentEncoding::Identity;
|
||||||
|
resp.replace_body(Binary::from(body));
|
||||||
|
}
|
||||||
|
|
||||||
if is_head {
|
if is_head {
|
||||||
let mut b = BytesMut::new();
|
let mut b = BytesMut::new();
|
||||||
let _ = write!(b, "{}", bytes.len());
|
let _ = write!(b, "{}", len);
|
||||||
resp.headers_mut().insert(
|
resp.headers_mut().insert(
|
||||||
CONTENT_LENGTH,
|
CONTENT_LENGTH,
|
||||||
HeaderValue::try_from(b.freeze()).unwrap(),
|
HeaderValue::try_from(b.freeze()).unwrap(),
|
||||||
@ -470,7 +482,7 @@ impl ContentEncoder {
|
|||||||
}
|
}
|
||||||
TransferEncoding::eof(buf)
|
TransferEncoding::eof(buf)
|
||||||
}
|
}
|
||||||
Body::Streaming(_) | Body::Actor(_) => {
|
&Body::Streaming(_) | &Body::Actor(_) => {
|
||||||
if resp.upgrade() {
|
if resp.upgrade() {
|
||||||
if version == Version::HTTP_2 {
|
if version == Version::HTTP_2 {
|
||||||
error!("Connection upgrade is forbidden for HTTP/2");
|
error!("Connection upgrade is forbidden for HTTP/2");
|
||||||
@ -485,11 +497,10 @@ impl ContentEncoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
//
|
// check for head response
|
||||||
if is_head {
|
if is_head {
|
||||||
|
resp.set_body(Body::Empty);
|
||||||
transfer.kind = TransferEncodingKind::Length(0);
|
transfer.kind = TransferEncodingKind::Length(0);
|
||||||
} else {
|
|
||||||
resp.replace_body(body);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
match encoding {
|
match encoding {
|
||||||
@ -511,7 +522,9 @@ impl ContentEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn streaming_encoding(
|
fn streaming_encoding(
|
||||||
buf: SharedBytes, version: Version, resp: &mut HttpResponse,
|
buf: SharedBytes,
|
||||||
|
version: Version,
|
||||||
|
resp: &mut HttpResponse,
|
||||||
) -> TransferEncoding {
|
) -> TransferEncoding {
|
||||||
match resp.chunked() {
|
match resp.chunked() {
|
||||||
Some(true) => {
|
Some(true) => {
|
||||||
@ -590,7 +603,7 @@ impl ContentEncoder {
|
|||||||
|
|
||||||
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
|
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn write_eof(&mut self) -> Result<(), io::Error> {
|
pub fn write_eof(&mut self) -> Result<bool, io::Error> {
|
||||||
let encoder = mem::replace(
|
let encoder = mem::replace(
|
||||||
self,
|
self,
|
||||||
ContentEncoder::Identity(TransferEncoding::eof(SharedBytes::empty())),
|
ContentEncoder::Identity(TransferEncoding::eof(SharedBytes::empty())),
|
||||||
@ -602,7 +615,7 @@ impl ContentEncoder {
|
|||||||
Ok(mut writer) => {
|
Ok(mut writer) => {
|
||||||
writer.encode_eof();
|
writer.encode_eof();
|
||||||
*self = ContentEncoder::Identity(writer);
|
*self = ContentEncoder::Identity(writer);
|
||||||
Ok(())
|
Ok(true)
|
||||||
}
|
}
|
||||||
Err(err) => Err(err),
|
Err(err) => Err(err),
|
||||||
},
|
},
|
||||||
@ -611,7 +624,7 @@ impl ContentEncoder {
|
|||||||
Ok(mut writer) => {
|
Ok(mut writer) => {
|
||||||
writer.encode_eof();
|
writer.encode_eof();
|
||||||
*self = ContentEncoder::Identity(writer);
|
*self = ContentEncoder::Identity(writer);
|
||||||
Ok(())
|
Ok(true)
|
||||||
}
|
}
|
||||||
Err(err) => Err(err),
|
Err(err) => Err(err),
|
||||||
},
|
},
|
||||||
@ -620,14 +633,14 @@ impl ContentEncoder {
|
|||||||
Ok(mut writer) => {
|
Ok(mut writer) => {
|
||||||
writer.encode_eof();
|
writer.encode_eof();
|
||||||
*self = ContentEncoder::Identity(writer);
|
*self = ContentEncoder::Identity(writer);
|
||||||
Ok(())
|
Ok(true)
|
||||||
}
|
}
|
||||||
Err(err) => Err(err),
|
Err(err) => Err(err),
|
||||||
},
|
},
|
||||||
ContentEncoder::Identity(mut writer) => {
|
ContentEncoder::Identity(mut writer) => {
|
||||||
writer.encode_eof();
|
let res = writer.encode_eof();
|
||||||
*self = ContentEncoder::Identity(writer);
|
*self = ContentEncoder::Identity(writer);
|
||||||
Ok(())
|
Ok(res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -763,8 +776,7 @@ impl TransferEncoding {
|
|||||||
return Ok(*remaining == 0);
|
return Ok(*remaining == 0);
|
||||||
}
|
}
|
||||||
let len = cmp::min(*remaining, msg.len() as u64);
|
let len = cmp::min(*remaining, msg.len() as u64);
|
||||||
self.buffer
|
self.buffer.extend(msg.take().split_to(len as usize).into());
|
||||||
.extend(msg.take().split_to(len as usize).into());
|
|
||||||
|
|
||||||
*remaining -= len as u64;
|
*remaining -= len as u64;
|
||||||
Ok(*remaining == 0)
|
Ok(*remaining == 0)
|
||||||
@ -777,14 +789,16 @@ impl TransferEncoding {
|
|||||||
|
|
||||||
/// Encode eof. Return `EOF` state of encoder
|
/// Encode eof. Return `EOF` state of encoder
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn encode_eof(&mut self) {
|
pub fn encode_eof(&mut self) -> bool {
|
||||||
match self.kind {
|
match self.kind {
|
||||||
TransferEncodingKind::Eof | TransferEncodingKind::Length(_) => (),
|
TransferEncodingKind::Eof => true,
|
||||||
|
TransferEncodingKind::Length(rem) => rem == 0,
|
||||||
TransferEncodingKind::Chunked(ref mut eof) => {
|
TransferEncodingKind::Chunked(ref mut eof) => {
|
||||||
if !*eof {
|
if !*eof {
|
||||||
*eof = true;
|
*eof = true;
|
||||||
self.buffer.extend_from_slice(b"0\r\n\r\n");
|
self.buffer.extend_from_slice(b"0\r\n\r\n");
|
||||||
}
|
}
|
||||||
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -848,10 +862,7 @@ impl AcceptEncoding {
|
|||||||
Err(_) => 0.0,
|
Err(_) => 0.0,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
Some(AcceptEncoding {
|
Some(AcceptEncoding { encoding, quality })
|
||||||
encoding,
|
|
||||||
quality,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a raw Accept-Encoding header value into an ordered list.
|
/// Parse a raw Accept-Encoding header value into an ordered list.
|
||||||
@ -879,9 +890,7 @@ mod tests {
|
|||||||
fn test_chunked_te() {
|
fn test_chunked_te() {
|
||||||
let bytes = SharedBytes::default();
|
let bytes = SharedBytes::default();
|
||||||
let mut enc = TransferEncoding::chunked(bytes.clone());
|
let mut enc = TransferEncoding::chunked(bytes.clone());
|
||||||
assert!(!enc.encode(Binary::from(b"test".as_ref()))
|
assert!(!enc.encode(Binary::from(b"test".as_ref())).ok().unwrap());
|
||||||
.ok()
|
|
||||||
.unwrap());
|
|
||||||
assert!(enc.encode(Binary::from(b"".as_ref())).ok().unwrap());
|
assert!(enc.encode(Binary::from(b"".as_ref())).ok().unwrap());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
bytes.get_mut().take().freeze(),
|
bytes.get_mut().take().freeze(),
|
||||||
|
182
src/server/h1.rs
182
src/server/h1.rs
@ -67,7 +67,9 @@ where
|
|||||||
H: HttpHandler + 'static,
|
H: HttpHandler + 'static,
|
||||||
{
|
{
|
||||||
pub fn new(
|
pub fn new(
|
||||||
settings: Rc<WorkerSettings<H>>, stream: T, addr: Option<SocketAddr>,
|
settings: Rc<WorkerSettings<H>>,
|
||||||
|
stream: T,
|
||||||
|
addr: Option<SocketAddr>,
|
||||||
buf: BytesMut,
|
buf: BytesMut,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let bytes = settings.get_shared_bytes();
|
let bytes = settings.get_shared_bytes();
|
||||||
@ -146,10 +148,12 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
/// read data from stream
|
||||||
pub fn poll_io(&mut self) {
|
pub fn poll_io(&mut self) {
|
||||||
// read io from socket
|
// read io from socket
|
||||||
if !self.flags.intersects(Flags::ERROR)
|
if !self.flags.intersects(Flags::ERROR)
|
||||||
&& self.tasks.len() < MAX_PIPELINED_MESSAGES && self.can_read()
|
&& self.tasks.len() < MAX_PIPELINED_MESSAGES
|
||||||
|
&& self.can_read()
|
||||||
{
|
{
|
||||||
if self.read() {
|
if self.read() {
|
||||||
// notify all tasks
|
// notify all tasks
|
||||||
@ -158,7 +162,6 @@ where
|
|||||||
entry.pipe.disconnected()
|
entry.pipe.disconnected()
|
||||||
}
|
}
|
||||||
// kill keepalive
|
// kill keepalive
|
||||||
self.flags.remove(Flags::KEEPALIVE);
|
|
||||||
self.keepalive_timer.take();
|
self.keepalive_timer.take();
|
||||||
|
|
||||||
// on parse error, stop reading stream but tasks need to be
|
// on parse error, stop reading stream but tasks need to be
|
||||||
@ -205,10 +208,9 @@ where
|
|||||||
self.stream.reset();
|
self.stream.reset();
|
||||||
|
|
||||||
if ready {
|
if ready {
|
||||||
item.flags
|
item.flags.insert(EntryFlags::EOF | EntryFlags::FINISHED);
|
||||||
.insert(EntryFlags::EOF | EntryFlags::FINISHED);
|
|
||||||
} else {
|
} else {
|
||||||
item.flags.insert(EntryFlags::FINISHED);
|
item.flags.insert(EntryFlags::EOF);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// no more IO for this iteration
|
// no more IO for this iteration
|
||||||
@ -324,7 +326,36 @@ where
|
|||||||
// search handler for request
|
// search handler for request
|
||||||
for h in self.settings.handlers().iter_mut() {
|
for h in self.settings.handlers().iter_mut() {
|
||||||
req = match h.handle(req) {
|
req = match h.handle(req) {
|
||||||
Ok(pipe) => {
|
Ok(mut pipe) => {
|
||||||
|
if self.tasks.is_empty() {
|
||||||
|
match pipe.poll_io(&mut self.stream) {
|
||||||
|
Ok(Async::Ready(ready)) => {
|
||||||
|
// override keep-alive state
|
||||||
|
if self.stream.keepalive() {
|
||||||
|
self.flags.insert(Flags::KEEPALIVE);
|
||||||
|
} else {
|
||||||
|
self.flags.remove(Flags::KEEPALIVE);
|
||||||
|
}
|
||||||
|
// prepare stream for next response
|
||||||
|
self.stream.reset();
|
||||||
|
|
||||||
|
if !ready {
|
||||||
|
let item = Entry {
|
||||||
|
pipe,
|
||||||
|
flags: EntryFlags::EOF,
|
||||||
|
};
|
||||||
|
self.tasks.push_back(item);
|
||||||
|
}
|
||||||
|
continue 'outer;
|
||||||
|
}
|
||||||
|
Ok(Async::NotReady) => {}
|
||||||
|
Err(err) => {
|
||||||
|
error!("Unhandled error: {}", err);
|
||||||
|
self.flags.insert(Flags::ERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
self.tasks.push_back(Entry {
|
self.tasks.push_back(Entry {
|
||||||
pipe,
|
pipe,
|
||||||
flags: EntryFlags::empty(),
|
flags: EntryFlags::empty(),
|
||||||
@ -347,6 +378,7 @@ where
|
|||||||
} else {
|
} else {
|
||||||
error!("Internal server error: unexpected payload chunk");
|
error!("Internal server error: unexpected payload chunk");
|
||||||
self.flags.insert(Flags::ERROR);
|
self.flags.insert(Flags::ERROR);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Some(Message::Eof)) => {
|
Ok(Some(Message::Eof)) => {
|
||||||
@ -355,6 +387,7 @@ where
|
|||||||
} else {
|
} else {
|
||||||
error!("Internal server error: unexpected eof");
|
error!("Internal server error: unexpected eof");
|
||||||
self.flags.insert(Flags::ERROR);
|
self.flags.insert(Flags::ERROR);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(None) => break,
|
Ok(None) => break,
|
||||||
@ -367,6 +400,7 @@ where
|
|||||||
};
|
};
|
||||||
payload.set_error(e);
|
payload.set_error(e);
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -398,8 +432,12 @@ where
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use bytes::{Bytes, BytesMut};
|
use std::net::Shutdown;
|
||||||
|
use std::{cmp, time};
|
||||||
|
|
||||||
|
use bytes::{Buf, Bytes, BytesMut};
|
||||||
use http::{Method, Version};
|
use http::{Method, Version};
|
||||||
|
use tokio_io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use application::HttpApplication;
|
use application::HttpApplication;
|
||||||
@ -463,6 +501,101 @@ mod tests {
|
|||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Buffer {
|
||||||
|
buf: Bytes,
|
||||||
|
err: Option<io::Error>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Buffer {
|
||||||
|
fn new(data: &'static str) -> Buffer {
|
||||||
|
Buffer {
|
||||||
|
buf: Bytes::from(data),
|
||||||
|
err: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn feed_data(&mut self, data: &'static str) {
|
||||||
|
let mut b = BytesMut::from(self.buf.as_ref());
|
||||||
|
b.extend(data.as_bytes());
|
||||||
|
self.buf = b.take().freeze();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsyncRead for Buffer {}
|
||||||
|
impl io::Read for Buffer {
|
||||||
|
fn read(&mut self, dst: &mut [u8]) -> Result<usize, io::Error> {
|
||||||
|
if self.buf.is_empty() {
|
||||||
|
if self.err.is_some() {
|
||||||
|
Err(self.err.take().unwrap())
|
||||||
|
} else {
|
||||||
|
Err(io::Error::new(io::ErrorKind::WouldBlock, ""))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let size = cmp::min(self.buf.len(), dst.len());
|
||||||
|
let b = self.buf.split_to(size);
|
||||||
|
dst[..size].copy_from_slice(&b);
|
||||||
|
Ok(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IoStream for Buffer {
|
||||||
|
fn shutdown(&mut self, _: Shutdown) -> io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn set_nodelay(&mut self, _: bool) -> io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn set_linger(&mut self, _: Option<time::Duration>) -> io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl io::Write for Buffer {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
|
Ok(buf.len())
|
||||||
|
}
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AsyncWrite for Buffer {
|
||||||
|
fn shutdown(&mut self) -> Poll<(), io::Error> {
|
||||||
|
Ok(Async::Ready(()))
|
||||||
|
}
|
||||||
|
fn write_buf<B: Buf>(&mut self, _: &mut B) -> Poll<usize, io::Error> {
|
||||||
|
Ok(Async::NotReady)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_req_parse() {
|
||||||
|
let buf = Buffer::new("GET /test HTTP/1.1\r\n\r\n");
|
||||||
|
let readbuf = BytesMut::new();
|
||||||
|
let settings = Rc::new(WorkerSettings::<HttpApplication>::new(
|
||||||
|
Vec::new(),
|
||||||
|
KeepAlive::Os,
|
||||||
|
));
|
||||||
|
|
||||||
|
let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf);
|
||||||
|
h1.poll_io();
|
||||||
|
h1.parse();
|
||||||
|
assert_eq!(h1.tasks.len(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_req_parse_err() {
|
||||||
|
let buf = Buffer::new("GET /test HTTP/1\r\n\r\n");
|
||||||
|
let readbuf = BytesMut::new();
|
||||||
|
let settings = Rc::new(WorkerSettings::<HttpApplication>::new(
|
||||||
|
Vec::new(),
|
||||||
|
KeepAlive::Os,
|
||||||
|
));
|
||||||
|
|
||||||
|
let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf);
|
||||||
|
h1.poll_io();
|
||||||
|
h1.parse();
|
||||||
|
assert!(h1.flags.contains(Flags::ERROR));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse() {
|
fn test_parse() {
|
||||||
let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n\r\n");
|
let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n\r\n");
|
||||||
@ -614,10 +747,7 @@ mod tests {
|
|||||||
assert_eq!(req.version(), Version::HTTP_11);
|
assert_eq!(req.version(), Version::HTTP_11);
|
||||||
assert_eq!(*req.method(), Method::GET);
|
assert_eq!(*req.method(), Method::GET);
|
||||||
assert_eq!(req.path(), "/test");
|
assert_eq!(req.path(), "/test");
|
||||||
assert_eq!(
|
assert_eq!(req.headers().get("test").unwrap().as_bytes(), b"value");
|
||||||
req.headers().get("test").unwrap().as_bytes(),
|
|
||||||
b"value"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
Ok(_) | Err(_) => unreachable!("Error during parsing http request"),
|
Ok(_) | Err(_) => unreachable!("Error during parsing http request"),
|
||||||
}
|
}
|
||||||
@ -918,13 +1048,7 @@ mod tests {
|
|||||||
.as_ref(),
|
.as_ref(),
|
||||||
b"line"
|
b"line"
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(reader.decode(&mut buf, &settings).unwrap().unwrap().eof());
|
||||||
reader
|
|
||||||
.decode(&mut buf, &settings)
|
|
||||||
.unwrap()
|
|
||||||
.unwrap()
|
|
||||||
.eof()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -1005,13 +1129,7 @@ mod tests {
|
|||||||
assert!(reader.decode(&mut buf, &settings).unwrap().is_none());
|
assert!(reader.decode(&mut buf, &settings).unwrap().is_none());
|
||||||
|
|
||||||
buf.extend(b"\r\n");
|
buf.extend(b"\r\n");
|
||||||
assert!(
|
assert!(reader.decode(&mut buf, &settings).unwrap().unwrap().eof());
|
||||||
reader
|
|
||||||
.decode(&mut buf, &settings)
|
|
||||||
.unwrap()
|
|
||||||
.unwrap()
|
|
||||||
.eof()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -1029,17 +1147,9 @@ mod tests {
|
|||||||
assert!(req.chunked().unwrap());
|
assert!(req.chunked().unwrap());
|
||||||
|
|
||||||
buf.extend(b"4;test\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); // test: test\r\n\r\n")
|
buf.extend(b"4;test\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); // test: test\r\n\r\n")
|
||||||
let chunk = reader
|
let chunk = reader.decode(&mut buf, &settings).unwrap().unwrap().chunk();
|
||||||
.decode(&mut buf, &settings)
|
|
||||||
.unwrap()
|
|
||||||
.unwrap()
|
|
||||||
.chunk();
|
|
||||||
assert_eq!(chunk, Bytes::from_static(b"data"));
|
assert_eq!(chunk, Bytes::from_static(b"data"));
|
||||||
let chunk = reader
|
let chunk = reader.decode(&mut buf, &settings).unwrap().unwrap().chunk();
|
||||||
.decode(&mut buf, &settings)
|
|
||||||
.unwrap()
|
|
||||||
.unwrap()
|
|
||||||
.chunk();
|
|
||||||
assert_eq!(chunk, Bytes::from_static(b"line"));
|
assert_eq!(chunk, Bytes::from_static(b"line"));
|
||||||
let msg = reader.decode(&mut buf, &settings).unwrap().unwrap();
|
let msg = reader.decode(&mut buf, &settings).unwrap().unwrap();
|
||||||
assert!(msg.eof());
|
assert!(msg.eof());
|
||||||
|
@ -46,7 +46,9 @@ impl H1Decoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn decode<H>(
|
pub fn decode<H>(
|
||||||
&mut self, src: &mut BytesMut, settings: &WorkerSettings<H>,
|
&mut self,
|
||||||
|
src: &mut BytesMut,
|
||||||
|
settings: &WorkerSettings<H>,
|
||||||
) -> Result<Option<Message>, DecoderError> {
|
) -> Result<Option<Message>, DecoderError> {
|
||||||
// read payload
|
// read payload
|
||||||
if self.decoder.is_some() {
|
if self.decoder.is_some() {
|
||||||
@ -64,18 +66,11 @@ impl H1Decoder {
|
|||||||
.map_err(DecoderError::Error)?
|
.map_err(DecoderError::Error)?
|
||||||
{
|
{
|
||||||
Async::Ready((msg, decoder)) => {
|
Async::Ready((msg, decoder)) => {
|
||||||
if let Some(decoder) = decoder {
|
self.decoder = decoder;
|
||||||
self.decoder = Some(decoder);
|
|
||||||
Ok(Some(Message::Message {
|
Ok(Some(Message::Message {
|
||||||
msg,
|
msg,
|
||||||
payload: true,
|
payload: self.decoder.is_some(),
|
||||||
}))
|
}))
|
||||||
} else {
|
|
||||||
Ok(Some(Message::Message {
|
|
||||||
msg,
|
|
||||||
payload: false,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Async::NotReady => {
|
Async::NotReady => {
|
||||||
if src.len() >= MAX_BUFFER_SIZE {
|
if src.len() >= MAX_BUFFER_SIZE {
|
||||||
@ -89,7 +84,9 @@ impl H1Decoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn parse_message<H>(
|
fn parse_message<H>(
|
||||||
&self, buf: &mut BytesMut, settings: &WorkerSettings<H>,
|
&self,
|
||||||
|
buf: &mut BytesMut,
|
||||||
|
settings: &WorkerSettings<H>,
|
||||||
) -> Poll<(SharedHttpInnerMessage, Option<EncodingDecoder>), ParseError> {
|
) -> Poll<(SharedHttpInnerMessage, Option<EncodingDecoder>), ParseError> {
|
||||||
// Parse http message
|
// Parse http message
|
||||||
let mut has_upgrade = false;
|
let mut has_upgrade = false;
|
||||||
@ -148,7 +145,7 @@ impl H1Decoder {
|
|||||||
header::CONTENT_LENGTH => {
|
header::CONTENT_LENGTH => {
|
||||||
if let Ok(s) = value.to_str() {
|
if let Ok(s) = value.to_str() {
|
||||||
if let Ok(len) = s.parse::<u64>() {
|
if let Ok(len) = s.parse::<u64>() {
|
||||||
content_length = Some(len)
|
content_length = Some(len);
|
||||||
} else {
|
} else {
|
||||||
debug!("illegal Content-Length: {:?}", len);
|
debug!("illegal Content-Length: {:?}", len);
|
||||||
return Err(ParseError::Header);
|
return Err(ParseError::Header);
|
||||||
@ -351,7 +348,10 @@ macro_rules! byte (
|
|||||||
|
|
||||||
impl ChunkedState {
|
impl ChunkedState {
|
||||||
fn step(
|
fn step(
|
||||||
&self, body: &mut BytesMut, size: &mut u64, buf: &mut Option<Bytes>,
|
&self,
|
||||||
|
body: &mut BytesMut,
|
||||||
|
size: &mut u64,
|
||||||
|
buf: &mut Option<Bytes>,
|
||||||
) -> Poll<ChunkedState, io::Error> {
|
) -> Poll<ChunkedState, io::Error> {
|
||||||
use self::ChunkedState::*;
|
use self::ChunkedState::*;
|
||||||
match *self {
|
match *self {
|
||||||
@ -414,7 +414,8 @@ impl ChunkedState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn read_size_lf(
|
fn read_size_lf(
|
||||||
rdr: &mut BytesMut, size: &mut u64,
|
rdr: &mut BytesMut,
|
||||||
|
size: &mut u64,
|
||||||
) -> Poll<ChunkedState, io::Error> {
|
) -> Poll<ChunkedState, io::Error> {
|
||||||
match byte!(rdr) {
|
match byte!(rdr) {
|
||||||
b'\n' if *size > 0 => Ok(Async::Ready(ChunkedState::Body)),
|
b'\n' if *size > 0 => Ok(Async::Ready(ChunkedState::Body)),
|
||||||
@ -427,7 +428,9 @@ impl ChunkedState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn read_body(
|
fn read_body(
|
||||||
rdr: &mut BytesMut, rem: &mut u64, buf: &mut Option<Bytes>,
|
rdr: &mut BytesMut,
|
||||||
|
rem: &mut u64,
|
||||||
|
buf: &mut Option<Bytes>,
|
||||||
) -> Poll<ChunkedState, io::Error> {
|
) -> Poll<ChunkedState, io::Error> {
|
||||||
trace!("Chunked read, remaining={:?}", rem);
|
trace!("Chunked read, remaining={:?}", rem);
|
||||||
|
|
||||||
|
@ -42,7 +42,9 @@ pub(crate) struct H1Writer<T: AsyncWrite, H: 'static> {
|
|||||||
|
|
||||||
impl<T: AsyncWrite, H: 'static> H1Writer<T, H> {
|
impl<T: AsyncWrite, H: 'static> H1Writer<T, H> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
stream: T, buf: SharedBytes, settings: Rc<WorkerSettings<H>>,
|
stream: T,
|
||||||
|
buf: SharedBytes,
|
||||||
|
settings: Rc<WorkerSettings<H>>,
|
||||||
) -> H1Writer<T, H> {
|
) -> H1Writer<T, H> {
|
||||||
H1Writer {
|
H1Writer {
|
||||||
flags: Flags::empty(),
|
flags: Flags::empty(),
|
||||||
@ -101,7 +103,9 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn start(
|
fn start(
|
||||||
&mut self, req: &mut HttpInnerMessage, msg: &mut HttpResponse,
|
&mut self,
|
||||||
|
req: &mut HttpInnerMessage,
|
||||||
|
msg: &mut HttpResponse,
|
||||||
encoding: ContentEncoding,
|
encoding: ContentEncoding,
|
||||||
) -> io::Result<WriterState> {
|
) -> io::Result<WriterState> {
|
||||||
// prepare task
|
// prepare task
|
||||||
@ -138,7 +142,9 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
|
|||||||
let reason = msg.reason().as_bytes();
|
let reason = msg.reason().as_bytes();
|
||||||
let mut is_bin = if let Body::Binary(ref bytes) = body {
|
let mut is_bin = if let Body::Binary(ref bytes) = body {
|
||||||
buffer.reserve(
|
buffer.reserve(
|
||||||
256 + msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len()
|
256
|
||||||
|
+ msg.headers().len() * AVERAGE_HEADER_SIZE
|
||||||
|
+ bytes.len()
|
||||||
+ reason.len(),
|
+ reason.len(),
|
||||||
);
|
);
|
||||||
true
|
true
|
||||||
@ -255,9 +261,7 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn write_eof(&mut self) -> io::Result<WriterState> {
|
fn write_eof(&mut self) -> io::Result<WriterState> {
|
||||||
self.encoder.write_eof()?;
|
if !self.encoder.write_eof()? {
|
||||||
|
|
||||||
if !self.encoder.is_eof() {
|
|
||||||
Err(io::Error::new(
|
Err(io::Error::new(
|
||||||
io::ErrorKind::Other,
|
io::ErrorKind::Other,
|
||||||
"Last payload item, but eof is not reached",
|
"Last payload item, but eof is not reached",
|
||||||
@ -276,7 +280,9 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
|
|||||||
unsafe { &mut *(self.buffer.as_ref() as *const _ as *mut _) };
|
unsafe { &mut *(self.buffer.as_ref() as *const _ as *mut _) };
|
||||||
let written = self.write_data(buf)?;
|
let written = self.write_data(buf)?;
|
||||||
let _ = self.buffer.split_to(written);
|
let _ = self.buffer.split_to(written);
|
||||||
if self.buffer.len() > self.buffer_capacity {
|
if shutdown && !self.buffer.is_empty()
|
||||||
|
|| (self.buffer.len() > self.buffer_capacity)
|
||||||
|
{
|
||||||
return Ok(Async::NotReady);
|
return Ok(Async::NotReady);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -343,24 +343,27 @@ impl<H: 'static> Entry<H> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn poll_payload(&mut self) {
|
fn poll_payload(&mut self) {
|
||||||
if !self.flags.contains(EntryFlags::REOF) {
|
while !self.flags.contains(EntryFlags::REOF)
|
||||||
if self.payload.need_read() == PayloadStatus::Read {
|
&& self.payload.need_read() == PayloadStatus::Read
|
||||||
if let Err(err) = self.recv.release_capacity().release_capacity(32_768) {
|
{
|
||||||
self.payload.set_error(PayloadError::Http2(err))
|
|
||||||
}
|
|
||||||
} else if let Err(err) = self.recv.release_capacity().release_capacity(0) {
|
|
||||||
self.payload.set_error(PayloadError::Http2(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
match self.recv.poll() {
|
match self.recv.poll() {
|
||||||
Ok(Async::Ready(Some(chunk))) => {
|
Ok(Async::Ready(Some(chunk))) => {
|
||||||
|
let l = chunk.len();
|
||||||
self.payload.feed_data(chunk);
|
self.payload.feed_data(chunk);
|
||||||
|
if let Err(err) = self.recv.release_capacity().release_capacity(l) {
|
||||||
|
self.payload.set_error(PayloadError::Http2(err));
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(Async::Ready(None)) => {
|
Ok(Async::Ready(None)) => {
|
||||||
self.flags.insert(EntryFlags::REOF);
|
self.flags.insert(EntryFlags::REOF);
|
||||||
|
self.payload.feed_eof();
|
||||||
|
}
|
||||||
|
Ok(Async::NotReady) => break,
|
||||||
|
Err(err) => {
|
||||||
|
self.payload.set_error(PayloadError::Http2(err));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
Ok(Async::NotReady) => (),
|
|
||||||
Err(err) => self.payload.set_error(PayloadError::Http2(err)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,6 @@ use body::Body;
|
|||||||
use httpresponse::{HttpResponse, HttpResponseBuilder, HttpResponsePool};
|
use httpresponse::{HttpResponse, HttpResponseBuilder, HttpResponsePool};
|
||||||
|
|
||||||
/// Various server settings
|
/// Various server settings
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct ServerSettings {
|
pub struct ServerSettings {
|
||||||
addr: Option<net::SocketAddr>,
|
addr: Option<net::SocketAddr>,
|
||||||
secure: bool,
|
secure: bool,
|
||||||
@ -28,6 +27,18 @@ pub struct ServerSettings {
|
|||||||
unsafe impl Sync for ServerSettings {}
|
unsafe impl Sync for ServerSettings {}
|
||||||
unsafe impl Send for ServerSettings {}
|
unsafe impl Send for ServerSettings {}
|
||||||
|
|
||||||
|
impl Clone for ServerSettings {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
ServerSettings {
|
||||||
|
addr: self.addr,
|
||||||
|
secure: self.secure,
|
||||||
|
host: self.host.clone(),
|
||||||
|
cpu_pool: self.cpu_pool.clone(),
|
||||||
|
responses: HttpResponsePool::pool(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct InnerCpuPool {
|
struct InnerCpuPool {
|
||||||
cpu_pool: UnsafeCell<Option<CpuPool>>,
|
cpu_pool: UnsafeCell<Option<CpuPool>>,
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,20 @@ use super::worker::{Conn, SocketInfo, StopWorker, StreamHandlerType, Worker};
|
|||||||
use super::{IntoHttpHandler, IoStream, KeepAlive};
|
use super::{IntoHttpHandler, IoStream, KeepAlive};
|
||||||
use super::{PauseServer, ResumeServer, StopServer};
|
use super::{PauseServer, ResumeServer, StopServer};
|
||||||
|
|
||||||
|
#[cfg(feature = "alpn")]
|
||||||
|
fn configure_alpn(builder: &mut SslAcceptorBuilder) -> io::Result<()> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// An HTTP Server
|
/// An HTTP Server
|
||||||
pub struct HttpServer<H>
|
pub struct HttpServer<H>
|
||||||
where
|
where
|
||||||
@ -197,6 +211,16 @@ where
|
|||||||
self.sockets.iter().map(|s| s.addr).collect()
|
self.sockets.iter().map(|s| s.addr).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get addresses of bound sockets and the scheme for it.
|
||||||
|
///
|
||||||
|
/// This is useful when the server is bound from different sources
|
||||||
|
/// with some sockets listening on http and some listening on https
|
||||||
|
/// and the user should be presented with an enumeration of which
|
||||||
|
/// socket requires which protocol.
|
||||||
|
pub fn addrs_with_scheme(&self) -> Vec<(net::SocketAddr, &str)> {
|
||||||
|
self.sockets.iter().map(|s| (s.addr, s.tp.scheme())).collect()
|
||||||
|
}
|
||||||
|
|
||||||
/// Use listener for accepting incoming connection requests
|
/// Use listener for accepting incoming connection requests
|
||||||
///
|
///
|
||||||
/// HttpServer does not change any configuration for TcpListener,
|
/// HttpServer does not change any configuration for TcpListener,
|
||||||
@ -211,6 +235,40 @@ where
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "tls")]
|
||||||
|
/// Use listener for accepting incoming tls connection requests
|
||||||
|
///
|
||||||
|
/// HttpServer does not change any configuration for TcpListener,
|
||||||
|
/// it needs to be configured before passing it to listen() method.
|
||||||
|
pub fn listen_tls(mut self, lst: net::TcpListener, acceptor: TlsAcceptor) -> Self {
|
||||||
|
let addr = lst.local_addr().unwrap();
|
||||||
|
self.sockets.push(Socket {
|
||||||
|
addr,
|
||||||
|
lst,
|
||||||
|
tp: StreamHandlerType::Tls(acceptor.clone()),
|
||||||
|
});
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "alpn")]
|
||||||
|
/// Use listener for accepting incoming tls connection requests
|
||||||
|
///
|
||||||
|
/// This method sets alpn protocols to "h2" and "http/1.1"
|
||||||
|
pub fn listen_ssl(mut self, lst: net::TcpListener, mut builder: SslAcceptorBuilder) -> io::Result<Self> {
|
||||||
|
// alpn support
|
||||||
|
if !self.no_http2 {
|
||||||
|
configure_alpn(&mut builder)?;
|
||||||
|
}
|
||||||
|
let acceptor = builder.build();
|
||||||
|
let addr = lst.local_addr().unwrap();
|
||||||
|
self.sockets.push(Socket {
|
||||||
|
addr,
|
||||||
|
lst,
|
||||||
|
tp: StreamHandlerType::Alpn(acceptor.clone()),
|
||||||
|
});
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
fn bind2<S: net::ToSocketAddrs>(&mut self, addr: S) -> io::Result<Vec<Socket>> {
|
fn bind2<S: net::ToSocketAddrs>(&mut self, addr: S) -> io::Result<Vec<Socket>> {
|
||||||
let mut err = None;
|
let mut err = None;
|
||||||
let mut succ = false;
|
let mut succ = false;
|
||||||
@ -277,15 +335,7 @@ where
|
|||||||
) -> io::Result<Self> {
|
) -> io::Result<Self> {
|
||||||
// alpn support
|
// alpn support
|
||||||
if !self.no_http2 {
|
if !self.no_http2 {
|
||||||
builder.set_alpn_protos(b"\x02h2\x08http/1.1")?;
|
configure_alpn(&mut builder)?;
|
||||||
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 acceptor = builder.build();
|
||||||
|
@ -239,4 +239,14 @@ impl StreamHandlerType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn scheme(&self) -> &'static str {
|
||||||
|
match *self {
|
||||||
|
StreamHandlerType::Normal => "http",
|
||||||
|
#[cfg(feature = "tls")]
|
||||||
|
StreamHandlerType::Tls(ref acceptor) => "https",
|
||||||
|
#[cfg(feature = "alpn")]
|
||||||
|
StreamHandlerType::Alpn(ref acceptor) => "https",
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
15
src/test.rs
15
src/test.rs
@ -355,12 +355,7 @@ impl<S: 'static> TestApp<S> {
|
|||||||
|
|
||||||
/// Register handler for "/"
|
/// Register handler for "/"
|
||||||
pub fn handler<H: Handler<S>>(&mut self, handler: H) {
|
pub fn handler<H: Handler<S>>(&mut self, handler: H) {
|
||||||
self.app = Some(
|
self.app = Some(self.app.take().unwrap().resource("/", |r| r.h(handler)));
|
||||||
self.app
|
|
||||||
.take()
|
|
||||||
.unwrap()
|
|
||||||
.resource("/", |r| r.h(handler)),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register middleware
|
/// Register middleware
|
||||||
@ -562,8 +557,8 @@ impl<S: 'static> TestRequest<S> {
|
|||||||
cookies,
|
cookies,
|
||||||
payload,
|
payload,
|
||||||
} = self;
|
} = self;
|
||||||
let req = HttpRequest::new(method, uri, version, headers, payload);
|
let mut req = HttpRequest::new(method, uri, version, headers, payload);
|
||||||
req.as_mut().cookies = cookies;
|
req.set_cookies(cookies);
|
||||||
req.as_mut().params = params;
|
req.as_mut().params = params;
|
||||||
let (router, _) = Router::new::<S>("/", ServerSettings::default(), Vec::new());
|
let (router, _) = Router::new::<S>("/", ServerSettings::default(), Vec::new());
|
||||||
req.with_state(Rc::new(state), router)
|
req.with_state(Rc::new(state), router)
|
||||||
@ -583,8 +578,8 @@ impl<S: 'static> TestRequest<S> {
|
|||||||
payload,
|
payload,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let req = HttpRequest::new(method, uri, version, headers, payload);
|
let mut req = HttpRequest::new(method, uri, version, headers, payload);
|
||||||
req.as_mut().cookies = cookies;
|
req.set_cookies(cookies);
|
||||||
req.as_mut().params = params;
|
req.as_mut().params = params;
|
||||||
req.with_state(Rc::new(state), router)
|
req.with_state(Rc::new(state), router)
|
||||||
}
|
}
|
||||||
|
195
src/with.rs
195
src/with.rs
@ -9,6 +9,62 @@ use handler::{AsyncResult, AsyncResultItem, FromRequest, Handler, Responder};
|
|||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
|
|
||||||
|
/// Extractor configuration
|
||||||
|
///
|
||||||
|
/// `Route::with()` and `Route::with_async()` returns instance
|
||||||
|
/// of the `ExtractorConfig` type. It could be used for extractor configuration.
|
||||||
|
///
|
||||||
|
/// In this example `Form<FormData>` configured.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate actix_web;
|
||||||
|
/// #[macro_use] extern crate serde_derive;
|
||||||
|
/// use actix_web::{App, Form, Result, http};
|
||||||
|
///
|
||||||
|
/// #[derive(Deserialize)]
|
||||||
|
/// struct FormData {
|
||||||
|
/// username: String,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn index(form: Form<FormData>) -> Result<String> {
|
||||||
|
/// Ok(format!("Welcome {}!", form.username))
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let app = App::new().resource(
|
||||||
|
/// "/index.html", |r| {
|
||||||
|
/// r.method(http::Method::GET)
|
||||||
|
/// .with(index)
|
||||||
|
/// .limit(4096);} // <- change form extractor configuration
|
||||||
|
/// );
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Same could be donce with multiple extractors
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate actix_web;
|
||||||
|
/// #[macro_use] extern crate serde_derive;
|
||||||
|
/// use actix_web::{App, Form, Path, Result, http};
|
||||||
|
///
|
||||||
|
/// #[derive(Deserialize)]
|
||||||
|
/// struct FormData {
|
||||||
|
/// username: String,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn index(data: (Path<(String,)>, Form<FormData>)) -> Result<String> {
|
||||||
|
/// Ok(format!("Welcome {}!", data.1.username))
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let app = App::new().resource(
|
||||||
|
/// "/index.html", |r| {
|
||||||
|
/// r.method(http::Method::GET)
|
||||||
|
/// .with(index)
|
||||||
|
/// .1.limit(4096);} // <- change form extractor configuration
|
||||||
|
/// );
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
pub struct ExtractorConfig<S: 'static, T: FromRequest<S>> {
|
pub struct ExtractorConfig<S: 'static, T: FromRequest<S>> {
|
||||||
cfg: Rc<UnsafeCell<T::Config>>,
|
cfg: Rc<UnsafeCell<T::Config>>,
|
||||||
}
|
}
|
||||||
@ -167,6 +223,145 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct WithAsync<T, S, F, R, I, E>
|
||||||
|
where
|
||||||
|
F: Fn(T) -> R,
|
||||||
|
R: Future<Item = I, Error = E>,
|
||||||
|
I: Responder,
|
||||||
|
E: Into<E>,
|
||||||
|
T: FromRequest<S>,
|
||||||
|
S: 'static,
|
||||||
|
{
|
||||||
|
hnd: Rc<UnsafeCell<F>>,
|
||||||
|
cfg: ExtractorConfig<S, T>,
|
||||||
|
_s: PhantomData<S>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, S, F, R, I, E> WithAsync<T, S, F, R, I, E>
|
||||||
|
where
|
||||||
|
F: Fn(T) -> R,
|
||||||
|
R: Future<Item = I, Error = E>,
|
||||||
|
I: Responder,
|
||||||
|
E: Into<Error>,
|
||||||
|
T: FromRequest<S>,
|
||||||
|
S: 'static,
|
||||||
|
{
|
||||||
|
pub fn new(f: F, cfg: ExtractorConfig<S, T>) -> Self {
|
||||||
|
WithAsync {
|
||||||
|
cfg,
|
||||||
|
hnd: Rc::new(UnsafeCell::new(f)),
|
||||||
|
_s: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, S, F, R, I, E> Handler<S> for WithAsync<T, S, F, R, I, E>
|
||||||
|
where
|
||||||
|
F: Fn(T) -> R + 'static,
|
||||||
|
R: Future<Item = I, Error = E> + 'static,
|
||||||
|
I: Responder + 'static,
|
||||||
|
E: Into<Error> + 'static,
|
||||||
|
T: FromRequest<S> + 'static,
|
||||||
|
S: 'static,
|
||||||
|
{
|
||||||
|
type Result = AsyncResult<HttpResponse>;
|
||||||
|
|
||||||
|
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
|
||||||
|
let mut fut = WithAsyncHandlerFut {
|
||||||
|
req,
|
||||||
|
started: false,
|
||||||
|
hnd: Rc::clone(&self.hnd),
|
||||||
|
cfg: self.cfg.clone(),
|
||||||
|
fut1: None,
|
||||||
|
fut2: None,
|
||||||
|
fut3: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
match fut.poll() {
|
||||||
|
Ok(Async::Ready(resp)) => AsyncResult::ok(resp),
|
||||||
|
Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)),
|
||||||
|
Err(e) => AsyncResult::err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WithAsyncHandlerFut<T, S, F, R, I, E>
|
||||||
|
where
|
||||||
|
F: Fn(T) -> R,
|
||||||
|
R: Future<Item = I, Error = E> + 'static,
|
||||||
|
I: Responder + 'static,
|
||||||
|
E: Into<Error> + 'static,
|
||||||
|
T: FromRequest<S> + 'static,
|
||||||
|
S: 'static,
|
||||||
|
{
|
||||||
|
started: bool,
|
||||||
|
hnd: Rc<UnsafeCell<F>>,
|
||||||
|
cfg: ExtractorConfig<S, T>,
|
||||||
|
req: HttpRequest<S>,
|
||||||
|
fut1: Option<Box<Future<Item = T, Error = Error>>>,
|
||||||
|
fut2: Option<R>,
|
||||||
|
fut3: Option<Box<Future<Item = HttpResponse, Error = Error>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, S, F, R, I, E> Future for WithAsyncHandlerFut<T, S, F, R, I, E>
|
||||||
|
where
|
||||||
|
F: Fn(T) -> R,
|
||||||
|
R: Future<Item = I, Error = E> + 'static,
|
||||||
|
I: Responder + 'static,
|
||||||
|
E: Into<Error> + 'static,
|
||||||
|
T: FromRequest<S> + 'static,
|
||||||
|
S: 'static,
|
||||||
|
{
|
||||||
|
type Item = HttpResponse;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||||
|
if let Some(ref mut fut) = self.fut3 {
|
||||||
|
return fut.poll();
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.fut2.is_some() {
|
||||||
|
return match self.fut2.as_mut().unwrap().poll() {
|
||||||
|
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||||
|
Ok(Async::Ready(r)) => match r.respond_to(&self.req) {
|
||||||
|
Ok(r) => match r.into().into() {
|
||||||
|
AsyncResultItem::Err(err) => Err(err),
|
||||||
|
AsyncResultItem::Ok(resp) => Ok(Async::Ready(resp)),
|
||||||
|
AsyncResultItem::Future(fut) => {
|
||||||
|
self.fut3 = Some(fut);
|
||||||
|
self.poll()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => Err(e.into()),
|
||||||
|
},
|
||||||
|
Err(e) => Err(e.into()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let item = if !self.started {
|
||||||
|
self.started = true;
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match self.fut1.as_mut().unwrap().poll()? {
|
||||||
|
Async::Ready(item) => item,
|
||||||
|
Async::NotReady => return Ok(Async::NotReady),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
|
||||||
|
self.fut2 = Some((*hnd)(item));
|
||||||
|
self.poll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct With2<T1, T2, S, F, R>
|
pub struct With2<T1, T2, S, F, R>
|
||||||
where
|
where
|
||||||
F: Fn(T1, T2) -> R,
|
F: Fn(T1, T2) -> R,
|
||||||
|
@ -518,24 +518,22 @@ impl ClientWriter {
|
|||||||
fn as_mut(&mut self) -> &mut Inner {
|
fn as_mut(&mut self) -> &mut Inner {
|
||||||
unsafe { &mut *self.inner.get() }
|
unsafe { &mut *self.inner.get() }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl WsWriter for ClientWriter {
|
|
||||||
/// Send text frame
|
/// Send text frame
|
||||||
#[inline]
|
#[inline]
|
||||||
fn text<T: Into<Binary>>(&mut self, text: T) {
|
pub fn text<T: Into<Binary>>(&mut self, text: T) {
|
||||||
self.write(Frame::message(text.into(), OpCode::Text, true, true));
|
self.write(Frame::message(text.into(), OpCode::Text, true, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send binary frame
|
/// Send binary frame
|
||||||
#[inline]
|
#[inline]
|
||||||
fn binary<B: Into<Binary>>(&mut self, data: B) {
|
pub fn binary<B: Into<Binary>>(&mut self, data: B) {
|
||||||
self.write(Frame::message(data, OpCode::Binary, true, true));
|
self.write(Frame::message(data, OpCode::Binary, true, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send ping frame
|
/// Send ping frame
|
||||||
#[inline]
|
#[inline]
|
||||||
fn ping(&mut self, message: &str) {
|
pub fn ping(&mut self, message: &str) {
|
||||||
self.write(Frame::message(
|
self.write(Frame::message(
|
||||||
Vec::from(message),
|
Vec::from(message),
|
||||||
OpCode::Ping,
|
OpCode::Ping,
|
||||||
@ -546,7 +544,7 @@ impl WsWriter for ClientWriter {
|
|||||||
|
|
||||||
/// Send pong frame
|
/// Send pong frame
|
||||||
#[inline]
|
#[inline]
|
||||||
fn pong(&mut self, message: &str) {
|
pub fn pong(&mut self, message: &str) {
|
||||||
self.write(Frame::message(
|
self.write(Frame::message(
|
||||||
Vec::from(message),
|
Vec::from(message),
|
||||||
OpCode::Pong,
|
OpCode::Pong,
|
||||||
@ -557,7 +555,39 @@ impl WsWriter for ClientWriter {
|
|||||||
|
|
||||||
/// Send close frame
|
/// Send close frame
|
||||||
#[inline]
|
#[inline]
|
||||||
fn close(&mut self, reason: Option<CloseReason>) {
|
pub fn close(&mut self, reason: Option<CloseReason>) {
|
||||||
self.write(Frame::close(reason, true));
|
self.write(Frame::close(reason, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl WsWriter for ClientWriter {
|
||||||
|
/// Send text frame
|
||||||
|
#[inline]
|
||||||
|
fn send_text<T: Into<Binary>>(&mut self, text: T) {
|
||||||
|
self.text(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send binary frame
|
||||||
|
#[inline]
|
||||||
|
fn send_binary<B: Into<Binary>>(&mut self, data: B) {
|
||||||
|
self.binary(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send ping frame
|
||||||
|
#[inline]
|
||||||
|
fn send_ping(&mut self, message: &str) {
|
||||||
|
self.ping(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send pong frame
|
||||||
|
#[inline]
|
||||||
|
fn send_pong(&mut self, message: &str) {
|
||||||
|
self.pong(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send close frame
|
||||||
|
#[inline]
|
||||||
|
fn send_close(&mut self, reason: Option<CloseReason>) {
|
||||||
|
self.close(reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -13,9 +13,9 @@ use context::{ActorHttpContext, Drain, Frame as ContextFrame};
|
|||||||
use error::{Error, ErrorInternalServerError};
|
use error::{Error, ErrorInternalServerError};
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
|
|
||||||
use ws::WsWriter;
|
|
||||||
use ws::frame::Frame;
|
use ws::frame::Frame;
|
||||||
use ws::proto::{CloseReason, OpCode};
|
use ws::proto::{CloseReason, OpCode};
|
||||||
|
use ws::WsWriter;
|
||||||
|
|
||||||
/// Execution context for `WebSockets` actors
|
/// Execution context for `WebSockets` actors
|
||||||
pub struct WebsocketContext<A, S = ()>
|
pub struct WebsocketContext<A, S = ()>
|
||||||
@ -149,6 +149,46 @@ where
|
|||||||
Drain::new(rx)
|
Drain::new(rx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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, reason: Option<CloseReason>) {
|
||||||
|
self.write(Frame::close(reason, false));
|
||||||
|
}
|
||||||
|
|
||||||
/// Check if connection still open
|
/// Check if connection still open
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn connected(&self) -> bool {
|
pub fn connected(&self) -> bool {
|
||||||
@ -181,42 +221,32 @@ where
|
|||||||
{
|
{
|
||||||
/// Send text frame
|
/// Send text frame
|
||||||
#[inline]
|
#[inline]
|
||||||
fn text<T: Into<Binary>>(&mut self, text: T) {
|
fn send_text<T: Into<Binary>>(&mut self, text: T) {
|
||||||
self.write(Frame::message(text.into(), OpCode::Text, true, false));
|
self.text(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send binary frame
|
/// Send binary frame
|
||||||
#[inline]
|
#[inline]
|
||||||
fn binary<B: Into<Binary>>(&mut self, data: B) {
|
fn send_binary<B: Into<Binary>>(&mut self, data: B) {
|
||||||
self.write(Frame::message(data, OpCode::Binary, true, false));
|
self.binary(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send ping frame
|
/// Send ping frame
|
||||||
#[inline]
|
#[inline]
|
||||||
fn ping(&mut self, message: &str) {
|
fn send_ping(&mut self, message: &str) {
|
||||||
self.write(Frame::message(
|
self.ping(message)
|
||||||
Vec::from(message),
|
|
||||||
OpCode::Ping,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send pong frame
|
/// Send pong frame
|
||||||
#[inline]
|
#[inline]
|
||||||
fn pong(&mut self, message: &str) {
|
fn send_pong(&mut self, message: &str) {
|
||||||
self.write(Frame::message(
|
self.pong(message)
|
||||||
Vec::from(message),
|
|
||||||
OpCode::Pong,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send close frame
|
/// Send close frame
|
||||||
#[inline]
|
#[inline]
|
||||||
fn close(&mut self, reason: Option<CloseReason>) {
|
fn send_close(&mut self, reason: Option<CloseReason>) {
|
||||||
self.write(Frame::close(reason, false));
|
self.close(reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,15 +343,15 @@ where
|
|||||||
/// Common writing methods for a websocket.
|
/// Common writing methods for a websocket.
|
||||||
pub trait WsWriter {
|
pub trait WsWriter {
|
||||||
/// Send a text
|
/// Send a text
|
||||||
fn text<T: Into<Binary>>(&mut self, text: T);
|
fn send_text<T: Into<Binary>>(&mut self, text: T);
|
||||||
/// Send a binary
|
/// Send a binary
|
||||||
fn binary<B: Into<Binary>>(&mut self, data: B);
|
fn send_binary<B: Into<Binary>>(&mut self, data: B);
|
||||||
/// Send a ping message
|
/// Send a ping message
|
||||||
fn ping(&mut self, message: &str);
|
fn send_ping(&mut self, message: &str);
|
||||||
/// Send a pong message
|
/// Send a pong message
|
||||||
fn pong(&mut self, message: &str);
|
fn send_pong(&mut self, message: &str);
|
||||||
/// Close the connection
|
/// Close the connection
|
||||||
fn close(&mut self, reason: Option<CloseReason>);
|
fn send_close(&mut self, reason: Option<CloseReason>);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -9,6 +9,7 @@ extern crate tokio_core;
|
|||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
|
|
||||||
|
use std::io;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use actix::*;
|
use actix::*;
|
||||||
@ -377,6 +378,83 @@ fn test_path_and_query_extractor2_async4() {
|
|||||||
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
|
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(actix_impl_trait)]
|
||||||
|
fn test_impl_trait(
|
||||||
|
data: (Json<Value>, Path<PParam>, Query<PParam>),
|
||||||
|
) -> impl Future<Item = String, Error = io::Error> {
|
||||||
|
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
|
||||||
|
.unwrap()
|
||||||
|
.and_then(move |_| {
|
||||||
|
Ok(format!(
|
||||||
|
"Welcome {} - {}!",
|
||||||
|
data.1.username,
|
||||||
|
(data.0).0
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(actix_impl_trait)]
|
||||||
|
fn test_impl_trait_err(
|
||||||
|
_data: (Json<Value>, Path<PParam>, Query<PParam>),
|
||||||
|
) -> impl Future<Item = String, Error = io::Error> {
|
||||||
|
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
|
||||||
|
.unwrap()
|
||||||
|
.and_then(move |_| Err(io::Error::new(io::ErrorKind::Other, "other")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(actix_impl_trait)]
|
||||||
|
#[test]
|
||||||
|
fn test_path_and_query_extractor2_async4_impl_trait() {
|
||||||
|
let mut srv = test::TestServer::new(|app| {
|
||||||
|
app.resource("/{username}/index.html", |r| {
|
||||||
|
r.route().with_async(test_impl_trait)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(actix_impl_trait)]
|
||||||
|
#[test]
|
||||||
|
fn test_path_and_query_extractor2_async4_impl_trait_err() {
|
||||||
|
let mut srv = test::TestServer::new(|app| {
|
||||||
|
app.resource("/{username}/index.html", |r| {
|
||||||
|
r.route().with_async(test_impl_trait_err)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_non_ascii_route() {
|
fn test_non_ascii_route() {
|
||||||
let mut srv = test::TestServer::new(|app| {
|
let mut srv = test::TestServer::new(|app| {
|
||||||
|
@ -21,28 +21,24 @@ struct MiddlewareTest {
|
|||||||
|
|
||||||
impl<S> middleware::Middleware<S> for MiddlewareTest {
|
impl<S> middleware::Middleware<S> for MiddlewareTest {
|
||||||
fn start(&self, _: &mut HttpRequest<S>) -> Result<middleware::Started> {
|
fn start(&self, _: &mut HttpRequest<S>) -> Result<middleware::Started> {
|
||||||
self.start.store(
|
self.start
|
||||||
self.start.load(Ordering::Relaxed) + 1,
|
.store(self.start.load(Ordering::Relaxed) + 1, Ordering::Relaxed);
|
||||||
Ordering::Relaxed,
|
|
||||||
);
|
|
||||||
Ok(middleware::Started::Done)
|
Ok(middleware::Started::Done)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn response(
|
fn response(
|
||||||
&self, _: &mut HttpRequest<S>, resp: HttpResponse,
|
&self,
|
||||||
|
_: &mut HttpRequest<S>,
|
||||||
|
resp: HttpResponse,
|
||||||
) -> Result<middleware::Response> {
|
) -> Result<middleware::Response> {
|
||||||
self.response.store(
|
self.response
|
||||||
self.response.load(Ordering::Relaxed) + 1,
|
.store(self.response.load(Ordering::Relaxed) + 1, Ordering::Relaxed);
|
||||||
Ordering::Relaxed,
|
|
||||||
);
|
|
||||||
Ok(middleware::Response::Done(resp))
|
Ok(middleware::Response::Done(resp))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(&self, _: &mut HttpRequest<S>, _: &HttpResponse) -> middleware::Finished {
|
fn finish(&self, _: &mut HttpRequest<S>, _: &HttpResponse) -> middleware::Finished {
|
||||||
self.finish.store(
|
self.finish
|
||||||
self.finish.load(Ordering::Relaxed) + 1,
|
.store(self.finish.load(Ordering::Relaxed) + 1, Ordering::Relaxed);
|
||||||
Ordering::Relaxed,
|
|
||||||
);
|
|
||||||
middleware::Finished::Done
|
middleware::Finished::Done
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -187,10 +183,7 @@ fn test_scope_middleware() {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
let request = srv.get()
|
let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap();
|
||||||
.uri(srv.url("/scope/test"))
|
|
||||||
.finish()
|
|
||||||
.unwrap();
|
|
||||||
let response = srv.execute(request.send()).unwrap();
|
let response = srv.execute(request.send()).unwrap();
|
||||||
assert!(response.status().is_success());
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
@ -226,10 +219,7 @@ fn test_scope_middleware_multiple() {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
let request = srv.get()
|
let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap();
|
||||||
.uri(srv.url("/scope/test"))
|
|
||||||
.finish()
|
|
||||||
.unwrap();
|
|
||||||
let response = srv.execute(request.send()).unwrap();
|
let response = srv.execute(request.send()).unwrap();
|
||||||
assert!(response.status().is_success());
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
@ -337,10 +327,7 @@ fn test_scope_middleware_async_handler() {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
let request = srv.get()
|
let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap();
|
||||||
.uri(srv.url("/scope/test"))
|
|
||||||
.finish()
|
|
||||||
.unwrap();
|
|
||||||
let response = srv.execute(request.send()).unwrap();
|
let response = srv.execute(request.send()).unwrap();
|
||||||
assert!(response.status().is_success());
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
@ -402,10 +389,7 @@ fn test_scope_middleware_async_error() {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
let request = srv.get()
|
let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap();
|
||||||
.uri(srv.url("/scope/test"))
|
|
||||||
.finish()
|
|
||||||
.unwrap();
|
|
||||||
let response = srv.execute(request.send()).unwrap();
|
let response = srv.execute(request.send()).unwrap();
|
||||||
assert_eq!(response.status(), http::StatusCode::BAD_REQUEST);
|
assert_eq!(response.status(), http::StatusCode::BAD_REQUEST);
|
||||||
|
|
||||||
@ -466,7 +450,9 @@ impl<S> middleware::Middleware<S> for MiddlewareAsyncTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn response(
|
fn response(
|
||||||
&self, _: &mut HttpRequest<S>, resp: HttpResponse,
|
&self,
|
||||||
|
_: &mut HttpRequest<S>,
|
||||||
|
resp: HttpResponse,
|
||||||
) -> Result<middleware::Response> {
|
) -> Result<middleware::Response> {
|
||||||
let to = Timeout::new(Duration::from_millis(10), &Arbiter::handle()).unwrap();
|
let to = Timeout::new(Duration::from_millis(10), &Arbiter::handle()).unwrap();
|
||||||
|
|
||||||
@ -551,7 +537,43 @@ fn test_async_middleware_multiple() {
|
|||||||
assert_eq!(num1.load(Ordering::Relaxed), 2);
|
assert_eq!(num1.load(Ordering::Relaxed), 2);
|
||||||
assert_eq!(num2.load(Ordering::Relaxed), 2);
|
assert_eq!(num2.load(Ordering::Relaxed), 2);
|
||||||
|
|
||||||
thread::sleep(Duration::from_millis(30));
|
thread::sleep(Duration::from_millis(50));
|
||||||
|
assert_eq!(num3.load(Ordering::Relaxed), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_async_sync_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(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("/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);
|
assert_eq!(num3.load(Ordering::Relaxed), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -577,10 +599,7 @@ fn test_async_scope_middleware() {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
let request = srv.get()
|
let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap();
|
||||||
.uri(srv.url("/scope/test"))
|
|
||||||
.finish()
|
|
||||||
.unwrap();
|
|
||||||
let response = srv.execute(request.send()).unwrap();
|
let response = srv.execute(request.send()).unwrap();
|
||||||
assert!(response.status().is_success());
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
@ -618,10 +637,45 @@ fn test_async_scope_middleware_multiple() {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
let request = srv.get()
|
let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap();
|
||||||
.uri(srv.url("/scope/test"))
|
let response = srv.execute(request.send()).unwrap();
|
||||||
.finish()
|
assert!(response.status().is_success());
|
||||||
.unwrap();
|
|
||||||
|
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_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(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();
|
let response = srv.execute(request.send()).unwrap();
|
||||||
assert!(response.status().is_success());
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
@ -703,3 +757,42 @@ fn test_async_resource_middleware_multiple() {
|
|||||||
thread::sleep(Duration::from_millis(40));
|
thread::sleep(Duration::from_millis(40));
|
||||||
assert_eq!(num3.load(Ordering::Relaxed), 2);
|
assert_eq!(num3.load(Ordering::Relaxed), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_async_sync_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 = MiddlewareTest {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
@ -809,7 +809,7 @@ fn test_h2() {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
let _res = core.run(tcp);
|
let _res = core.run(tcp);
|
||||||
// assert_eq!(res.unwrap(), Bytes::from_static(STR.as_ref()));
|
// assert_eq!(_res.unwrap(), Bytes::from_static(STR.as_ref()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
Loading…
Reference in New Issue
Block a user