1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-14 05:57:02 +02:00

Compare commits

...

92 Commits

Author SHA1 Message Date
6862aa6ee7 prepare release 2018-06-13 05:04:59 -07:00
8a22558f25 http/2 end-of-frame is not set if body is empty bytes #307 2018-06-12 14:47:45 -07:00
b5b9f9656e do not allow stream or actor responses for internal error #301 2018-06-11 19:44:11 -07:00
2fffc55d34 update changelog 2018-06-11 18:53:36 -07:00
7d39f1582e InternalError can trigger memory unsafety #301 2018-06-11 18:52:54 -07:00
75ed053a35 bump version 2018-06-11 12:32:31 -07:00
cfedf5fff4 Merge branch '0.6' of github.com:actix/actix-web into 0.6 2018-06-11 12:32:05 -07:00
be73a36339 use custom resolver 2018-06-11 12:28:43 -07:00
1ad8ba2604 Fix docs.rs build 2018-06-11 12:25:20 -07:00
6848a12095 prepare 0.6.12 release 2018-06-09 01:22:19 +02:00
4797298706 Allow to use custom resolver for ClientConnector 2018-06-08 16:10:47 -07:00
5eaf4cbefd update changelog 2018-06-08 08:42:10 -07:00
7f1844e541 fix doc test 2018-06-07 20:22:50 -07:00
6c7ac7fc22 update changelog 2018-06-07 20:07:58 -07:00
42f9e1034b add Host predicate 2018-06-07 20:05:45 -07:00
e3cd0fdd13 add application filters 2018-06-07 20:05:26 -07:00
40ff550460 update changelog 2018-06-07 20:04:40 -07:00
7119340d44 Added improved failure interoperability with downcasting (#285)
Deprecates Error::cause and introduces failure interoperability functions and downcasting.
2018-06-07 20:03:10 -07:00
e140bc3906 prep release 2018-06-05 09:44:29 -07:00
fdc08d365d metadata for docs.rs 2018-06-05 08:59:30 -07:00
8f1b88e39e update changelog 2018-06-05 08:53:27 -07:00
6a40a0a466 fix multipart boundary parsing #282 2018-06-05 08:52:46 -07:00
89fc6b6ac9 changelog 2018-06-05 07:42:52 -07:00
afa67b838a CORS: Do not validate Origin header on non-OPTION requests #271 2018-06-05 07:41:13 -07:00
f7b7d282bf Middleware::response is not invoked if error result was returned by another Middleware::start #255 2018-06-04 13:57:54 -07:00
09780ea9f3 changelog updates 2018-06-02 15:03:34 -07:00
a7dab950f3 Support chunked encoding for UrlEncoded body #262 2018-06-02 15:02:42 -07:00
ec0737e392 fix doc test 2018-06-02 13:48:37 -07:00
d664993d56 remove debug prints 2018-06-02 11:58:11 -07:00
a9c6c57a67 remove debug print 2018-06-02 11:55:27 -07:00
08e7374eee update changelog 2018-06-02 11:46:53 -07:00
42da1448fb fixed HttpRequest::url_for for a named route with no variables #265 2018-06-02 11:46:02 -07:00
9f9e0b98ad change homepage link 2018-05-24 08:55:10 -07:00
556646aaec update changelog 2018-05-24 07:56:51 -07:00
174fb0b5f4 Merge pull request #239 from max-frai/master
Add ability to set encoding for exact NamedFile.
2018-05-24 07:46:53 -07:00
836706653b Merge branch 'master' into master 2018-05-24 07:46:46 -07:00
17f1a2b92a more scope tests 2018-05-23 14:11:01 -07:00
3b08b16c11 bump version 2018-05-23 13:21:54 -07:00
68eb2f26c9 Allow to use path without traling slashes for scope registration #241 2018-05-23 13:21:29 -07:00
72757887c9 update doc links 2018-05-23 11:20:12 -07:00
eb5dbd43ae update changelog 2018-05-23 10:37:17 -07:00
1f1dfac3f9 Merge pull request #240 from ivanovaleksey/patch-2
Fix TestServer::post
2018-05-23 09:50:40 -07:00
2479b14aba Fix TestServer::post 2018-05-23 19:07:42 +03:00
ac24703512 Add ability to set encoding for exact NamedFile. 2018-05-23 09:12:23 +03:00
db0091ba6f disable server test for windows 2018-05-21 21:01:52 -07:00
2159158c30 Fix streaming response with body compression 2018-05-21 20:50:10 -07:00
76d790425f bump version 2018-05-21 19:07:56 -07:00
90968d4333 Drop connection if request's payload is not fulle consumed #236 2018-05-21 18:54:17 -07:00
577a509875 increase delay 2018-05-21 16:12:33 -07:00
a9728abfc8 run coverage report on 1.24 2018-05-20 21:10:50 -07:00
14d1b8e2b6 prepare release 2018-05-20 21:09:54 -07:00
285c73e95e Re-use tcp listener on pause/resume 2018-05-20 20:47:20 -07:00
483db7028c expose low level data 2018-05-20 20:37:19 -07:00
082ff46041 Fix scope resource path extractor #234 2018-05-20 17:04:23 -07:00
f32e8f22c8 Merge pull request #231 from qrvaelet/ranges
NamedFile: range upgrade
2018-05-20 09:18:46 -07:00
766dde7c42 Merge branch 'master' into ranges 2018-05-20 08:51:07 -07:00
b68687044e range header syntax fix, change range to content-range in responses, enabled accept ranges, tests for content-range, content-length, and range status code 2018-05-20 17:40:36 +02:00
c9e84e9dd3 Merge pull request #233 from sindreij/patch-1
Fix some typos in server/srv.rs
2018-05-20 06:19:53 -07:00
0126ac46fc Fix some typos in server/srv.rs
Hello! This looks like a great library, thanks for creating it! While reading through the documentation I found a few typos.
2018-05-20 14:43:26 +02:00
9b7ea836d0 bump version 2018-05-17 18:34:09 -07:00
537b420d35 Fix compilation with --no-default-features 2018-05-17 18:33:48 -07:00
16906c5951 clippy warnings 2018-05-17 12:23:37 -07:00
45e9aaa462 rustfmt 0.7 2018-05-17 12:20:20 -07:00
564cc15c04 update changes 2018-05-17 12:20:04 -07:00
8fd18d56a5 Merge pull request #227 from qrvaelet/ranges
NamedFile: added basic ranges header support, added content-length support
2018-05-17 12:18:10 -07:00
a5692d4ecf Merge branch 'master' into ranges 2018-05-17 11:16:08 -07:00
2d83f79433 NamedFile: added ranges support, content-length support 2018-05-17 20:09:41 +02:00
f3ece74406 better error handling 2018-05-17 10:58:08 -07:00
8de1f60347 add session extractor doc api 2018-05-16 21:05:59 -07:00
b4252f8fd1 implement extractor for Session 2018-05-16 21:02:51 -07:00
fe2b50a9ef update changelog 2018-05-16 11:02:50 -07:00
d8ae8c3821 Merge pull request #225 from mitsuhiko/feature/addrs-with-scheme
Support returning addresses plus scheme from the server
2018-05-16 11:02:15 -07:00
c9a026fabb Merge branch 'master' into feature/addrs-with-scheme 2018-05-16 11:01:45 -07:00
64eca1546e Merge pull request #224 from mitsuhiko/feature/listen-tls
Add support for listen_tls/listen_ssl
2018-05-16 11:01:28 -07:00
b19fe98ff4 Merge branch 'master' into feature/listen-tls 2018-05-16 11:01:21 -07:00
b393ddf879 fix panic during middleware execution #226 2018-05-16 11:00:29 -07:00
7bb7d85c1d Added support for returning addresses plus scheme from the server 2018-05-16 16:17:27 +02:00
6e976153e7 Add support for listen_tls/listen_ssl 2018-05-16 15:20:47 +02:00
03e758cee4 bump version 2018-05-15 19:08:34 -07:00
0d36b8f826 fix 1.24 compatibility 2018-05-15 19:07:43 -07:00
f82fa08d72 various optimizations 2018-05-15 16:49:03 -07:00
d6787e6c56 prepare release 2018-05-15 10:20:32 -07:00
b9d870645f store cookies in extensions 2018-05-15 10:09:48 -07:00
ef89430f9b undeprecate query() and store query in extensions 2018-05-15 09:53:58 -07:00
953a0d4e4a add test case for #222 2018-05-15 09:29:59 -07:00
5ea2d68438 h1 decoder blocks on error #222 2018-05-15 07:55:36 -07:00
d65a03f6ac use latest nightly for appveyor 2018-05-13 08:43:09 -07:00
b588b2bf5c Merge pull request #221 from skorgu/patch-1
Include mention of http client in README.md
2018-05-11 21:56:46 -07:00
d455e2cd13 Merge branch 'master' into patch-1 2018-05-11 21:56:35 -07:00
9306631d6e Fix segfault in ServerSettings::get_response_builder() 2018-05-11 21:19:48 -07:00
f735da504b Include mention of http client in README.md 2018-05-11 20:36:54 -04:00
487a713ca0 update doc string 2018-05-11 15:01:15 -07:00
70 changed files with 2532 additions and 1307 deletions

View File

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

View File

@ -31,12 +31,12 @@ before_script:
script: script:
- | - |
if [[ "$TRAVIS_RUST_VERSION" != "beta" ]]; then if [[ "$TRAVIS_RUST_VERSION" != "1.24.0" ]]; then
cargo clean cargo clean
cargo test --features="alpn,tls" -- --nocapture cargo test --features="alpn,tls" -- --nocapture
fi fi
- | - |
if [[ "$TRAVIS_RUST_VERSION" == "beta" ]]; then if [[ "$TRAVIS_RUST_VERSION" == "1.24.0" ]]; then
bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh) bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh)
USE_SKEPTIC=1 cargo tarpaulin --out Xml --no-count USE_SKEPTIC=1 cargo tarpaulin --out Xml --no-count
bash <(curl -s https://codecov.io/bash) bash <(curl -s https://codecov.io/bash)

View File

@ -1,5 +1,102 @@
# Changes # Changes
## [0.6.13] - 2018-06-13
### Fixed
* http/2 end-of-frame is not set if body is empty bytes #307
* InternalError can trigger memory unsafety #301
* Fix docs.rs build
## [0.6.12] - 2018-06-08
### Added
* Add `Host` filter #287
* Allow to filter applications
* Improved failure interoperability with downcasting #285
* Allow to use custom resolver for `ClientConnector`
### Deprecated
* `Error::cause()` and introduces failure interoperability functions and downcasting.
## [0.6.11] - 2018-06-05
### Fixed
* Support chunked encoding for UrlEncoded body #262
* `HttpRequest::url_for()` for a named route with no variables segments #265
* `Middleware::response()` is not invoked if error result was returned by another `Middleware::start()` #255
* CORS: Do not validate Origin header on non-OPTION requests #271
* Fix multipart upload "Incomplete" error #282
## [0.6.10] - 2018-05-24
### Added
* Allow to use path without traling slashes for scope registration #241
* Allow to set encoding for exact NamedFile #239
### Fixed
* `TestServer::post()` actually sends `GET` request #240
## 0.6.9 (2018-05-22)
* Drop connection if request's payload is not fully consumed #236
* Fix streaming response with body compression
## 0.6.8 (2018-05-20)
* Fix scope resource path extractor #234
* Re-use tcp listener on pause/resume
## 0.6.7 (2018-05-17)
* Fix compilation with --no-default-features
## 0.6.6 (2018-05-17)
* Panic during middleware execution #226
* Add support for listen_tls/listen_ssl #224
* Implement extractor for `Session`
* Ranges header support for NamedFile #60
## 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) ## 0.6.3 (2018-05-10)
* Add `Router::with_async()` method for async handler registration. * Add `Router::with_async()` method for async handler registration.

View File

@ -1,11 +1,11 @@
[package] [package]
name = "actix-web" name = "actix-web"
version = "0.6.3" version = "0.6.13"
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"
keywords = ["http", "web", "framework", "async", "futures"] keywords = ["http", "web", "framework", "async", "futures"]
homepage = "https://github.com/actix/actix-web" homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web.git" repository = "https://github.com/actix/actix-web.git"
documentation = "https://docs.rs/actix-web/" documentation = "https://docs.rs/actix-web/"
categories = ["network-programming", "asynchronous", categories = ["network-programming", "asynchronous",
@ -46,8 +46,11 @@ flate2-c = ["flate2/miniz-sys"]
# rust backend for flate2 crate # rust backend for flate2 crate
flate2-rust = ["flate2/rust_backend"] flate2-rust = ["flate2/rust_backend"]
[package.metadata.docs.rs]
features = ["tls", "alpn", "session", "brotli", "flate2-c"]
[dependencies] [dependencies]
actix = "^0.5.5" actix = "^0.5.8"
base64 = "0.9" base64 = "0.9"
bitflags = "1.0" bitflags = "1.0"

View File

@ -32,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<..> {

View File

@ -2,12 +2,12 @@
Actix web is a simple, pragmatic and extremely fast web framework for Rust. Actix web is a simple, pragmatic and extremely fast web framework for Rust.
* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/book/actix-web/sec-12-http2.html) protocols * Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/docs/http2/) protocols
* Streaming and pipelining * Streaming and pipelining
* Keep-alive and slow requests handling * Keep-alive and slow requests handling
* Client/server [WebSockets](https://actix.rs/book/actix-web/sec-11-websockets.html) support * Client/server [WebSockets](https://actix.rs/docs/websockets/) support
* Transparent content compression/decompression (br, gzip, deflate) * Transparent content compression/decompression (br, gzip, deflate)
* Configurable [request routing](https://actix.rs/book/actix-web/sec-6-url-dispatch.html) * Configurable [request routing](https://actix.rs/docs/url-dispatch/)
* Graceful server shutdown * Graceful server shutdown
* Multipart streams * Multipart streams
* Static assets * Static assets
@ -18,11 +18,12 @@ 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://actix.rs/actix-web/actix_web/client/index.html)
* 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
* [User Guide](https://actix.rs/book/actix-web/) * [User Guide](https://actix.rs/docs/)
* [API Documentation (Development)](https://actix.rs/actix-web/actix_web/) * [API Documentation (Development)](https://actix.rs/actix-web/actix_web/)
* [API Documentation (Releases)](https://docs.rs/actix-web/) * [API Documentation (Releases)](https://docs.rs/actix-web/)
* [Chat on gitter](https://gitter.im/actix/actix) * [Chat on gitter](https://gitter.im/actix/actix)
@ -33,9 +34,9 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust.
```rust ```rust
extern crate actix_web; extern crate actix_web;
use actix_web::{http, server, App, Path}; use actix_web::{http, server, App, Path, Responder};
fn index(info: Path<(u32, String)>) -> String { fn index(info: Path<(u32, String)>) -> impl Responder {
format!("Hello {}! id:{}", info.1, info.0) format!("Hello {}! id:{}", info.1, info.0)
} }

View File

@ -22,6 +22,7 @@ pub struct HttpApplication<S = ()> {
prefix_len: usize, prefix_len: usize,
router: Router, router: Router,
inner: Rc<UnsafeCell<Inner<S>>>, inner: Rc<UnsafeCell<Inner<S>>>,
filters: Option<Vec<Box<Predicate<S>>>>,
middlewares: Rc<Vec<Box<Middleware<S>>>>, middlewares: Rc<Vec<Box<Middleware<S>>>>,
} }
@ -107,16 +108,12 @@ impl<S: 'static> HttpApplication<S> {
} }
} }
let prefix_len = inner.prefix + prefix_len - 1; let prefix_len = inner.prefix + prefix_len;
let path: &'static str = let path: &'static str =
unsafe { &*(&req.path()[prefix_len..] as *const _) }; unsafe { &*(&req.path()[prefix_len..] as *const _) };
req.set_prefix_len(prefix_len as u16); req.set_prefix_len(prefix_len as u16);
if path.is_empty() { req.match_info_mut().set("tail", path);
req.match_info_mut().set("tail", "/");
} else {
req.match_info_mut().set("tail", path);
}
return HandlerType::Handler(idx); return HandlerType::Handler(idx);
} }
} }
@ -147,11 +144,21 @@ impl<S: 'static> HttpHandler for HttpApplication<S> {
|| path.split_at(self.prefix_len).1.starts_with('/')) || path.split_at(self.prefix_len).1.starts_with('/'))
}; };
if m { if m {
let mut req = req.with_state(Rc::clone(&self.state), self.router.clone()); let mut req2 =
let tp = self.get_handler(&mut req); req.clone_with_state(Rc::clone(&self.state), self.router.clone());
if let Some(ref filters) = self.filters {
for filter in filters {
if !filter.check(&mut req2) {
return Err(req);
}
}
}
let tp = self.get_handler(&mut req2);
let inner = Rc::clone(&self.inner); let inner = Rc::clone(&self.inner);
Ok(Box::new(Pipeline::new( Ok(Box::new(Pipeline::new(
req, req2,
Rc::clone(&self.middlewares), Rc::clone(&self.middlewares),
inner, inner,
tp, tp,
@ -172,6 +179,7 @@ struct ApplicationParts<S> {
external: HashMap<String, Resource>, external: HashMap<String, Resource>,
encoding: ContentEncoding, encoding: ContentEncoding,
middlewares: Vec<Box<Middleware<S>>>, middlewares: Vec<Box<Middleware<S>>>,
filters: Vec<Box<Predicate<S>>>,
} }
/// Structure that follows the builder pattern for building application /// Structure that follows the builder pattern for building application
@ -194,6 +202,7 @@ impl App<()> {
handlers: Vec::new(), handlers: Vec::new(),
external: HashMap::new(), external: HashMap::new(),
encoding: ContentEncoding::Auto, encoding: ContentEncoding::Auto,
filters: Vec::new(),
middlewares: Vec::new(), middlewares: Vec::new(),
}), }),
} }
@ -233,6 +242,7 @@ where
handlers: Vec::new(), handlers: Vec::new(),
external: HashMap::new(), external: HashMap::new(),
middlewares: Vec::new(), middlewares: Vec::new(),
filters: Vec::new(),
encoding: ContentEncoding::Auto, encoding: ContentEncoding::Auto,
}), }),
} }
@ -271,8 +281,8 @@ where
/// let app = App::new() /// let app = App::new()
/// .prefix("/app") /// .prefix("/app")
/// .resource("/test", |r| { /// .resource("/test", |r| {
/// r.get().f(|_| HttpResponse::Ok()); /// r.get().f(|_| HttpResponse::Ok());
/// r.head().f(|_| HttpResponse::MethodNotAllowed()); /// r.head().f(|_| HttpResponse::MethodNotAllowed());
/// }) /// })
/// .finish(); /// .finish();
/// } /// }
@ -289,6 +299,26 @@ where
self self
} }
/// Add match predicate to application.
///
/// ```rust
/// # extern crate actix_web;
/// # use actix_web::*;
/// # fn main() {
/// App::new()
/// .filter(pred::Host("www.rust-lang.org"))
/// .resource("/path", |r| r.f(|_| HttpResponse::Ok()))
/// # .finish();
/// # }
/// ```
pub fn filter<T: Predicate<S> + 'static>(mut self, p: T) -> App<S> {
{
let parts = self.parts.as_mut().expect("Use after finish");
parts.filters.push(Box::new(p));
}
self
}
/// Configure route for a specific path. /// Configure route for a specific path.
/// ///
/// This is a simplified version of the `App::resource()` method. /// This is a simplified version of the `App::resource()` method.
@ -304,10 +334,12 @@ where
/// ///
/// fn main() { /// fn main() {
/// let app = App::new() /// let app = App::new()
/// .route("/test", http::Method::GET, /// .route("/test", http::Method::GET, |_: HttpRequest| {
/// |_: HttpRequest| HttpResponse::Ok()) /// HttpResponse::Ok()
/// .route("/test", http::Method::POST, /// })
/// |_: HttpRequest| HttpResponse::MethodNotAllowed()); /// .route("/test", http::Method::POST, |_: HttpRequest| {
/// HttpResponse::MethodNotAllowed()
/// });
/// } /// }
/// ``` /// ```
pub fn route<T, F, R>(mut self, path: &str, method: Method, f: F) -> App<S> pub fn route<T, F, R>(mut self, path: &str, method: Method, f: F) -> App<S>
@ -349,12 +381,12 @@ where
/// use actix_web::{http, App, HttpRequest, HttpResponse}; /// use actix_web::{http, App, HttpRequest, HttpResponse};
/// ///
/// fn main() { /// fn main() {
/// let app = App::new() /// let app = App::new().scope("/{project_id}", |scope| {
/// .scope("/{project_id}", |scope| { /// scope
/// scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok())) /// .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()))
/// }); /// });
/// } /// }
/// ``` /// ```
/// ///
@ -369,14 +401,6 @@ where
{ {
{ {
let mut scope = Box::new(f(Scope::new())); let mut scope = Box::new(f(Scope::new()));
let mut path = path.trim().trim_right_matches('/').to_owned();
if !path.is_empty() && !path.starts_with('/') {
path.insert(0, '/')
}
if !path.ends_with('/') {
path.push('/');
}
let parts = self.parts.as_mut().expect("Use after finish"); let parts = self.parts.as_mut().expect("Use after finish");
let filters = scope.take_filters(); let filters = scope.take_filters();
@ -414,11 +438,10 @@ where
/// use actix_web::{http, App, HttpResponse}; /// use actix_web::{http, App, HttpResponse};
/// ///
/// fn main() { /// fn main() {
/// let app = App::new() /// let app = App::new().resource("/users/{userid}/{friend}", |r| {
/// .resource("/users/{userid}/{friend}", |r| { /// r.get().f(|_| HttpResponse::Ok());
/// r.get().f(|_| HttpResponse::Ok()); /// r.head().f(|_| HttpResponse::MethodNotAllowed());
/// r.head().f(|_| HttpResponse::MethodNotAllowed()); /// });
/// });
/// } /// }
/// ``` /// ```
pub fn resource<F, R>(mut self, path: &str, f: F) -> App<S> pub fn resource<F, R>(mut self, path: &str, f: F) -> App<S>
@ -481,9 +504,9 @@ where
/// use actix_web::{App, HttpRequest, HttpResponse, Result}; /// use actix_web::{App, HttpRequest, HttpResponse, Result};
/// ///
/// fn index(mut req: HttpRequest) -> Result<HttpResponse> { /// fn index(mut req: HttpRequest) -> Result<HttpResponse> {
/// let url = req.url_for("youtube", &["oHg5SJYRHA0"])?; /// let url = req.url_for("youtube", &["oHg5SJYRHA0"])?;
/// assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0"); /// assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0");
/// Ok(HttpResponse::Ok().into()) /// Ok(HttpResponse::Ok().into())
/// } /// }
/// ///
/// fn main() { /// fn main() {
@ -526,13 +549,11 @@ where
/// use actix_web::{http, App, HttpRequest, HttpResponse}; /// use actix_web::{http, App, HttpRequest, HttpResponse};
/// ///
/// fn main() { /// fn main() {
/// let app = App::new() /// let app = App::new().handler("/app", |req: HttpRequest| match *req.method() {
/// .handler("/app", |req: HttpRequest| { /// http::Method::GET => HttpResponse::Ok(),
/// match *req.method() { /// http::Method::POST => HttpResponse::MethodNotAllowed(),
/// http::Method::GET => HttpResponse::Ok(), /// _ => HttpResponse::NotFound(),
/// http::Method::POST => HttpResponse::MethodNotAllowed(), /// });
/// _ => HttpResponse::NotFound(),
/// }});
/// } /// }
/// ``` /// ```
pub fn handler<H: Handler<S>>(mut self, path: &str, handler: H) -> App<S> { pub fn handler<H: Handler<S>>(mut self, path: &str, handler: H) -> App<S> {
@ -573,15 +594,14 @@ where
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// use actix_web::{App, HttpResponse, fs, middleware}; /// use actix_web::{fs, middleware, App, HttpResponse};
/// ///
/// // this function could be located in different module /// // this function could be located in different module
/// fn config(app: App) -> App { /// fn config(app: App) -> App {
/// app /// app.resource("/test", |r| {
/// .resource("/test", |r| { /// r.get().f(|_| HttpResponse::Ok());
/// r.get().f(|_| HttpResponse::Ok()); /// r.head().f(|_| HttpResponse::MethodNotAllowed());
/// r.head().f(|_| HttpResponse::MethodNotAllowed()); /// })
/// })
/// } /// }
/// ///
/// fn main() { /// fn main() {
@ -622,6 +642,11 @@ where
handlers: parts.handlers, handlers: parts.handlers,
resources, resources,
})); }));
let filters = if parts.filters.is_empty() {
None
} else {
Some(parts.filters)
};
HttpApplication { HttpApplication {
state: Rc::new(parts.state), state: Rc::new(parts.state),
@ -630,6 +655,7 @@ where
prefix, prefix,
prefix_len, prefix_len,
inner, inner,
filters,
} }
} }
@ -648,19 +674,22 @@ where
/// struct State2; /// struct State2;
/// ///
/// fn main() { /// fn main() {
/// # thread::spawn(|| { /// # thread::spawn(|| {
/// server::new(|| { vec![ /// server::new(|| {
/// App::with_state(State1) /// vec![
/// .prefix("/app1") /// App::with_state(State1)
/// .resource("/", |r| r.f(|r| HttpResponse::Ok())) /// .prefix("/app1")
/// .boxed(), /// .resource("/", |r| r.f(|r| HttpResponse::Ok()))
/// App::with_state(State2) /// .boxed(),
/// .prefix("/app2") /// App::with_state(State2)
/// .resource("/", |r| r.f(|r| HttpResponse::Ok())) /// .prefix("/app2")
/// .boxed() ]}) /// .resource("/", |r| r.f(|r| HttpResponse::Ok()))
/// .bind("127.0.0.1:8080").unwrap() /// .boxed(),
/// ]
/// }).bind("127.0.0.1:8080")
/// .unwrap()
/// .run() /// .run()
/// # }); /// # });
/// } /// }
/// ``` /// ```
pub fn boxed(mut self) -> Box<HttpHandler> { pub fn boxed(mut self) -> Box<HttpHandler> {
@ -711,7 +740,8 @@ mod tests {
use http::StatusCode; use http::StatusCode;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
use test::TestRequest; use pred;
use test::{TestRequest, TestServer};
#[test] #[test]
fn test_default_resource() { fn test_default_resource() {
@ -780,9 +810,7 @@ mod tests {
#[test] #[test]
fn test_handler() { fn test_handler() {
let mut app = App::new() let mut app = App::new().handler("/test", |_| HttpResponse::Ok()).finish();
.handler("/test", |_| HttpResponse::Ok())
.finish();
let req = TestRequest::with_uri("/test").finish(); let req = TestRequest::with_uri("/test").finish();
let resp = app.run(req); let resp = app.run(req);
@ -807,9 +835,7 @@ mod tests {
#[test] #[test]
fn test_handler2() { fn test_handler2() {
let mut app = App::new() let mut app = App::new().handler("test", |_| HttpResponse::Ok()).finish();
.handler("test", |_| HttpResponse::Ok())
.finish();
let req = TestRequest::with_uri("/test").finish(); let req = TestRequest::with_uri("/test").finish();
let resp = app.run(req); let resp = app.run(req);
@ -863,29 +889,21 @@ mod tests {
#[test] #[test]
fn test_route() { fn test_route() {
let mut app = App::new() let mut app = App::new()
.route("/test", Method::GET, |_: HttpRequest| { .route("/test", Method::GET, |_: HttpRequest| HttpResponse::Ok())
HttpResponse::Ok()
})
.route("/test", Method::POST, |_: HttpRequest| { .route("/test", Method::POST, |_: HttpRequest| {
HttpResponse::Created() HttpResponse::Created()
}) })
.finish(); .finish();
let req = TestRequest::with_uri("/test") let req = TestRequest::with_uri("/test").method(Method::GET).finish();
.method(Method::GET)
.finish();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK); assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/test") let req = TestRequest::with_uri("/test").method(Method::POST).finish();
.method(Method::POST)
.finish();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::CREATED); assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
let req = TestRequest::with_uri("/test") let req = TestRequest::with_uri("/test").method(Method::HEAD).finish();
.method(Method::HEAD)
.finish();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
} }
@ -922,4 +940,21 @@ mod tests {
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
} }
#[test]
fn test_filter() {
let mut srv = TestServer::with_factory(|| {
App::new()
.filter(pred::Get())
.handler("/test", |_| HttpResponse::Ok())
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::OK);
let request = srv.post().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::NOT_FOUND);
}
} }

View File

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

View File

@ -8,8 +8,10 @@ use std::{fmt, io, mem, time};
use actix::actors::{Connect as ResolveConnect, Connector, ConnectorError}; use actix::actors::{Connect as ResolveConnect, Connector, ConnectorError};
use actix::fut::WrapFuture; use actix::fut::WrapFuture;
use actix::registry::ArbiterService; use actix::registry::ArbiterService;
use actix::{fut, Actor, ActorFuture, ActorResponse, Arbiter, AsyncContext, Context, use actix::{
ContextFutureSpawner, Handler, Message, Recipient, Supervised, Syn}; fut, Actor, ActorFuture, ActorResponse, Addr, Arbiter, AsyncContext, Context,
ContextFutureSpawner, Handler, Message, Recipient, Supervised, Syn, Unsync,
};
use futures::task::{current as current_task, Task}; use futures::task::{current as current_task, Task};
use futures::unsync::oneshot; use futures::unsync::oneshot;
@ -179,6 +181,7 @@ pub struct ClientConnector {
pool: Rc<Pool>, pool: Rc<Pool>,
pool_modified: Rc<Cell<bool>>, pool_modified: Rc<Cell<bool>>,
resolver: Addr<Unsync, Connector>,
conn_lifetime: Duration, conn_lifetime: Duration,
conn_keep_alive: Duration, conn_keep_alive: Duration,
limit: usize, limit: usize,
@ -223,6 +226,7 @@ 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(),
resolver: Connector::from_registry(),
conn_lifetime: Duration::from_secs(75), conn_lifetime: Duration::from_secs(75),
conn_keep_alive: Duration::from_secs(15), conn_keep_alive: Duration::from_secs(15),
limit: 100, limit: 100,
@ -243,6 +247,7 @@ 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,
resolver: Connector::from_registry(),
conn_lifetime: Duration::from_secs(75), conn_lifetime: Duration::from_secs(75),
conn_keep_alive: Duration::from_secs(15), conn_keep_alive: Duration::from_secs(15),
limit: 100, limit: 100,
@ -275,9 +280,9 @@ impl ClientConnector {
/// # use std::io::Write; /// # use std::io::Write;
/// extern crate openssl; /// extern crate openssl;
/// use actix::prelude::*; /// use actix::prelude::*;
/// use actix_web::client::{Connect, ClientConnector}; /// use actix_web::client::{ClientConnector, Connect};
/// ///
/// use openssl::ssl::{SslMethod, SslConnector}; /// use openssl::ssl::{SslConnector, SslMethod};
/// ///
/// fn main() { /// fn main() {
/// let sys = System::new("test"); /// let sys = System::new("test");
@ -310,6 +315,7 @@ impl 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,
resolver: Connector::from_registry(),
conn_lifetime: Duration::from_secs(75), conn_lifetime: Duration::from_secs(75),
conn_keep_alive: Duration::from_secs(15), conn_keep_alive: Duration::from_secs(15),
limit: 100, limit: 100,
@ -369,6 +375,12 @@ impl ClientConnector {
self self
} }
/// Use custom resolver actor
pub fn resolver(mut self, addr: Addr<Unsync, Connector>) -> Self {
self.resolver = addr;
self
}
fn acquire(&mut self, key: &Key) -> Acquire { fn acquire(&mut self, key: &Key) -> Acquire {
// check limits // check limits
if self.limit > 0 { if self.limit > 0 {
@ -429,8 +441,7 @@ impl ClientConnector {
} else { } else {
0 0
}; };
self.acquired_per_host self.acquired_per_host.insert(key.clone(), per_host + 1);
.insert(key.clone(), per_host + 1);
} }
fn release_key(&mut self, key: &Key) { fn release_key(&mut self, key: &Key) {
@ -441,8 +452,7 @@ impl ClientConnector {
return; return;
}; };
if per_host > 1 { if per_host > 1 {
self.acquired_per_host self.acquired_per_host.insert(key.clone(), per_host - 1);
.insert(key.clone(), per_host - 1);
} else { } else {
self.acquired_per_host.remove(key); self.acquired_per_host.remove(key);
} }
@ -518,9 +528,7 @@ impl ClientConnector {
fn collect_periodic(&mut self, ctx: &mut Context<Self>) { fn collect_periodic(&mut self, ctx: &mut Context<Self>) {
self.collect(true); self.collect(true);
// re-schedule next collect period // re-schedule next collect period
ctx.run_later(Duration::from_secs(1), |act, ctx| { ctx.run_later(Duration::from_secs(1), |act, ctx| act.collect_periodic(ctx));
act.collect_periodic(ctx)
});
// send stats // send stats
let stats = mem::replace(&mut self.stats, ClientConnectorStats::default()); let stats = mem::replace(&mut self.stats, ClientConnectorStats::default());
@ -707,7 +715,7 @@ impl Handler<Connect> for ClientConnector {
{ {
ActorResponse::async( ActorResponse::async(
Connector::from_registry() self.resolver
.send( .send(
ResolveConnect::host_and_port(&conn.0.host, port) ResolveConnect::host_and_port(&conn.0.host, port)
.timeout(conn_timeout), .timeout(conn_timeout),
@ -857,7 +865,7 @@ impl fut::ActorFuture for Maintenance {
let conn = AcquiredConn(key.clone(), Some(Rc::clone(&act.pool))); let conn = AcquiredConn(key.clone(), Some(Rc::clone(&act.pool)));
fut::WrapFuture::<ClientConnector>::actfuture( fut::WrapFuture::<ClientConnector>::actfuture(
Connector::from_registry().send( act.resolver.send(
ResolveConnect::host_and_port(&conn.0.host, conn.0.port) ResolveConnect::host_and_port(&conn.0.host, conn.0.port)
.timeout(waiter.conn_timeout), .timeout(waiter.conn_timeout),
), ),
@ -1107,10 +1115,7 @@ impl Pool {
if self.to_close.borrow().is_empty() { if self.to_close.borrow().is_empty() {
None None
} else { } else {
Some(mem::replace( Some(mem::replace(&mut *self.to_close.borrow_mut(), Vec::new()))
&mut *self.to_close.borrow_mut(),
Vec::new(),
))
} }
} }
@ -1118,10 +1123,7 @@ impl Pool {
if self.to_release.borrow().is_empty() { if self.to_release.borrow().is_empty() {
None None
} else { } else {
Some(mem::replace( Some(mem::replace(&mut *self.to_release.borrow_mut(), Vec::new()))
&mut *self.to_release.borrow_mut(),
Vec::new(),
))
} }
} }

View File

@ -33,8 +33,10 @@ mod request;
mod response; mod response;
mod writer; mod writer;
pub use self::connector::{ClientConnector, ClientConnectorError, ClientConnectorStats, pub use self::connector::{
Connect, Connection, Pause, Resume}; ClientConnector, ClientConnectorError, ClientConnectorStats, Connect, Connection,
Pause, Resume,
};
pub(crate) use self::parser::{HttpResponseParser, HttpResponseParserError}; pub(crate) use self::parser::{HttpResponseParser, HttpResponseParserError};
pub use self::pipeline::{SendRequest, SendRequestError}; pub use self::pipeline::{SendRequest, SendRequestError};
pub use self::request::{ClientRequest, ClientRequestBuilder}; pub use self::request::{ClientRequest, ClientRequestBuilder};

View File

@ -270,7 +270,8 @@ impl Pipeline {
#[inline] #[inline]
fn parse(&mut self) -> Poll<ClientResponse, HttpResponseParserError> { fn parse(&mut self) -> Poll<ClientResponse, HttpResponseParserError> {
if let Some(ref mut conn) = self.conn { if let Some(ref mut conn) = self.conn {
match self.parser match self
.parser
.as_mut() .as_mut()
.unwrap() .unwrap()
.parse(conn, &mut self.parser_buf) .parse(conn, &mut self.parser_buf)
@ -311,7 +312,8 @@ impl Pipeline {
let mut need_run = false; let mut need_run = false;
// need write? // need write?
match self.poll_write() match self
.poll_write()
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e)))? .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e)))?
{ {
Async::NotReady => need_run = true, Async::NotReady => need_run = true,
@ -325,7 +327,8 @@ impl Pipeline {
// need read? // need read?
if self.parser.is_some() { if self.parser.is_some() {
loop { loop {
match self.parser match self
.parser
.as_mut() .as_mut()
.unwrap() .unwrap()
.parse_payload(conn, &mut self.parser_buf)? .parse_payload(conn, &mut self.parser_buf)?
@ -469,7 +472,8 @@ impl Pipeline {
} }
// flush io but only if we need to // flush io but only if we need to
match self.writer match self
.writer
.poll_completed(self.conn.as_mut().unwrap(), false) .poll_completed(self.conn.as_mut().unwrap(), false)
{ {
Ok(Async::Ready(_)) => { Ok(Async::Ready(_)) => {

View File

@ -499,10 +499,7 @@ impl ClientRequestBuilder {
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
} }
@ -610,9 +607,7 @@ impl ClientRequestBuilder {
} }
} }
let mut request = self.request let mut request = self.request.take().expect("cannot reuse request builder");
.take()
.expect("cannot reuse request builder");
// set cookies // set cookies
if let Some(ref mut jar) = self.cookies { if let Some(ref mut jar) = self.cookies {
@ -657,9 +652,7 @@ impl ClientRequestBuilder {
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 an empty body and generate `ClientRequest` /// Set an empty body and generate `ClientRequest`

View File

@ -103,12 +103,7 @@ impl ClientResponse {
impl fmt::Debug for ClientResponse { impl fmt::Debug for ClientResponse {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let res = writeln!( let res = writeln!(f, "\nClientResponse {:?} {}", self.version(), self.status());
f,
"\nClientResponse {:?} {}",
self.version(),
self.status()
);
let _ = writeln!(f, " headers:"); let _ = writeln!(f, " headers:");
for (key, val) in self.headers().iter() { for (key, val) in self.headers().iter() {
let _ = writeln!(f, " {:?}: {:?}", key, val); let _ = writeln!(f, " {:?}: {:?}", key, val);
@ -138,14 +133,12 @@ mod tests {
#[test] #[test]
fn test_debug() { fn test_debug() {
let resp = ClientResponse::new(ClientMessage::default()); let resp = ClientResponse::new(ClientMessage::default());
resp.as_mut().headers.insert( resp.as_mut()
header::COOKIE, .headers
HeaderValue::from_static("cookie1=value1"), .insert(header::COOKIE, HeaderValue::from_static("cookie1=value1"));
); resp.as_mut()
resp.as_mut().headers.insert( .headers
header::COOKIE, .insert(header::COOKIE, HeaderValue::from_static("cookie2=value2"));
HeaderValue::from_static("cookie2=value2"),
);
let dbg = format!("{:?}", resp); let dbg = format!("{:?}", resp);
assert!(dbg.contains("ClientResponse")); assert!(dbg.contains("ClientResponse"));

View File

@ -12,8 +12,9 @@ use flate2::write::{DeflateEncoder, GzEncoder};
#[cfg(feature = "flate2")] #[cfg(feature = "flate2")]
use flate2::Compression; use flate2::Compression;
use futures::{Async, Poll}; use futures::{Async, Poll};
use http::header::{HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, use http::header::{
TRANSFER_ENCODING}; HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING,
};
use http::{HttpTryFrom, Version}; use http::{HttpTryFrom, Version};
use time::{self, Duration}; use time::{self, Duration};
use tokio_io::AsyncWrite; use tokio_io::AsyncWrite;
@ -253,10 +254,8 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder
} }
let mut b = BytesMut::new(); let mut b = BytesMut::new();
let _ = write!(b, "{}", bytes.len()); let _ = write!(b, "{}", bytes.len());
req.headers_mut().insert( req.headers_mut()
CONTENT_LENGTH, .insert(CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap());
HeaderValue::try_from(b.freeze()).unwrap(),
);
TransferEncoding::eof(buf) TransferEncoding::eof(buf)
} }
Body::Streaming(_) | Body::Actor(_) => { Body::Streaming(_) | Body::Actor(_) => {

View File

@ -6,8 +6,10 @@ use std::marker::PhantomData;
use actix::dev::{ContextImpl, SyncEnvelope, ToEnvelope}; use actix::dev::{ContextImpl, SyncEnvelope, ToEnvelope};
use actix::fut::ActorFuture; use actix::fut::ActorFuture;
use actix::{Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, use actix::{
SpawnHandle, Syn, Unsync}; Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle,
Syn, Unsync,
};
use body::{Binary, Body}; use body::{Binary, Body};
use error::{Error, ErrorInternalServerError}; use error::{Error, ErrorInternalServerError};
@ -80,7 +82,8 @@ where
#[doc(hidden)] #[doc(hidden)]
#[inline] #[inline]
fn waiting(&self) -> bool { fn waiting(&self) -> bool {
self.inner.waiting() || self.inner.state() == ActorState::Stopping self.inner.waiting()
|| self.inner.state() == ActorState::Stopping
|| self.inner.state() == ActorState::Stopped || self.inner.state() == ActorState::Stopped
} }
#[inline] #[inline]

View File

@ -202,7 +202,8 @@ impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> {
where where
K: de::DeserializeSeed<'de>, K: de::DeserializeSeed<'de>,
{ {
self.current = self.params self.current = self
.params
.next() .next()
.map(|&(ref k, ref v)| (k.as_ref(), v.as_ref())); .map(|&(ref k, ref v)| (k.as_ref(), v.as_ref()));
match self.current { match self.current {
@ -336,9 +337,7 @@ impl<'de> Deserializer<'de> for Value<'de> {
where where
V: Visitor<'de>, V: Visitor<'de>,
{ {
visitor.visit_enum(ValueEnum { visitor.visit_enum(ValueEnum { value: self.value })
value: self.value,
})
} }
fn deserialize_newtype_struct<V>( fn deserialize_newtype_struct<V>(
@ -372,9 +371,7 @@ impl<'de> Deserializer<'de> for Value<'de> {
where where
V: Visitor<'de>, V: Visitor<'de>,
{ {
Err(de::value::Error::custom( Err(de::value::Error::custom("unsupported type: tuple struct"))
"unsupported type: tuple struct",
))
} }
unsupported_type!(deserialize_any, "any"); unsupported_type!(deserialize_any, "any");
@ -415,10 +412,7 @@ impl<'de> de::EnumAccess<'de> for ValueEnum<'de> {
where where
V: de::DeserializeSeed<'de>, V: de::DeserializeSeed<'de>,
{ {
Ok(( Ok((seed.deserialize(Key { key: self.value })?, UnitVariant))
seed.deserialize(Key { key: self.value })?,
UnitVariant,
))
} }
} }

View File

@ -1,9 +1,9 @@
//! Error and Result module //! Error and Result module
use std::cell::RefCell;
use std::io::Error as IoError; use std::io::Error as IoError;
use std::str::Utf8Error; use std::str::Utf8Error;
use std::string::FromUtf8Error; use std::string::FromUtf8Error;
use std::{fmt, io, result}; use std::sync::Mutex;
use std::{fmt, io, mem, result};
use actix::MailboxError; use actix::MailboxError;
use cookie; use cookie;
@ -21,9 +21,10 @@ pub use url::ParseError as UrlParseError;
// re-exports // re-exports
pub use cookie::ParseError as CookieParseError; pub use cookie::ParseError as CookieParseError;
use body::Body;
use handler::Responder; use handler::Responder;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::{HttpResponse, InnerHttpResponse};
/// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) /// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html)
/// for actix web operations /// for actix web operations
@ -33,21 +34,44 @@ use httpresponse::HttpResponse;
/// `Result`. /// `Result`.
pub type Result<T, E = Error> = result::Result<T, E>; pub type Result<T, E = Error> = result::Result<T, E>;
/// General purpose actix web error /// General purpose actix web error.
///
/// An actix web error is used to carry errors from `failure` or `std::error`
/// through actix in a convenient way. It can be created through through
/// converting errors with `into()`.
///
/// Whenever it is created from an external object a response error is created
/// for it that can be used to create an http response from it this means that
/// if you have access to an actix `Error` you can always get a
/// `ResponseError` reference from it.
pub struct Error { pub struct Error {
cause: Box<ResponseError>, cause: Box<ResponseError>,
backtrace: Option<Backtrace>, backtrace: Option<Backtrace>,
} }
impl Error { impl Error {
/// Returns a reference to the underlying cause of this Error. /// Deprecated way to reference the underlying response error.
// this should return &Fail but needs this https://github.com/rust-lang/rust/issues/5665 #[deprecated(
since = "0.6.0", note = "please use `Error::as_response_error()` instead"
)]
pub fn cause(&self) -> &ResponseError { pub fn cause(&self) -> &ResponseError {
self.cause.as_ref() self.cause.as_ref()
} }
/// Returns a reference to the underlying cause of this `Error` as `Fail`
pub fn as_fail(&self) -> &Fail {
self.cause.as_fail()
}
/// Returns the reference to the underlying `ResponseError`.
pub fn as_response_error(&self) -> &ResponseError {
self.cause.as_ref()
}
/// Returns a reference to the Backtrace carried by this error, if it /// Returns a reference to the Backtrace carried by this error, if it
/// carries one. /// carries one.
///
/// This uses the same `Backtrace` type that `failure` uses.
pub fn backtrace(&self) -> &Backtrace { pub fn backtrace(&self) -> &Backtrace {
if let Some(bt) = self.cause.backtrace() { if let Some(bt) = self.cause.backtrace() {
bt bt
@ -55,10 +79,65 @@ impl Error {
self.backtrace.as_ref().unwrap() self.backtrace.as_ref().unwrap()
} }
} }
/// Attempts to downcast this `Error` to a particular `Fail` type by
/// reference.
///
/// If the underlying error is not of type `T`, this will return `None`.
pub fn downcast_ref<T: Fail>(&self) -> Option<&T> {
// in the most trivial way the cause is directly of the requested type.
if let Some(rv) = Fail::downcast_ref(self.cause.as_fail()) {
return Some(rv);
}
// in the more complex case the error has been constructed from a failure
// error. This happens because we implement From<failure::Error> by
// calling compat() and then storing it here. In failure this is
// represented by a failure::Error being wrapped in a failure::Compat.
//
// So we first downcast into that compat, to then further downcast through
// the failure's Error downcasting system into the original failure.
//
// This currently requires a transmute. This could be avoided if failure
// provides a deref: https://github.com/rust-lang-nursery/failure/pull/213
let compat: Option<&failure::Compat<failure::Error>> =
Fail::downcast_ref(self.cause.as_fail());
if let Some(compat) = compat {
pub struct CompatWrappedError {
error: failure::Error,
}
let compat: &CompatWrappedError = unsafe { ::std::mem::transmute(compat) };
compat.error.downcast_ref()
} else {
None
}
}
}
/// Helper trait to downcast a response error into a fail.
///
/// This is currently not exposed because it's unclear if this is the best way
/// to achieve the downcasting on `Error` for which this is needed.
#[doc(hidden)]
pub trait InternalResponseErrorAsFail {
#[doc(hidden)]
fn as_fail(&self) -> &Fail;
#[doc(hidden)]
fn as_mut_fail(&mut self) -> &mut Fail;
}
#[doc(hidden)]
impl<T: ResponseError> InternalResponseErrorAsFail for T {
fn as_fail(&self) -> &Fail {
self
}
fn as_mut_fail(&mut self) -> &mut Fail {
self
}
} }
/// Error that can be converted to `HttpResponse` /// Error that can be converted to `HttpResponse`
pub trait ResponseError: Fail { pub trait ResponseError: Fail + InternalResponseErrorAsFail {
/// Create response for error /// Create response for error
/// ///
/// Internal server error is generated by default. /// Internal server error is generated by default.
@ -88,7 +167,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)
@ -111,11 +190,9 @@ impl<T: ResponseError> From<T> for Error {
} }
/// Compatibility for `failure::Error` /// Compatibility for `failure::Error`
impl<T> ResponseError for failure::Compat<T> impl<T> ResponseError for failure::Compat<T> where
where T: fmt::Display + fmt::Debug + Sync + Send + 'static
T: fmt::Display + fmt::Debug + Sync + Send + 'static, {}
{
}
impl From<failure::Error> for Error { impl From<failure::Error> for Error {
fn from(err: failure::Error) -> Error { fn from(err: failure::Error) -> Error {
@ -317,10 +394,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",
)
} }
} }
@ -551,8 +625,8 @@ impl From<UrlParseError> for UrlGenerationError {
/// use actix_web::fs::NamedFile; /// use actix_web::fs::NamedFile;
/// ///
/// fn index(req: HttpRequest) -> Result<fs::NamedFile> { /// fn index(req: HttpRequest) -> Result<fs::NamedFile> {
/// let f = NamedFile::open("test.txt").map_err(error::ErrorBadRequest)?; /// let f = NamedFile::open("test.txt").map_err(error::ErrorBadRequest)?;
/// Ok(f) /// Ok(f)
/// } /// }
/// # fn main() {} /// # fn main() {}
/// ``` /// ```
@ -562,12 +636,9 @@ pub struct InternalError<T> {
backtrace: Backtrace, backtrace: Backtrace,
} }
unsafe impl<T> Sync for InternalError<T> {}
unsafe impl<T> Send for InternalError<T> {}
enum InternalErrorType { enum InternalErrorType {
Status(StatusCode), Status(StatusCode),
Response(RefCell<Option<HttpResponse>>), Response(Mutex<Option<Box<InnerHttpResponse>>>),
} }
impl<T> InternalError<T> { impl<T> InternalError<T> {
@ -582,9 +653,21 @@ impl<T> InternalError<T> {
/// Create `InternalError` with predefined `HttpResponse`. /// Create `InternalError` with predefined `HttpResponse`.
pub fn from_response(cause: T, response: HttpResponse) -> Self { pub fn from_response(cause: T, response: HttpResponse) -> Self {
let mut resp = response.into_inner();
let body = mem::replace(&mut resp.body, Body::Empty);
match body {
Body::Empty => (),
Body::Binary(mut bin) => {
resp.body = Body::Binary(bin.take().into());
}
Body::Streaming(_) | Body::Actor(_) => {
error!("Streaming or Actor body is not support by error response");
}
}
InternalError { InternalError {
cause, cause,
status: InternalErrorType::Response(RefCell::new(Some(response))), status: InternalErrorType::Response(Mutex::new(Some(resp))),
backtrace: Backtrace::new(), backtrace: Backtrace::new(),
} }
} }
@ -625,8 +708,8 @@ where
match self.status { match self.status {
InternalErrorType::Status(st) => HttpResponse::new(st), InternalErrorType::Status(st) => HttpResponse::new(st),
InternalErrorType::Response(ref resp) => { InternalErrorType::Response(ref resp) => {
if let Some(resp) = resp.borrow_mut().take() { if let Some(resp) = resp.lock().unwrap().take() {
resp HttpResponse::from_inner(resp)
} else { } else {
HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR) HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR)
} }
@ -836,7 +919,7 @@ mod tests {
} }
#[test] #[test]
fn test_cause() { fn test_as_fail() {
let orig = io::Error::new(io::ErrorKind::Other, "other"); let orig = io::Error::new(io::ErrorKind::Other, "other");
let desc = orig.description().to_owned(); let desc = orig.description().to_owned();
let e = ParseError::Io(orig); let e = ParseError::Io(orig);
@ -854,7 +937,7 @@ mod tests {
let orig = io::Error::new(io::ErrorKind::Other, "other"); let orig = io::Error::new(io::ErrorKind::Other, "other");
let desc = orig.description().to_owned(); let desc = orig.description().to_owned();
let e = Error::from(orig); let e = Error::from(orig);
assert_eq!(format!("{}", e.cause()), desc); assert_eq!(format!("{}", e.as_fail()), desc);
} }
#[test] #[test]
@ -953,6 +1036,32 @@ mod tests {
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
} }
#[test]
fn test_error_downcasting_direct() {
#[derive(Debug, Fail)]
#[fail(display = "demo error")]
struct DemoError;
impl ResponseError for DemoError {}
let err: Error = DemoError.into();
let err_ref: &DemoError = err.downcast_ref().unwrap();
assert_eq!(err_ref.to_string(), "demo error");
}
#[test]
fn test_error_downcasting_compat() {
#[derive(Debug, Fail)]
#[fail(display = "demo error")]
struct DemoError;
impl ResponseError for DemoError {}
let err: Error = failure::Error::from(DemoError).into();
let err_ref: &DemoError = err.downcast_ref().unwrap();
assert_eq!(err_ref.to_string(), "demo error");
}
#[test] #[test]
fn test_error_helpers() { fn test_error_helpers() {
let r: HttpResponse = ErrorBadRequest("err").into(); let r: HttpResponse = ErrorBadRequest("err").into();

View File

@ -11,7 +11,7 @@ use serde::de::{self, DeserializeOwned};
use serde_urlencoded; use serde_urlencoded;
use de::PathDeserializer; use de::PathDeserializer;
use error::{Error, ErrorNotFound, ErrorBadRequest}; use error::{Error, ErrorBadRequest, ErrorNotFound};
use handler::{AsyncResult, FromRequest}; use handler::{AsyncResult, FromRequest};
use httpmessage::{HttpMessage, MessageBody, UrlEncoded}; use httpmessage::{HttpMessage, MessageBody, UrlEncoded};
use httprequest::HttpRequest; use httprequest::HttpRequest;
@ -330,9 +330,7 @@ impl<S: 'static> FromRequest<S> for Bytes {
cfg.check_mimetype(req)?; cfg.check_mimetype(req)?;
Ok(Box::new( Ok(Box::new(
MessageBody::new(req.clone()) MessageBody::new(req.clone()).limit(cfg.limit).from_err(),
.limit(cfg.limit)
.from_err(),
)) ))
} }
} }
@ -512,14 +510,7 @@ tuple_from_req!(TupleFromRequest1, (0, A));
tuple_from_req!(TupleFromRequest2, (0, A), (1, B)); tuple_from_req!(TupleFromRequest2, (0, A), (1, B));
tuple_from_req!(TupleFromRequest3, (0, A), (1, B), (2, C)); tuple_from_req!(TupleFromRequest3, (0, A), (1, B), (2, C));
tuple_from_req!(TupleFromRequest4, (0, A), (1, B), (2, C), (3, D)); tuple_from_req!(TupleFromRequest4, (0, A), (1, B), (2, C), (3, D));
tuple_from_req!( tuple_from_req!(TupleFromRequest5, (0, A), (1, B), (2, C), (3, D), (4, E));
TupleFromRequest5,
(0, A),
(1, B),
(2, C),
(3, D),
(4, E)
);
tuple_from_req!( tuple_from_req!(
TupleFromRequest6, TupleFromRequest6,
(0, A), (0, A),
@ -587,11 +578,7 @@ mod tests {
req.payload_mut() req.payload_mut()
.unread_data(Bytes::from_static(b"hello=world")); .unread_data(Bytes::from_static(b"hello=world"));
match Bytes::from_request(&req, &cfg) match Bytes::from_request(&req, &cfg).unwrap().poll().unwrap() {
.unwrap()
.poll()
.unwrap()
{
Async::Ready(s) => { Async::Ready(s) => {
assert_eq!(s, Bytes::from_static(b"hello=world")); assert_eq!(s, Bytes::from_static(b"hello=world"));
} }
@ -606,11 +593,7 @@ mod tests {
req.payload_mut() req.payload_mut()
.unread_data(Bytes::from_static(b"hello=world")); .unread_data(Bytes::from_static(b"hello=world"));
match String::from_request(&req, &cfg) match String::from_request(&req, &cfg).unwrap().poll().unwrap() {
.unwrap()
.poll()
.unwrap()
{
Async::Ready(s) => { Async::Ready(s) => {
assert_eq!(s, "hello=world"); assert_eq!(s, "hello=world");
} }
@ -680,10 +663,7 @@ mod tests {
let mut resource = ResourceHandler::<()>::default(); let mut resource = ResourceHandler::<()>::default();
resource.name("index"); resource.name("index");
let mut routes = Vec::new(); let mut routes = Vec::new();
routes.push(( routes.push((Resource::new("index", "/{key}/{value}/"), Some(resource)));
Resource::new("index", "/{key}/{value}/"),
Some(resource),
));
let (router, _) = Router::new("", ServerSettings::default(), routes); let (router, _) = Router::new("", ServerSettings::default(), routes);
assert!(router.recognize(&mut req).is_some()); assert!(router.recognize(&mut req).is_some());
@ -735,10 +715,7 @@ mod tests {
let mut resource = ResourceHandler::<()>::default(); let mut resource = ResourceHandler::<()>::default();
resource.name("index"); resource.name("index");
let mut routes = Vec::new(); let mut routes = Vec::new();
routes.push(( routes.push((Resource::new("index", "/{key}/{value}/"), Some(resource)));
Resource::new("index", "/{key}/{value}/"),
Some(resource),
));
let (router, _) = Router::new("", ServerSettings::default(), routes); let (router, _) = Router::new("", ServerSettings::default(), routes);
assert!(router.recognize(&mut req).is_some()); assert!(router.recognize(&mut req).is_some());

333
src/fs.rs
View File

@ -20,7 +20,7 @@ use mime_guess::{get_mime_type, guess_mime_type};
use error::Error; use error::Error;
use handler::{AsyncResult, Handler, Responder, RouteHandler, WrapHandler}; use handler::{AsyncResult, Handler, Responder, RouteHandler, WrapHandler};
use header; use header;
use http::{Method, StatusCode}; use http::{ContentEncoding, HttpRange, Method, StatusCode};
use httpmessage::HttpMessage; use httpmessage::HttpMessage;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
@ -38,6 +38,7 @@ pub struct NamedFile {
md: Metadata, md: Metadata,
modified: Option<SystemTime>, modified: Option<SystemTime>,
cpu_pool: Option<CpuPool>, cpu_pool: Option<CpuPool>,
encoding: Option<ContentEncoding>,
only_get: bool, only_get: bool,
status_code: StatusCode, status_code: StatusCode,
} }
@ -58,12 +59,14 @@ impl NamedFile {
let path = path.as_ref().to_path_buf(); let path = path.as_ref().to_path_buf();
let modified = md.modified().ok(); let modified = md.modified().ok();
let cpu_pool = None; let cpu_pool = None;
let encoding = None;
Ok(NamedFile { Ok(NamedFile {
path, path,
file, file,
md, md,
modified, modified,
cpu_pool, cpu_pool,
encoding,
only_get: false, only_get: false,
status_code: StatusCode::OK, status_code: StatusCode::OK,
}) })
@ -114,6 +117,13 @@ impl NamedFile {
self self
} }
/// Set content encoding for serving this file
#[inline]
pub fn set_content_encoding(mut self, enc: ContentEncoding) -> Self {
self.encoding = Some(enc);
self
}
fn etag(&self) -> Option<header::EntityTag> { fn etag(&self) -> Option<header::EntityTag> {
// This etag format is similar to Apache's. // This etag format is similar to Apache's.
self.modified.as_ref().map(|mtime| { self.modified.as_ref().map(|mtime| {
@ -203,31 +213,32 @@ impl Responder for NamedFile {
if self.status_code != StatusCode::OK { if self.status_code != StatusCode::OK {
let mut resp = HttpResponse::build(self.status_code); let mut resp = HttpResponse::build(self.status_code);
resp.if_some(self.path().extension(), |ext, resp| { resp.if_some(self.path().extension(), |ext, resp| {
resp.set(header::ContentType(get_mime_type( resp.set(header::ContentType(get_mime_type(&ext.to_string_lossy())));
&ext.to_string_lossy(),
)));
}).if_some(self.path().file_name(), |file_name, resp| { }).if_some(self.path().file_name(), |file_name, resp| {
let mime_type = guess_mime_type(self.path()); let mime_type = guess_mime_type(self.path());
let inline_or_attachment = match mime_type.type_() { let inline_or_attachment = match mime_type.type_() {
mime::IMAGE | mime::TEXT => "inline", mime::IMAGE | mime::TEXT | mime::VIDEO => "inline",
_ => "attachment", _ => "attachment",
}; };
resp.header( resp.header(
"Content-Disposition", "Content-Disposition",
format!( format!(
"{inline_or_attachment}; filename={filename}", "{inline_or_attachment}; filename={filename}",
inline_or_attachment = inline_or_attachment, inline_or_attachment = inline_or_attachment,
filename = file_name.to_string_lossy() filename = file_name.to_string_lossy()
), ),
); );
}); });
if let Some(current_encoding) = self.encoding {
resp.content_encoding(current_encoding);
}
let reader = ChunkedReadFile { let reader = ChunkedReadFile {
size: self.md.len(), size: self.md.len(),
offset: 0, offset: 0,
cpu_pool: self.cpu_pool cpu_pool: self.cpu_pool.unwrap_or_else(|| req.cpu_pool().clone()),
.unwrap_or_else(|| req.cpu_pool().clone()),
file: Some(self.file), file: Some(self.file),
fut: None, fut: None,
counter: 0,
}; };
return Ok(resp.streaming(reader)); return Ok(resp.streaming(reader));
} }
@ -266,15 +277,16 @@ impl Responder for NamedFile {
}; };
let mut resp = HttpResponse::build(self.status_code); let mut resp = HttpResponse::build(self.status_code);
if let Some(current_encoding) = self.encoding {
resp.content_encoding(current_encoding);
}
resp.if_some(self.path().extension(), |ext, resp| { resp.if_some(self.path().extension(), |ext, resp| {
resp.set(header::ContentType(get_mime_type( resp.set(header::ContentType(get_mime_type(&ext.to_string_lossy())));
&ext.to_string_lossy(),
)));
}).if_some(self.path().file_name(), |file_name, resp| { }).if_some(self.path().file_name(), |file_name, resp| {
let mime_type = guess_mime_type(self.path()); let mime_type = guess_mime_type(self.path());
let inline_or_attachment = match mime_type.type_() { let inline_or_attachment = match mime_type.type_() {
mime::IMAGE | mime::TEXT => "inline", mime::IMAGE | mime::TEXT | mime::VIDEO => "inline",
_ => "attachment", _ => "attachment",
}; };
resp.header( resp.header(
@ -293,6 +305,38 @@ impl Responder for NamedFile {
resp.set(header::ETag(etag)); resp.set(header::ETag(etag));
}); });
resp.header(header::ACCEPT_RANGES, "bytes");
let mut length = self.md.len();
let mut offset = 0;
// check for range header
if let Some(ranges) = req.headers().get(header::RANGE) {
if let Ok(rangesheader) = ranges.to_str() {
if let Ok(rangesvec) = HttpRange::parse(rangesheader, length) {
length = rangesvec[0].length;
offset = rangesvec[0].start;
resp.content_encoding(ContentEncoding::Identity);
resp.header(
header::CONTENT_RANGE,
format!(
"bytes {}-{}/{}",
offset,
offset + length - 1,
self.md.len()
),
);
} else {
resp.header(header::CONTENT_RANGE, format!("bytes */{}", length));
return Ok(resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish());
};
} else {
return Ok(resp.status(StatusCode::BAD_REQUEST).finish());
};
};
resp.header(header::CONTENT_LENGTH, format!("{}", length));
if precondition_failed { if precondition_failed {
return Ok(resp.status(StatusCode::PRECONDITION_FAILED).finish()); return Ok(resp.status(StatusCode::PRECONDITION_FAILED).finish());
} else if not_modified { } else if not_modified {
@ -303,12 +347,15 @@ impl Responder for NamedFile {
Ok(resp.finish()) Ok(resp.finish())
} else { } else {
let reader = ChunkedReadFile { let reader = ChunkedReadFile {
size: self.md.len(), offset,
offset: 0, size: length,
cpu_pool: self.cpu_pool cpu_pool: self.cpu_pool.unwrap_or_else(|| req.cpu_pool().clone()),
.unwrap_or_else(|| req.cpu_pool().clone()),
file: Some(self.file), file: Some(self.file),
fut: None, fut: None,
counter: 0,
};
if offset != 0 || length != self.md.len() {
return Ok(resp.status(StatusCode::PARTIAL_CONTENT).streaming(reader));
}; };
Ok(resp.streaming(reader)) Ok(resp.streaming(reader))
} }
@ -323,6 +370,7 @@ pub struct ChunkedReadFile {
cpu_pool: CpuPool, cpu_pool: CpuPool,
file: Option<File>, file: Option<File>,
fut: Option<CpuFuture<(File, Bytes), io::Error>>, fut: Option<CpuFuture<(File, Bytes), io::Error>>,
counter: u64,
} }
impl Stream for ChunkedReadFile { impl Stream for ChunkedReadFile {
@ -336,6 +384,7 @@ impl Stream for ChunkedReadFile {
self.fut.take(); self.fut.take();
self.file = Some(file); self.file = Some(file);
self.offset += bytes.len() as u64; self.offset += bytes.len() as u64;
self.counter += bytes.len() as u64;
Ok(Async::Ready(Some(bytes))) Ok(Async::Ready(Some(bytes)))
} }
Async::NotReady => Ok(Async::NotReady), Async::NotReady => Ok(Async::NotReady),
@ -344,14 +393,16 @@ impl Stream for ChunkedReadFile {
let size = self.size; let size = self.size;
let offset = self.offset; let offset = self.offset;
let counter = self.counter;
if size == offset { if size == counter {
Ok(Async::Ready(None)) Ok(Async::Ready(None))
} else { } else {
let mut file = self.file.take().expect("Use after completion"); let mut file = self.file.take().expect("Use after completion");
self.fut = Some(self.cpu_pool.spawn_fn(move || { self.fut = Some(self.cpu_pool.spawn_fn(move || {
let max_bytes = cmp::min(size.saturating_sub(offset), 65_536) as usize; let max_bytes: usize;
let mut buf = BytesMut::with_capacity(max_bytes); max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize;
let mut buf = BytesMut::from(Vec::with_capacity(max_bytes));
file.seek(io::SeekFrom::Start(offset))?; file.seek(io::SeekFrom::Start(offset))?;
let nbytes = file.read(unsafe { buf.bytes_mut() })?; let nbytes = file.read(unsafe { buf.bytes_mut() })?;
if nbytes == 0 { if nbytes == 0 {
@ -584,7 +635,8 @@ impl<S: 'static> Handler<S> for StaticFiles<S> {
if !self.accessible { if !self.accessible {
Ok(self.default.handle(req)) Ok(self.default.handle(req))
} else { } else {
let relpath = match req.match_info() let relpath = match req
.match_info()
.get("tail") .get("tail")
.map(|tail| PathBuf::from_param(tail.trim_left_matches('/'))) .map(|tail| PathBuf::from_param(tail.trim_left_matches('/')))
{ {
@ -656,9 +708,7 @@ mod tests {
"text/x-toml" "text/x-toml"
); );
assert_eq!( assert_eq!(
resp.headers() resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
.get(header::CONTENT_DISPOSITION)
.unwrap(),
"inline; filename=Cargo.toml" "inline; filename=Cargo.toml"
); );
} }
@ -682,9 +732,7 @@ mod tests {
"image/png" "image/png"
); );
assert_eq!( assert_eq!(
resp.headers() resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
.get(header::CONTENT_DISPOSITION)
.unwrap(),
"inline; filename=test.png" "inline; filename=test.png"
); );
} }
@ -708,9 +756,7 @@ mod tests {
"application/octet-stream" "application/octet-stream"
); );
assert_eq!( assert_eq!(
resp.headers() resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
.get(header::CONTENT_DISPOSITION)
.unwrap(),
"attachment; filename=test.binary" "attachment; filename=test.binary"
); );
} }
@ -735,14 +781,173 @@ mod tests {
"text/x-toml" "text/x-toml"
); );
assert_eq!( assert_eq!(
resp.headers() resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
.get(header::CONTENT_DISPOSITION)
.unwrap(),
"inline; filename=Cargo.toml" "inline; filename=Cargo.toml"
); );
assert_eq!(resp.status(), StatusCode::NOT_FOUND); assert_eq!(resp.status(), StatusCode::NOT_FOUND);
} }
#[test]
fn test_named_file_ranges_status_code() {
let mut srv = test::TestServer::with_factory(|| {
App::new().handler("test", StaticFiles::new(".").index_file("Cargo.toml"))
});
// Valid range header
let request = srv
.get()
.uri(srv.url("/t%65st/Cargo.toml"))
.header(header::RANGE, "bytes=10-20")
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
// Invalid range header
let request = srv
.get()
.uri(srv.url("/t%65st/Cargo.toml"))
.header(header::RANGE, "bytes=1-0")
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE);
}
#[test]
fn test_named_file_content_range_headers() {
let mut srv = test::TestServer::with_factory(|| {
App::new().handler(
"test",
StaticFiles::new(".").index_file("tests/test.binary"),
)
});
// Valid range header
let request = srv
.get()
.uri(srv.url("/t%65st/tests/test.binary"))
.header(header::RANGE, "bytes=10-20")
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
let contentrange = response
.headers()
.get(header::CONTENT_RANGE)
.unwrap()
.to_str()
.unwrap();
assert_eq!(contentrange, "bytes 10-20/100");
// Invalid range header
let request = srv
.get()
.uri(srv.url("/t%65st/tests/test.binary"))
.header(header::RANGE, "bytes=10-5")
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
let contentrange = response
.headers()
.get(header::CONTENT_RANGE)
.unwrap()
.to_str()
.unwrap();
assert_eq!(contentrange, "bytes */100");
}
#[test]
fn test_named_file_content_length_headers() {
let mut srv = test::TestServer::with_factory(|| {
App::new().handler(
"test",
StaticFiles::new(".").index_file("tests/test.binary"),
)
});
// Valid range header
let request = srv
.get()
.uri(srv.url("/t%65st/tests/test.binary"))
.header(header::RANGE, "bytes=10-20")
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
let contentlength = response
.headers()
.get(header::CONTENT_LENGTH)
.unwrap()
.to_str()
.unwrap();
assert_eq!(contentlength, "11");
// Invalid range header
let request = srv
.get()
.uri(srv.url("/t%65st/tests/test.binary"))
.header(header::RANGE, "bytes=10-8")
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
let contentlength = response
.headers()
.get(header::CONTENT_LENGTH)
.unwrap()
.to_str()
.unwrap();
assert_eq!(contentlength, "0");
// Without range header
let request = srv
.get()
.uri(srv.url("/t%65st/tests/test.binary"))
.no_default_headers()
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
let contentlength = response
.headers()
.get(header::CONTENT_LENGTH)
.unwrap()
.to_str()
.unwrap();
assert_eq!(contentlength, "100");
// chunked
let request = srv
.get()
.uri(srv.url("/t%65st/tests/test.binary"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
let te = response
.headers()
.get(header::TRANSFER_ENCODING)
.unwrap()
.to_str()
.unwrap();
assert_eq!(te, "chunked");
}
#[test] #[test]
fn test_named_file_not_allowed() { fn test_named_file_not_allowed() {
let req = TestRequest::default().method(Method::POST).finish(); let req = TestRequest::default().method(Method::POST).finish();
@ -752,6 +957,21 @@ mod tests {
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
} }
#[test]
fn test_named_file_content_encoding() {
let req = TestRequest::default().method(Method::GET).finish();
let file = NamedFile::open("Cargo.toml").unwrap();
assert!(file.encoding.is_none());
let resp = file
.set_content_encoding(ContentEncoding::Identity)
.respond_to(&req)
.unwrap();
assert!(resp.content_encoding().is_some());
assert_eq!(resp.content_encoding().unwrap().as_str(), "identity");
}
#[test] #[test]
fn test_named_file_any_method() { fn test_named_file_any_method() {
let req = TestRequest::default().method(Method::POST).finish(); let req = TestRequest::default().method(Method::POST).finish();
@ -764,7 +984,8 @@ mod tests {
fn test_static_files() { fn test_static_files() {
let mut st = StaticFiles::new(".").show_files_listing(); let mut st = StaticFiles::new(".").show_files_listing();
st.accessible = false; st.accessible = false;
let resp = st.handle(HttpRequest::default()) let resp = st
.handle(HttpRequest::default())
.respond_to(&HttpRequest::default()) .respond_to(&HttpRequest::default())
.unwrap(); .unwrap();
let resp = resp.as_msg(); let resp = resp.as_msg();
@ -772,7 +993,8 @@ mod tests {
st.accessible = true; st.accessible = true;
st.show_index = false; st.show_index = false;
let resp = st.handle(HttpRequest::default()) let resp = st
.handle(HttpRequest::default())
.respond_to(&HttpRequest::default()) .respond_to(&HttpRequest::default())
.unwrap(); .unwrap();
let resp = resp.as_msg(); let resp = resp.as_msg();
@ -782,9 +1004,7 @@ mod tests {
req.match_info_mut().add("tail", ""); req.match_info_mut().add("tail", "");
st.show_index = true; st.show_index = true;
let resp = st.handle(req) let resp = st.handle(req).respond_to(&HttpRequest::default()).unwrap();
.respond_to(&HttpRequest::default())
.unwrap();
let resp = resp.as_msg(); let resp = resp.as_msg();
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),
@ -800,9 +1020,7 @@ mod tests {
let mut req = HttpRequest::default(); let mut req = HttpRequest::default();
req.match_info_mut().add("tail", "tests"); req.match_info_mut().add("tail", "tests");
let resp = st.handle(req) let resp = st.handle(req).respond_to(&HttpRequest::default()).unwrap();
.respond_to(&HttpRequest::default())
.unwrap();
let resp = resp.as_msg(); let resp = resp.as_msg();
assert_eq!(resp.status(), StatusCode::FOUND); assert_eq!(resp.status(), StatusCode::FOUND);
assert_eq!( assert_eq!(
@ -813,9 +1031,7 @@ mod tests {
let mut req = HttpRequest::default(); let mut req = HttpRequest::default();
req.match_info_mut().add("tail", "tests/"); req.match_info_mut().add("tail", "tests/");
let resp = st.handle(req) let resp = st.handle(req).respond_to(&HttpRequest::default()).unwrap();
.respond_to(&HttpRequest::default())
.unwrap();
let resp = resp.as_msg(); let resp = resp.as_msg();
assert_eq!(resp.status(), StatusCode::FOUND); assert_eq!(resp.status(), StatusCode::FOUND);
assert_eq!( assert_eq!(
@ -830,9 +1046,7 @@ mod tests {
let mut req = HttpRequest::default(); let mut req = HttpRequest::default();
req.match_info_mut().add("tail", "tools/wsload"); req.match_info_mut().add("tail", "tools/wsload");
let resp = st.handle(req) let resp = st.handle(req).respond_to(&HttpRequest::default()).unwrap();
.respond_to(&HttpRequest::default())
.unwrap();
let resp = resp.as_msg(); let resp = resp.as_msg();
assert_eq!(resp.status(), StatusCode::FOUND); assert_eq!(resp.status(), StatusCode::FOUND);
assert_eq!( assert_eq!(
@ -907,7 +1121,8 @@ mod tests {
App::new().handler("test", StaticFiles::new(".").index_file("Cargo.toml")) App::new().handler("test", StaticFiles::new(".").index_file("Cargo.toml"))
}); });
let request = srv.get() let request = srv
.get()
.uri(srv.url("/test/%43argo.toml")) .uri(srv.url("/test/%43argo.toml"))
.finish() .finish()
.unwrap(); .unwrap();

View File

@ -362,7 +362,8 @@ where
self, req: &HttpRequest<S>, self, req: &HttpRequest<S>,
) -> Result<AsyncResult<HttpResponse>, Error> { ) -> Result<AsyncResult<HttpResponse>, Error> {
let req = req.clone(); let req = req.clone();
let fut = self.map_err(|e| e.into()) let fut = self
.map_err(|e| e.into())
.then(move |r| match r.respond_to(&req) { .then(move |r| match r.respond_to(&req) {
Ok(reply) => match reply.into().into() { Ok(reply) => match reply.into().into() {
AsyncResultItem::Ok(resp) => ok(resp), AsyncResultItem::Ok(resp) => ok(resp),
@ -397,10 +398,7 @@ where
S: 'static, S: 'static,
{ {
pub fn new(h: H) -> Self { pub fn new(h: H) -> Self {
WrapHandler { WrapHandler { h, s: PhantomData }
h,
s: PhantomData,
}
} }
} }
@ -456,16 +454,16 @@ where
S: 'static, S: 'static,
{ {
fn handle(&mut self, req: HttpRequest<S>) -> AsyncResult<HttpResponse> { fn handle(&mut self, req: HttpRequest<S>) -> AsyncResult<HttpResponse> {
let fut = (self.h)(req.clone()) let fut = (self.h)(req.clone()).map_err(|e| e.into()).then(move |r| {
.map_err(|e| e.into()) match r.respond_to(&req) {
.then(move |r| match r.respond_to(&req) {
Ok(reply) => match reply.into().into() { Ok(reply) => match reply.into().into() {
AsyncResultItem::Ok(resp) => Either::A(ok(resp)), AsyncResultItem::Ok(resp) => Either::A(ok(resp)),
AsyncResultItem::Err(e) => Either::A(err(e)), AsyncResultItem::Err(e) => Either::A(err(e)),
AsyncResultItem::Future(fut) => Either::B(fut), AsyncResultItem::Future(fut) => Either::B(fut),
}, },
Err(e) => Either::A(err(e)), Err(e) => Either::A(err(e)),
}); }
});
AsyncResult::async(Box::new(fut)) AsyncResult::async(Box::new(fut))
} }
} }
@ -492,14 +490,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>);

View File

@ -1,8 +1,9 @@
use std::fmt; use std::fmt;
use std::str; use std::str;
pub use self::Encoding::{Brotli, Chunked, Compress, Deflate, EncodingExt, Gzip, pub use self::Encoding::{
Identity, Trailers}; Brotli, Chunked, Compress, Deflate, EncodingExt, Gzip, Identity, Trailers,
};
/// A value to represent an encoding used in `Transfer-Encoding` /// A value to represent an encoding used in `Transfer-Encoding`
/// or `Accept-Encoding` header. /// or `Accept-Encoding` header.

View File

@ -132,7 +132,8 @@ impl FromStr for EntityTag {
return Err(::error::ParseError::Header); return Err(::error::ParseError::Header);
} }
// The etag is weak if its first char is not a DQUOTE. // The etag is weak if its first char is not a DQUOTE.
if slice.len() >= 2 && slice.starts_with('"') if slice.len() >= 2
&& slice.starts_with('"')
&& check_slice_validity(&slice[1..length - 1]) && check_slice_validity(&slice[1..length - 1])
{ {
// No need to check if the last char is a DQUOTE, // No need to check if the last char is a DQUOTE,
@ -141,7 +142,8 @@ impl FromStr for EntityTag {
weak: false, weak: false,
tag: slice[1..length - 1].to_owned(), tag: slice[1..length - 1].to_owned(),
}); });
} else if slice.len() >= 4 && slice.starts_with("W/\"") } else if slice.len() >= 4
&& slice.starts_with("W/\"")
&& check_slice_validity(&slice[3..length - 1]) && check_slice_validity(&slice[3..length - 1])
{ {
return Ok(EntityTag { return Ok(EntityTag {
@ -213,10 +215,7 @@ mod tests {
format!("{}", EntityTag::strong("foobar".to_owned())), format!("{}", EntityTag::strong("foobar".to_owned())),
"\"foobar\"" "\"foobar\""
); );
assert_eq!( assert_eq!(format!("{}", EntityTag::strong("".to_owned())), "\"\"");
format!("{}", EntityTag::strong("".to_owned())),
"\"\""
);
assert_eq!( assert_eq!(
format!("{}", EntityTag::weak("weak-etag".to_owned())), format!("{}", EntityTag::weak("weak-etag".to_owned())),
"W/\"weak-etag\"" "W/\"weak-etag\""
@ -225,10 +224,7 @@ mod tests {
format!("{}", EntityTag::weak("\u{0065}".to_owned())), format!("{}", EntityTag::weak("\u{0065}".to_owned())),
"W/\"\x65\"" "W/\"\x65\""
); );
assert_eq!( assert_eq!(format!("{}", EntityTag::weak("".to_owned())), "W/\"\"");
format!("{}", EntityTag::weak("".to_owned())),
"W/\"\""
);
} }
#[test] #[test]

View File

@ -105,9 +105,7 @@ mod tests {
#[test] #[test]
fn test_date() { fn test_date() {
assert_eq!( assert_eq!(
"Sun, 07 Nov 1994 08:48:37 GMT" "Sun, 07 Nov 1994 08:48:37 GMT".parse::<HttpDate>().unwrap(),
.parse::<HttpDate>()
.unwrap(),
NOV_07 NOV_07
); );
assert_eq!( assert_eq!(
@ -117,9 +115,7 @@ mod tests {
NOV_07 NOV_07
); );
assert_eq!( assert_eq!(
"Sun Nov 7 08:48:37 1994" "Sun Nov 7 08:48:37 1994".parse::<HttpDate>().unwrap(),
.parse::<HttpDate>()
.unwrap(),
NOV_07 NOV_07
); );
assert!("this-is-no-date".parse::<HttpDate>().is_err()); assert!("this-is-no-date".parse::<HttpDate>().is_err());

View File

@ -63,11 +63,7 @@ impl<T: fmt::Display> fmt::Display for QualityItem<T> {
match self.quality.0 { match self.quality.0 {
1000 => Ok(()), 1000 => Ok(()),
0 => f.write_str("; q=0"), 0 => f.write_str("; q=0"),
x => write!( x => write!(f, "; q=0.{}", format!("{:03}", x).trim_right_matches('0')),
f,
"; q=0.{}",
format!("{:03}", x).trim_right_matches('0')
),
} }
} }
} }
@ -295,10 +291,6 @@ mod tests {
#[test] #[test]
fn test_fuzzing_bugs() { fn test_fuzzing_bugs() {
assert!("99999;".parse::<QualityItem<String>>().is_err()); assert!("99999;".parse::<QualityItem<String>>().is_err());
assert!( assert!("\x0d;;;=\u{d6aa}==".parse::<QualityItem<String>>().is_err())
"\x0d;;;=\u{d6aa}=="
.parse::<QualityItem<String>>()
.is_err()
)
} }
} }

View File

@ -190,16 +190,8 @@ mod tests {
// trailing slashes // trailing slashes
let params = vec![ let params = vec![
("/resource1", "", StatusCode::OK), ("/resource1", "", StatusCode::OK),
( ("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY),
"/resource1/", ("/resource2", "/resource2/", StatusCode::MOVED_PERMANENTLY),
"/resource1",
StatusCode::MOVED_PERMANENTLY,
),
(
"/resource2",
"/resource2/",
StatusCode::MOVED_PERMANENTLY,
),
("/resource2/", "", StatusCode::OK), ("/resource2/", "", StatusCode::OK),
("/resource1?p1=1&p2=2", "", StatusCode::OK), ("/resource1?p1=1&p2=2", "", StatusCode::OK),
( (
@ -222,11 +214,7 @@ mod tests {
if !target.is_empty() { if !target.is_empty() {
assert_eq!( assert_eq!(
target, target,
r.headers() r.headers().get(header::LOCATION).unwrap().to_str().unwrap()
.get(header::LOCATION)
.unwrap()
.to_str()
.unwrap()
); );
} }
} }
@ -276,16 +264,8 @@ mod tests {
// trailing slashes // trailing slashes
let params = vec![ let params = vec![
("/resource1/a/b", "", StatusCode::OK), ("/resource1/a/b", "", StatusCode::OK),
( ("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY),
"/resource1/", ("/resource1//", "/resource1", StatusCode::MOVED_PERMANENTLY),
"/resource1",
StatusCode::MOVED_PERMANENTLY,
),
(
"/resource1//",
"/resource1",
StatusCode::MOVED_PERMANENTLY,
),
( (
"//resource1//a//b", "//resource1//a//b",
"/resource1/a/b", "/resource1/a/b",
@ -356,11 +336,7 @@ mod tests {
if !target.is_empty() { if !target.is_empty() {
assert_eq!( assert_eq!(
target, target,
r.headers() r.headers().get(header::LOCATION).unwrap().to_str().unwrap()
.get(header::LOCATION)
.unwrap()
.to_str()
.unwrap()
); );
} }
} }
@ -540,11 +516,7 @@ mod tests {
if !target.is_empty() { if !target.is_empty() {
assert_eq!( assert_eq!(
target, target,
r.headers() r.headers().get(header::LOCATION).unwrap().to_str().unwrap()
.get(header::LOCATION)
.unwrap()
.to_str()
.unwrap()
); );
} }
} }

View File

@ -55,10 +55,7 @@ impl HttpResponse {
STATIC_RESP!(PreconditionFailed, StatusCode::PRECONDITION_FAILED); STATIC_RESP!(PreconditionFailed, StatusCode::PRECONDITION_FAILED);
STATIC_RESP!(PayloadTooLarge, StatusCode::PAYLOAD_TOO_LARGE); STATIC_RESP!(PayloadTooLarge, StatusCode::PAYLOAD_TOO_LARGE);
STATIC_RESP!(UriTooLong, StatusCode::URI_TOO_LONG); STATIC_RESP!(UriTooLong, StatusCode::URI_TOO_LONG);
STATIC_RESP!( STATIC_RESP!(UnsupportedMediaType, StatusCode::UNSUPPORTED_MEDIA_TYPE);
UnsupportedMediaType,
StatusCode::UNSUPPORTED_MEDIA_TYPE
);
STATIC_RESP!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE); STATIC_RESP!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE);
STATIC_RESP!(ExpectationFailed, StatusCode::EXPECTATION_FAILED); STATIC_RESP!(ExpectationFailed, StatusCode::EXPECTATION_FAILED);
@ -67,14 +64,8 @@ impl HttpResponse {
STATIC_RESP!(BadGateway, StatusCode::BAD_GATEWAY); STATIC_RESP!(BadGateway, StatusCode::BAD_GATEWAY);
STATIC_RESP!(ServiceUnavailable, StatusCode::SERVICE_UNAVAILABLE); STATIC_RESP!(ServiceUnavailable, StatusCode::SERVICE_UNAVAILABLE);
STATIC_RESP!(GatewayTimeout, StatusCode::GATEWAY_TIMEOUT); STATIC_RESP!(GatewayTimeout, StatusCode::GATEWAY_TIMEOUT);
STATIC_RESP!( STATIC_RESP!(VersionNotSupported, StatusCode::HTTP_VERSION_NOT_SUPPORTED);
VersionNotSupported, STATIC_RESP!(VariantAlsoNegotiates, StatusCode::VARIANT_ALSO_NEGOTIATES);
StatusCode::HTTP_VERSION_NOT_SUPPORTED
);
STATIC_RESP!(
VariantAlsoNegotiates,
StatusCode::VARIANT_ALSO_NEGOTIATES
);
STATIC_RESP!(InsufficientStorage, StatusCode::INSUFFICIENT_STORAGE); STATIC_RESP!(InsufficientStorage, StatusCode::INSUFFICIENT_STORAGE);
STATIC_RESP!(LoopDetected, StatusCode::LOOP_DETECTED); STATIC_RESP!(LoopDetected, StatusCode::LOOP_DETECTED);
} }

View File

@ -11,7 +11,9 @@ use serde::de::DeserializeOwned;
use serde_urlencoded; use serde_urlencoded;
use std::str; use std::str;
use error::{ContentTypeError, HttpRangeError, ParseError, PayloadError, UrlencodedError}; use error::{
ContentTypeError, HttpRangeError, ParseError, PayloadError, UrlencodedError,
};
use header::Header; use header::Header;
use json::JsonBody; use json::JsonBody;
use multipart::Multipart; use multipart::Multipart;
@ -96,10 +98,8 @@ pub trait HttpMessage {
/// `size` is full size of response (file). /// `size` is full size of response (file).
fn range(&self, size: u64) -> Result<Vec<HttpRange>, HttpRangeError> { fn range(&self, size: u64) -> Result<Vec<HttpRange>, HttpRangeError> {
if let Some(range) = self.headers().get(header::RANGE) { if let Some(range) = self.headers().get(header::RANGE) {
HttpRange::parse( HttpRange::parse(unsafe { str::from_utf8_unchecked(range.as_bytes()) }, size)
unsafe { str::from_utf8_unchecked(range.as_bytes()) }, .map_err(|e| e.into())
size,
).map_err(|e| e.into())
} else { } else {
Ok(Vec::new()) Ok(Vec::new())
} }
@ -148,7 +148,6 @@ pub trait HttpMessage {
/// Returns error: /// Returns error:
/// ///
/// * content type is not `application/x-www-form-urlencoded` /// * content type is not `application/x-www-form-urlencoded`
/// * transfer encoding is `chunked`.
/// * content-length is greater than 256k /// * content-length is greater than 256k
/// ///
/// ## Server example /// ## Server example
@ -365,9 +364,7 @@ where
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(req) = self.req.take() { if let Some(req) = self.req.take() {
if req.chunked().unwrap_or(false) { if let Some(len) = req.headers().get(header::CONTENT_LENGTH) {
return Err(UrlencodedError::Chunked);
} else if let Some(len) = req.headers().get(header::CONTENT_LENGTH) {
if let Ok(s) = len.to_str() { if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<u64>() { if let Ok(len) = s.parse::<u64>() {
if len > 262_144 { if len > 262_144 {
@ -385,12 +382,12 @@ where
if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" { if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" {
return Err(UrlencodedError::ContentType); return Err(UrlencodedError::ContentType);
} }
let encoding = req.encoding() let encoding = req.encoding().map_err(|_| UrlencodedError::ContentType)?;
.map_err(|_| UrlencodedError::ContentType)?;
// future // future
let limit = self.limit; let limit = self.limit;
let fut = req.from_err() let fut = req
.from_err()
.fold(BytesMut::new(), move |mut body, chunk| { .fold(BytesMut::new(), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit { if (body.len() + chunk.len()) > limit {
Err(UrlencodedError::Overflow) Err(UrlencodedError::Overflow)
@ -488,10 +485,7 @@ mod tests {
#[test] #[test]
fn test_encoding_error() { fn test_encoding_error() {
let req = TestRequest::with_header("content-type", "applicatjson").finish(); let req = TestRequest::with_header("content-type", "applicatjson").finish();
assert_eq!( assert_eq!(Some(ContentTypeError::ParseError), req.encoding().err());
Some(ContentTypeError::ParseError),
req.encoding().err()
);
let req = TestRequest::with_header( let req = TestRequest::with_header(
"content-type", "content-type",
@ -578,13 +572,6 @@ mod tests {
#[test] #[test]
fn test_urlencoded_error() { fn test_urlencoded_error() {
let req =
TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish();
assert_eq!(
req.urlencoded::<Info>().poll().err().unwrap(),
UrlencodedError::Chunked
);
let req = TestRequest::with_header( let req = TestRequest::with_header(
header::CONTENT_TYPE, header::CONTENT_TYPE,
"application/x-www-form-urlencoded", "application/x-www-form-urlencoded",
@ -664,8 +651,7 @@ mod tests {
} }
let mut req = HttpRequest::default(); let mut req = HttpRequest::default();
req.payload_mut() req.payload_mut().unread_data(Bytes::from_static(b"test"));
.unread_data(Bytes::from_static(b"test"));
match req.body().poll().ok().unwrap() { match req.body().poll().ok().unwrap() {
Async::Ready(bytes) => assert_eq!(bytes, Bytes::from_static(b"test")), Async::Ready(bytes) => assert_eq!(bytes, Bytes::from_static(b"test")),
_ => unreachable!("error"), _ => unreachable!("error"),

View File

@ -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;
@ -121,9 +118,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,
@ -146,6 +141,12 @@ impl HttpRequest<()> {
pub fn with_state<S>(self, state: Rc<S>, router: Router) -> HttpRequest<S> { pub fn with_state<S>(self, state: Rc<S>, router: Router) -> HttpRequest<S> {
HttpRequest(self.0, Some(state), Some(router)) HttpRequest(self.0, Some(state), Some(router))
} }
pub(crate) fn clone_with_state<S>(
&self, state: Rc<S>, router: Router,
) -> HttpRequest<S> {
HttpRequest(self.0.clone(), Some(state), Some(router))
}
} }
impl<S> HttpMessage for HttpRequest<S> { impl<S> HttpMessage for HttpRequest<S> {
@ -330,6 +331,15 @@ impl<S> HttpRequest<S> {
} }
} }
/// Generate url for named resource
///
/// This method is similar to `HttpRequest::url_for()` but it can be used
/// for urls that do not contain variable parts.
pub fn url_for_static(&self, name: &str) -> Result<Url, UrlGenerationError> {
const NO_PARAMS: [&str; 0] = [];
self.url_for(name, &NO_PARAMS)
}
/// This method returns reference to current `Router` object. /// This method returns reference to current `Router` object.
#[inline] #[inline]
pub fn router(&self) -> Option<&Router> { pub fn router(&self) -> Option<&Router> {
@ -369,20 +379,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 self.extensions().get::<Query>().is_none() {
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 +409,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 self.extensions().get::<Query>().is_none() {
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 +420,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 +437,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 +680,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,22 +710,38 @@ mod tests {
let mut resource = ResourceHandler::<()>::default(); let mut resource = ResourceHandler::<()>::default();
resource.name("index"); resource.name("index");
let routes = vec![( let routes = vec![(Resource::new("index", "/user/{name}.html"), Some(resource))];
Resource::new("index", "/user/{name}.{ext}"),
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"));
let req = req.with_state(Rc::new(()), router); let req = req.with_state(Rc::new(()), router);
let url = req.url_for("index", &["test", "html"]); let url = req.url_for("index", &["test"]);
assert_eq!( assert_eq!(
url.ok().unwrap().as_str(), url.ok().unwrap().as_str(),
"http://www.rust-lang.org/prefix/user/test.html" "http://www.rust-lang.org/prefix/user/test.html"
); );
} }
#[test]
fn test_url_for_static() {
let req = TestRequest::with_header(header::HOST, "www.rust-lang.org").finish();
let mut resource = ResourceHandler::<()>::default();
resource.name("index");
let routes = vec![(Resource::new("index", "/index.html"), Some(resource))];
let (router, _) = Router::new("/prefix/", ServerSettings::default(), routes);
assert!(router.has_route("/index.html"));
assert!(!router.has_route("/prefix/index.html"));
let req = req.with_state(Rc::new(()), router);
let url = req.url_for_static("index");
assert_eq!(
url.ok().unwrap().as_str(),
"http://www.rust-lang.org/prefix/index.html"
);
}
#[test] #[test]
fn test_url_for_external() { fn test_url_for_external() {
let req = HttpRequest::default(); let req = HttpRequest::default();

View File

@ -89,7 +89,7 @@ impl HttpResponse {
/// Constructs a error response /// Constructs a error response
#[inline] #[inline]
pub fn from_error(error: Error) -> HttpResponse { pub fn from_error(error: Error) -> HttpResponse {
let mut resp = error.cause().error_response(); let mut resp = error.as_response_error().error_response();
resp.get_mut().error = Some(error); resp.get_mut().error = Some(error);
resp resp
} }
@ -241,6 +241,14 @@ impl HttpResponse {
pub fn set_write_buffer_capacity(&mut self, cap: usize) { pub fn set_write_buffer_capacity(&mut self, cap: usize) {
self.get_mut().write_capacity = cap; self.get_mut().write_capacity = cap;
} }
pub(crate) fn into_inner(mut self) -> Box<InnerHttpResponse> {
self.0.take().unwrap()
}
pub(crate) fn from_inner(inner: Box<InnerHttpResponse>) -> HttpResponse {
HttpResponse(Some(inner), HttpResponsePool::pool())
}
} }
impl fmt::Debug for HttpResponse { impl fmt::Debug for HttpResponse {
@ -297,11 +305,13 @@ impl HttpResponseBuilder {
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// use actix_web::{HttpRequest, HttpResponse, Result, http}; /// use actix_web::{http, HttpRequest, HttpResponse, Result};
/// ///
/// fn index(req: HttpRequest) -> Result<HttpResponse> { /// fn index(req: HttpRequest) -> Result<HttpResponse> {
/// Ok(HttpResponse::Ok() /// Ok(HttpResponse::Ok()
/// .set(http::header::IfModifiedSince("Sun, 07 Nov 1994 08:48:37 GMT".parse()?)) /// .set(http::header::IfModifiedSince(
/// "Sun, 07 Nov 1994 08:48:37 GMT".parse()?,
/// ))
/// .finish()) /// .finish())
/// } /// }
/// fn main() {} /// fn main() {}
@ -455,7 +465,8 @@ impl HttpResponseBuilder {
/// .path("/") /// .path("/")
/// .secure(true) /// .secure(true)
/// .http_only(true) /// .http_only(true)
/// .finish()) /// .finish(),
/// )
/// .finish() /// .finish()
/// } /// }
/// fn main() {} /// fn main() {}
@ -466,10 +477,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 +542,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 +564,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`
@ -654,7 +658,8 @@ impl Responder for &'static str {
type Error = Error; type Error = Error;
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> { fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(req.build_response(StatusCode::OK) Ok(req
.build_response(StatusCode::OK)
.content_type("text/plain; charset=utf-8") .content_type("text/plain; charset=utf-8")
.body(self)) .body(self))
} }
@ -673,7 +678,8 @@ impl Responder for &'static [u8] {
type Error = Error; type Error = Error;
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> { fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(req.build_response(StatusCode::OK) Ok(req
.build_response(StatusCode::OK)
.content_type("application/octet-stream") .content_type("application/octet-stream")
.body(self)) .body(self))
} }
@ -692,7 +698,8 @@ impl Responder for String {
type Error = Error; type Error = Error;
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> { fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(req.build_response(StatusCode::OK) Ok(req
.build_response(StatusCode::OK)
.content_type("text/plain; charset=utf-8") .content_type("text/plain; charset=utf-8")
.body(self)) .body(self))
} }
@ -711,7 +718,8 @@ impl<'a> Responder for &'a String {
type Error = Error; type Error = Error;
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> { fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(req.build_response(StatusCode::OK) Ok(req
.build_response(StatusCode::OK)
.content_type("text/plain; charset=utf-8") .content_type("text/plain; charset=utf-8")
.body(self)) .body(self))
} }
@ -730,7 +738,8 @@ impl Responder for Bytes {
type Error = Error; type Error = Error;
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> { fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(req.build_response(StatusCode::OK) Ok(req
.build_response(StatusCode::OK)
.content_type("application/octet-stream") .content_type("application/octet-stream")
.body(self)) .body(self))
} }
@ -749,7 +758,8 @@ impl Responder for BytesMut {
type Error = Error; type Error = Error;
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> { fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(req.build_response(StatusCode::OK) Ok(req
.build_response(StatusCode::OK)
.content_type("application/octet-stream") .content_type("application/octet-stream")
.body(self)) .body(self))
} }
@ -782,12 +792,12 @@ impl<'a, S> From<&'a HttpRequest<S>> for HttpResponseBuilder {
} }
#[derive(Debug)] #[derive(Debug)]
struct InnerHttpResponse { pub(crate) struct InnerHttpResponse {
version: Option<Version>, version: Option<Version>,
headers: HeaderMap, headers: HeaderMap,
status: StatusCode, status: StatusCode,
reason: Option<&'static str>, reason: Option<&'static str>,
body: Body, pub(crate) body: Body,
chunked: Option<bool>, chunked: Option<bool>,
encoding: Option<ContentEncoding>, encoding: Option<ContentEncoding>,
connection_type: Option<ConnectionType>, connection_type: Option<ConnectionType>,
@ -796,6 +806,9 @@ struct InnerHttpResponse {
error: Option<Error>, error: Option<Error>,
} }
unsafe impl Sync for InnerHttpResponse {}
unsafe impl Send for InnerHttpResponse {}
impl InnerHttpResponse { impl InnerHttpResponse {
#[inline] #[inline]
fn new(status: StatusCode, body: Body) -> InnerHttpResponse { fn new(status: StatusCode, body: Body) -> InnerHttpResponse {
@ -822,9 +835,9 @@ 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]
@ -944,7 +957,8 @@ mod tests {
.del_cookie(&cookies[0]) .del_cookie(&cookies[0])
.finish(); .finish();
let mut val: Vec<_> = resp.headers() let mut val: Vec<_> = resp
.headers()
.get_all("Set-Cookie") .get_all("Set-Cookie")
.iter() .iter()
.map(|v| v.to_str().unwrap().to_owned()) .map(|v| v.to_str().unwrap().to_owned())
@ -975,9 +989,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 +998,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 +1045,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 +1064,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 +1073,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 +1082,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 +1091,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 +1100,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 +1109,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 +1118,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 +1127,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 +1138,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 +1151,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 +1163,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 +1173,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]

View File

@ -53,7 +53,8 @@ impl<'a> ConnectionInfo<'a> {
// scheme // scheme
if scheme.is_none() { if scheme.is_none() {
if let Some(h) = req.headers() if let Some(h) = req
.headers()
.get(HeaderName::from_str(X_FORWARDED_PROTO).unwrap()) .get(HeaderName::from_str(X_FORWARDED_PROTO).unwrap())
{ {
if let Ok(h) = h.to_str() { if let Ok(h) = h.to_str() {
@ -74,7 +75,8 @@ impl<'a> ConnectionInfo<'a> {
// host // host
if host.is_none() { if host.is_none() {
if let Some(h) = req.headers() if let Some(h) = req
.headers()
.get(HeaderName::from_str(X_FORWARDED_HOST).unwrap()) .get(HeaderName::from_str(X_FORWARDED_HOST).unwrap())
{ {
if let Ok(h) = h.to_str() { if let Ok(h) = h.to_str() {
@ -98,7 +100,8 @@ impl<'a> ConnectionInfo<'a> {
// remote addr // remote addr
if remote.is_none() { if remote.is_none() {
if let Some(h) = req.headers() if let Some(h) = req
.headers()
.get(HeaderName::from_str(X_FORWARDED_FOR).unwrap()) .get(HeaderName::from_str(X_FORWARDED_FOR).unwrap())
{ {
if let Ok(h) = h.to_str() { if let Ok(h) = h.to_str() {
@ -189,10 +192,8 @@ mod tests {
assert_eq!(info.remote(), Some("192.0.2.60")); assert_eq!(info.remote(), Some("192.0.2.60"));
let mut req = HttpRequest::default(); let mut req = HttpRequest::default();
req.headers_mut().insert( req.headers_mut()
header::HOST, .insert(header::HOST, HeaderValue::from_static("rust-lang.org"));
HeaderValue::from_static("rust-lang.org"),
);
let info = ConnectionInfo::new(&req); let info = ConnectionInfo::new(&req);
assert_eq!(info.scheme(), "http"); assert_eq!(info.scheme(), "http");

View File

@ -121,7 +121,8 @@ impl<T: Serialize> Responder for Json<T> {
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> { fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
let body = serde_json::to_string(&self.0)?; let body = serde_json::to_string(&self.0)?;
Ok(req.build_response(StatusCode::OK) Ok(req
.build_response(StatusCode::OK)
.content_type("application/json") .content_type("application/json")
.body(body)) .body(body))
} }
@ -295,7 +296,8 @@ where
} }
let limit = self.limit; let limit = self.limit;
let fut = req.from_err() let fut = req
.from_err()
.fold(BytesMut::new(), move |mut body, chunk| { .fold(BytesMut::new(), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit { if (body.len() + chunk.len()) > limit {
Err(JsonPayloadError::Overflow) Err(JsonPayloadError::Overflow)
@ -362,10 +364,7 @@ mod tests {
fn test_json_body() { fn test_json_body() {
let req = HttpRequest::default(); let req = HttpRequest::default();
let mut json = req.json::<MyObject>(); let mut json = req.json::<MyObject>();
assert_eq!( assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType);
json.poll().err().unwrap(),
JsonPayloadError::ContentType
);
let mut req = HttpRequest::default(); let mut req = HttpRequest::default();
req.headers_mut().insert( req.headers_mut().insert(
@ -373,10 +372,7 @@ mod tests {
header::HeaderValue::from_static("application/text"), header::HeaderValue::from_static("application/text"),
); );
let mut json = req.json::<MyObject>(); let mut json = req.json::<MyObject>();
assert_eq!( assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType);
json.poll().err().unwrap(),
JsonPayloadError::ContentType
);
let mut req = HttpRequest::default(); let mut req = HttpRequest::default();
req.headers_mut().insert( req.headers_mut().insert(

View File

@ -6,15 +6,15 @@
//! # use std::thread; //! # use std::thread;
//! //!
//! fn index(info: Path<(String, u32)>) -> String { //! fn index(info: Path<(String, u32)>) -> String {
//! format!("Hello {}! id:{}", info.0, info.1) //! format!("Hello {}! id:{}", info.0, info.1)
//! } //! }
//! //!
//! fn main() { //! fn main() {
//! # thread::spawn(|| { //! # thread::spawn(|| {
//! server::new( //! server::new(|| {
//! || App::new() //! App::new().resource("/{name}/{id}/index.html", |r| r.with(index))
//! .resource("/{name}/{id}/index.html", |r| r.with(index))) //! }).bind("127.0.0.1:8080")
//! .bind("127.0.0.1:8080").unwrap() //! .unwrap()
//! .run(); //! .run();
//! # }); //! # });
//! } //! }
@ -25,7 +25,7 @@
//! Besides the API documentation (which you are currently looking //! Besides the API documentation (which you are currently looking
//! at!), several other resources are available: //! at!), several other resources are available:
//! //!
//! * [User Guide](https://actix.rs/book/actix-web/) //! * [User Guide](https://actix.rs/docs/)
//! * [Chat on gitter](https://gitter.im/actix/actix) //! * [Chat on gitter](https://gitter.im/actix/actix)
//! * [GitHub repository](https://github.com/actix/actix-web) //! * [GitHub repository](https://github.com/actix/actix-web)
//! * [Cargo package](https://crates.io/crates/actix-web) //! * [Cargo package](https://crates.io/crates/actix-web)
@ -60,9 +60,24 @@
//! * 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
extern_prelude,
))] ))]
#![cfg_attr( #![cfg_attr(
feature = "cargo-clippy", feature = "cargo-clippy",
@ -169,7 +184,9 @@ 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;

View File

@ -19,16 +19,16 @@
//! //!
//! ```rust //! ```rust
//! # extern crate actix_web; //! # extern crate actix_web;
//! use actix_web::{http, App, HttpRequest, HttpResponse};
//! use actix_web::middleware::cors::Cors; //! use actix_web::middleware::cors::Cors;
//! use actix_web::{http, App, HttpRequest, HttpResponse};
//! //!
//! fn index(mut req: HttpRequest) -> &'static str { //! fn index(mut req: HttpRequest) -> &'static str {
//! "Hello world" //! "Hello world"
//! } //! }
//! //!
//! fn main() { //! fn main() {
//! let app = App::new() //! let app = App::new().configure(|app| {
//! .configure(|app| Cors::for_app(app) // <- Construct CORS middleware builder //! Cors::for_app(app) // <- Construct CORS middleware builder
//! .allowed_origin("https://www.rust-lang.org/") //! .allowed_origin("https://www.rust-lang.org/")
//! .allowed_methods(vec!["GET", "POST"]) //! .allowed_methods(vec!["GET", "POST"])
//! .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT]) //! .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT])
@ -38,7 +38,8 @@
//! r.method(http::Method::GET).f(|_| HttpResponse::Ok()); //! r.method(http::Method::GET).f(|_| HttpResponse::Ok());
//! r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed()); //! r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed());
//! }) //! })
//! .register()); //! .register()
//! });
//! } //! }
//! ``` //! ```
//! In this example custom *CORS* middleware get registered for "/index.html" //! In this example custom *CORS* middleware get registered for "/index.html"
@ -232,18 +233,20 @@ impl Cors {
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// use actix_web::{http, App, HttpResponse};
/// use actix_web::middleware::cors::Cors; /// use actix_web::middleware::cors::Cors;
/// use actix_web::{http, App, HttpResponse};
/// ///
/// fn main() { /// fn main() {
/// let app = App::new() /// let app = App::new().configure(
/// .configure(|app| Cors::for_app(app) // <- Construct CORS builder /// |app| {
/// Cors::for_app(app) // <- Construct CORS builder
/// .allowed_origin("https://www.rust-lang.org/") /// .allowed_origin("https://www.rust-lang.org/")
/// .resource("/resource", |r| { // register resource /// .resource("/resource", |r| { // register resource
/// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); /// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
/// }) /// })
/// .register() // construct CORS and return application instance /// .register()
/// ); /// }, // construct CORS and return application instance
/// );
/// } /// }
/// ``` /// ```
pub fn for_app<S: 'static>(app: App<S>) -> CorsBuilder<S> { pub fn for_app<S: 'static>(app: App<S>) -> CorsBuilder<S> {
@ -275,9 +278,7 @@ impl Cors {
/// `ResourceHandler::middleware()` method, but in that case *Cors* /// `ResourceHandler::middleware()` method, but in that case *Cors*
/// middleware wont be able to handle *OPTIONS* requests. /// middleware wont be able to handle *OPTIONS* requests.
pub fn register<S: 'static>(self, resource: &mut ResourceHandler<S>) { pub fn register<S: 'static>(self, resource: &mut ResourceHandler<S>) {
resource resource.method(Method::OPTIONS).h(|_| HttpResponse::Ok());
.method(Method::OPTIONS)
.h(|_| HttpResponse::Ok());
resource.middleware(self); resource.middleware(self);
} }
@ -304,12 +305,11 @@ impl Cors {
fn validate_allowed_method<S>( fn validate_allowed_method<S>(
&self, req: &mut HttpRequest<S>, &self, req: &mut HttpRequest<S>,
) -> Result<(), CorsError> { ) -> Result<(), CorsError> {
if let Some(hdr) = req.headers() if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_METHOD) {
.get(header::ACCESS_CONTROL_REQUEST_METHOD)
{
if let Ok(meth) = hdr.to_str() { if let Ok(meth) = hdr.to_str() {
if let Ok(method) = Method::try_from(meth) { if let Ok(method) = Method::try_from(meth) {
return self.inner return self
.inner
.methods .methods
.get(&method) .get(&method)
.and_then(|_| Some(())) .and_then(|_| Some(()))
@ -328,8 +328,8 @@ impl Cors {
match self.inner.headers { match self.inner.headers {
AllOrSome::All => Ok(()), AllOrSome::All => Ok(()),
AllOrSome::Some(ref allowed_headers) => { AllOrSome::Some(ref allowed_headers) => {
if let Some(hdr) = req.headers() if let Some(hdr) =
.get(header::ACCESS_CONTROL_REQUEST_HEADERS) req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS)
{ {
if let Ok(headers) = hdr.to_str() { if let Ok(headers) = hdr.to_str() {
let mut hdrs = HashSet::new(); let mut hdrs = HashSet::new();
@ -371,8 +371,8 @@ impl<S> Middleware<S> for Cors {
.as_str()[1..], .as_str()[1..],
).unwrap(), ).unwrap(),
) )
} else if let Some(hdr) = req.headers() } else if let Some(hdr) =
.get(header::ACCESS_CONTROL_REQUEST_HEADERS) req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS)
{ {
Some(hdr.clone()) Some(hdr.clone())
} else { } else {
@ -413,7 +413,8 @@ impl<S> Middleware<S> for Cors {
}) })
.header( .header(
header::ACCESS_CONTROL_ALLOW_METHODS, header::ACCESS_CONTROL_ALLOW_METHODS,
&self.inner &self
.inner
.methods .methods
.iter() .iter()
.fold(String::new(), |s, v| s + "," + v.as_str()) .fold(String::new(), |s, v| s + "," + v.as_str())
@ -422,7 +423,10 @@ impl<S> Middleware<S> for Cors {
.finish(), .finish(),
)) ))
} else { } else {
self.validate_origin(req)?; // Only check requests with a origin header.
if req.headers().contains_key(header::ORIGIN) {
self.validate_origin(req)?;
}
Ok(Started::Done) Ok(Started::Done)
} }
@ -493,8 +497,8 @@ impl<S> Middleware<S> for Cors {
/// ```rust /// ```rust
/// # extern crate http; /// # extern crate http;
/// # extern crate actix_web; /// # extern crate actix_web;
/// use http::header;
/// use actix_web::middleware::cors; /// use actix_web::middleware::cors;
/// use http::header;
/// ///
/// # fn main() { /// # fn main() {
/// let cors = cors::Cors::build() /// let cors = cors::Cors::build()
@ -766,12 +770,13 @@ impl<S: 'static> CorsBuilder<S> {
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// use actix_web::{http, App, HttpResponse};
/// use actix_web::middleware::cors::Cors; /// use actix_web::middleware::cors::Cors;
/// use actix_web::{http, App, HttpResponse};
/// ///
/// fn main() { /// fn main() {
/// let app = App::new() /// let app = App::new().configure(
/// .configure(|app| Cors::for_app(app) // <- Construct CORS builder /// |app| {
/// Cors::for_app(app) // <- Construct CORS builder
/// .allowed_origin("https://www.rust-lang.org/") /// .allowed_origin("https://www.rust-lang.org/")
/// .allowed_methods(vec!["GET", "POST"]) /// .allowed_methods(vec!["GET", "POST"])
/// .allowed_header(http::header::CONTENT_TYPE) /// .allowed_header(http::header::CONTENT_TYPE)
@ -783,8 +788,9 @@ impl<S: 'static> CorsBuilder<S> {
/// r.method(http::Method::HEAD) /// r.method(http::Method::HEAD)
/// .f(|_| HttpResponse::MethodNotAllowed()); /// .f(|_| HttpResponse::MethodNotAllowed());
/// }) /// })
/// .register() // construct CORS and return application instance /// .register()
/// ); /// }, // construct CORS and return application instance
/// );
/// } /// }
/// ``` /// ```
pub fn resource<F, R>(&mut self, path: &str, f: F) -> &mut CorsBuilder<S> pub fn resource<F, R>(&mut self, path: &str, f: F) -> &mut CorsBuilder<S>
@ -866,7 +872,8 @@ impl<S: 'static> CorsBuilder<S> {
} }
let cors = self.construct(); let cors = self.construct();
let mut app = self.app let mut app = self
.app
.take() .take()
.expect("CorsBuilder has to be constructed with Cors::for_app(app)"); .expect("CorsBuilder has to be constructed with Cors::for_app(app)");
@ -1002,16 +1009,15 @@ mod tests {
assert!(cors.start(&mut req).unwrap().is_done()); assert!(cors.start(&mut req).unwrap().is_done());
} }
#[test] // #[test]
#[should_panic(expected = "MissingOrigin")] // #[should_panic(expected = "MissingOrigin")]
fn test_validate_missing_origin() { // fn test_validate_missing_origin() {
let cors = Cors::build() // let mut cors = Cors::build()
.allowed_origin("https://www.example.com") // .allowed_origin("https://www.example.com")
.finish(); // .finish();
// let mut req = HttpRequest::default();
let mut req = HttpRequest::default(); // cors.start(&mut req).unwrap();
cors.start(&mut req).unwrap(); // }
}
#[test] #[test]
#[should_panic(expected = "OriginNotAllowed")] #[should_panic(expected = "OriginNotAllowed")]
@ -1094,9 +1100,8 @@ mod tests {
resp.headers().get(header::VARY).unwrap().as_bytes() resp.headers().get(header::VARY).unwrap().as_bytes()
); );
let resp: HttpResponse = HttpResponse::Ok() let resp: HttpResponse =
.header(header::VARY, "Accept") HttpResponse::Ok().header(header::VARY, "Accept").finish();
.finish();
let resp = cors.response(&mut req, resp).unwrap().response(); let resp = cors.response(&mut req, resp).unwrap().response();
assert_eq!( assert_eq!(
&b"Accept, Origin"[..], &b"Accept, Origin"[..],
@ -1129,11 +1134,21 @@ mod tests {
}) })
}); });
let request = srv.get().uri(srv.url("/test")).finish().unwrap(); let request = srv
.get()
.uri(srv.url("/test"))
.header("ORIGIN", "https://www.example2.com")
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap(); let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::BAD_REQUEST); assert_eq!(response.status(), StatusCode::BAD_REQUEST);
let request = srv.get() let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::OK);
let request = srv
.get()
.uri(srv.url("/test")) .uri(srv.url("/test"))
.header("ORIGIN", "https://www.example.com") .header("ORIGIN", "https://www.example.com")
.finish() .finish()

View File

@ -112,9 +112,7 @@ mod tests {
}; };
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
let resp = HttpResponse::Ok() let resp = HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish();
.header(CONTENT_TYPE, "0002")
.finish();
let resp = match mw.response(&mut req, resp) { let resp = match mw.response(&mut req, resp) {
Ok(Response::Done(resp)) => resp, Ok(Response::Done(resp)) => resp,
_ => panic!(), _ => panic!(),

View File

@ -179,7 +179,8 @@ impl<S: 'static, T: IdentityPolicy<S>> Middleware<S> for IdentityService<T> {
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.backend let fut = self
.backend
.from_request(&mut req) .from_request(&mut req)
.then(move |res| match res { .then(move |res| match res {
Ok(id) => { Ok(id) => {

View File

@ -376,9 +376,7 @@ mod tests {
headers, headers,
None, None,
); );
let resp = HttpResponse::build(StatusCode::OK) let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
.force_close()
.finish();
let entry_time = time::now(); let entry_time = time::now();
let render = |fmt: &mut Formatter| { let render = |fmt: &mut Formatter| {
@ -399,9 +397,7 @@ mod tests {
HeaderMap::new(), HeaderMap::new(),
None, None,
); );
let resp = HttpResponse::build(StatusCode::OK) let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
.force_close()
.finish();
let entry_time = time::now(); let entry_time = time::now();
let render = |fmt: &mut Formatter| { let render = |fmt: &mut Formatter| {

View File

@ -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,17 +255,14 @@ 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) Ok(sess) => {
.then(move |res| match res { req.extensions_mut()
Ok(sess) => { .insert(Arc::new(SessionImplCell(RefCell::new(Box::new(sess)))));
req.extensions_mut().insert(Arc::new(SessionImplCell( FutOk(None)
RefCell::new(Box::new(sess)), }
))); Err(err) => FutErr(err),
FutOk(None) });
}
Err(err) => FutErr(err),
});
Ok(Started::Future(Box::new(fut))) Ok(Started::Future(Box::new(fut)))
} }
@ -551,4 +577,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());
}
} }

View File

@ -122,11 +122,7 @@ where
if let Some(err) = self.error.take() { if let Some(err) = self.error.take() {
Err(err) Err(err)
} else if self.safety.current() { } else if self.safety.current() {
self.inner self.inner.as_mut().unwrap().borrow_mut().poll(&self.safety)
.as_mut()
.unwrap()
.borrow_mut()
.poll(&self.safety)
} else { } else {
Ok(Async::NotReady) Ok(Async::NotReady)
} }
@ -175,11 +171,13 @@ where
Async::NotReady => Ok(Async::NotReady), Async::NotReady => Ok(Async::NotReady),
Async::Ready(None) => Err(MultipartError::Incomplete), Async::Ready(None) => Err(MultipartError::Incomplete),
Async::Ready(Some(chunk)) => { Async::Ready(Some(chunk)) => {
if chunk.len() == boundary.len() + 4 && &chunk[..2] == b"--" if chunk.len() == boundary.len() + 4
&& &chunk[..2] == b"--"
&& &chunk[2..boundary.len() + 2] == boundary.as_bytes() && &chunk[2..boundary.len() + 2] == boundary.as_bytes()
{ {
Ok(Async::Ready(false)) Ok(Async::Ready(false))
} else if chunk.len() == boundary.len() + 6 && &chunk[..2] == b"--" } else if chunk.len() == boundary.len() + 6
&& &chunk[..2] == b"--"
&& &chunk[2..boundary.len() + 2] == boundary.as_bytes() && &chunk[2..boundary.len() + 2] == boundary.as_bytes()
&& &chunk[boundary.len() + 2..boundary.len() + 4] == b"--" && &chunk[boundary.len() + 2..boundary.len() + 4] == b"--"
{ {
@ -513,14 +511,18 @@ where
match payload.read_exact(boundary.len() + 4)? { match payload.read_exact(boundary.len() + 4)? {
Async::NotReady => Ok(Async::NotReady), Async::NotReady => Ok(Async::NotReady),
Async::Ready(None) => Err(MultipartError::Incomplete), Async::Ready(None) => Err(MultipartError::Incomplete),
Async::Ready(Some(chunk)) => { Async::Ready(Some(mut chunk)) => {
if &chunk[..2] == b"\r\n" && &chunk[2..4] == b"--" if &chunk[..2] == b"\r\n"
&& &chunk[2..4] == b"--"
&& &chunk[4..] == boundary.as_bytes() && &chunk[4..] == boundary.as_bytes()
{ {
payload.unread_data(chunk); payload.unread_data(chunk);
Ok(Async::Ready(None)) Ok(Async::Ready(None))
} else { } else {
Ok(Async::Ready(Some(chunk))) // \r might be part of data stream
let ch = chunk.split_to(1);
payload.unread_data(chunk);
Ok(Async::Ready(Some(ch)))
} }
} }
} }

View File

@ -58,6 +58,15 @@ impl<'a> Params<'a> {
self.0.push((name, value)); self.0.push((name, value));
} }
pub(crate) fn remove(&mut self, name: &str) {
for idx in (0..self.0.len()).rev() {
if self.0[idx].0 == name {
self.0.remove(idx);
return;
}
}
}
/// Check if there are any matched patterns /// Check if there are any matched patterns
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.0.is_empty() self.0.is_empty()

View File

@ -671,10 +671,7 @@ mod tests {
let (mut sender, payload) = Payload::new(false); let (mut sender, payload) = Payload::new(false);
let mut payload = PayloadHelper::new(payload); let mut payload = PayloadHelper::new(payload);
assert_eq!( assert_eq!(Async::NotReady, payload.read_until(b"ne").ok().unwrap());
Async::NotReady,
payload.read_until(b"ne").ok().unwrap()
);
sender.feed_data(Bytes::from("line1")); sender.feed_data(Bytes::from("line1"));
sender.feed_data(Bytes::from("line2")); sender.feed_data(Bytes::from("line2"));

View File

@ -148,6 +148,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 +193,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()
)
} }
_ => (), _ => (),
} }
@ -259,7 +262,7 @@ impl<S: 'static, H: PipelineHandler<S>> StartMiddlewares<S, H> {
_s: PhantomData, _s: PhantomData,
}) })
} }
Err(err) => return ProcessResponse::init(err.into()), Err(err) => return RunMiddlewares::init(info, err.into()),
} }
} }
} }
@ -275,12 +278,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));
} }
if info.count == len { loop {
let reply = unsafe { &mut *self.hnd.get() } if info.count == len {
.handle(info.req().clone(), self.htype); let reply = unsafe { &mut *self.hnd.get() }
return Some(WaitingResponse::init(info, reply)); .handle(info.req().clone(), self.htype);
} else { return Some(WaitingResponse::init(info, reply));
loop { } else {
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)) => {
@ -291,13 +294,13 @@ impl<S: 'static, H: PipelineHandler<S>> StartMiddlewares<S, H> {
continue 'outer; continue 'outer;
} }
Err(err) => { Err(err) => {
return Some(ProcessResponse::init(err.into())) return Some(RunMiddlewares::init(info, err.into()))
} }
} }
} }
} }
} }
Err(err) => return Some(ProcessResponse::init(err.into())), Err(err) => return Some(RunMiddlewares::init(info, err.into())),
} }
} }
} }
@ -344,6 +347,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);
@ -676,6 +680,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)

View File

@ -181,11 +181,7 @@ pub fn Header<S: 'static>(
} }
#[doc(hidden)] #[doc(hidden)]
pub struct HeaderPredicate<S>( pub struct HeaderPredicate<S>(header::HeaderName, header::HeaderValue, PhantomData<S>);
header::HeaderName,
header::HeaderValue,
PhantomData<S>,
);
impl<S: 'static> Predicate<S> for HeaderPredicate<S> { impl<S: 'static> Predicate<S> for HeaderPredicate<S> {
fn check(&self, req: &mut HttpRequest<S>) -> bool { fn check(&self, req: &mut HttpRequest<S>) -> bool {
@ -196,6 +192,45 @@ impl<S: 'static> Predicate<S> for HeaderPredicate<S> {
} }
} }
/// Return predicate that matches if request contains specified Host name.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{pred, App, HttpResponse};
///
/// fn main() {
/// App::new().resource("/index.html", |r| {
/// r.route()
/// .filter(pred::Host("www.rust-lang.org"))
/// .f(|_| HttpResponse::MethodNotAllowed())
/// });
/// }
/// ```
pub fn Host<S: 'static, H: AsRef<str>>(host: H) -> HostPredicate<S> {
HostPredicate(host.as_ref().to_string(), None, PhantomData)
}
#[doc(hidden)]
pub struct HostPredicate<S>(String, Option<String>, PhantomData<S>);
impl<S> HostPredicate<S> {
/// Set reuest scheme to match
pub fn scheme<H: AsRef<str>>(&mut self, scheme: H) {
self.1 = Some(scheme.as_ref().to_string())
}
}
impl<S: 'static> Predicate<S> for HostPredicate<S> {
fn check(&self, req: &mut HttpRequest<S>) -> bool {
let info = req.connection_info();
if let Some(ref scheme) = self.1 {
self.0 == info.host() && scheme == info.scheme()
} else {
self.0 == info.host()
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -228,6 +263,28 @@ mod tests {
assert!(!pred.check(&mut req)); assert!(!pred.check(&mut req));
} }
#[test]
fn test_host() {
let mut headers = HeaderMap::new();
headers.insert(
header::HOST,
header::HeaderValue::from_static("www.rust-lang.org"),
);
let mut req = HttpRequest::new(
Method::GET,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
headers,
None,
);
let pred = Host("www.rust-lang.org");
assert!(pred.check(&mut req));
let pred = Host("localhost");
assert!(!pred.check(&mut req));
}
#[test] #[test]
fn test_methods() { fn test_methods() {
let mut req = HttpRequest::new( let mut req = HttpRequest::new(

View File

@ -134,10 +134,7 @@ impl<S: 'static> ResourceHandler<S> {
/// ``` /// ```
pub fn method(&mut self, method: Method) -> &mut Route<S> { pub fn method(&mut self, method: Method) -> &mut Route<S> {
self.routes.push(Route::default()); self.routes.push(Route::default());
self.routes self.routes.last_mut().unwrap().filter(pred::Method(method))
.last_mut()
.unwrap()
.filter(pred::Method(method))
} }
/// Register a new route and add handler object. /// Register a new route and add handler object.

View File

@ -5,13 +5,17 @@ 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, WithAsync}; use with::{ExtractorConfig, With, With2, With3, WithAsync};
@ -62,13 +66,12 @@ impl<S: 'static> Route<S> {
/// # extern crate actix_web; /// # extern crate actix_web;
/// # use actix_web::*; /// # use actix_web::*;
/// # fn main() { /// # fn main() {
/// App::new() /// App::new().resource("/path", |r| {
/// .resource("/path", |r| /// r.route()
/// r.route() /// .filter(pred::Get())
/// .filter(pred::Get()) /// .filter(pred::Header("content-type", "text/plain"))
/// .filter(pred::Header("content-type", "text/plain")) /// .f(|req| HttpResponse::Ok())
/// .f(|req| HttpResponse::Ok()) /// })
/// )
/// # .finish(); /// # .finish();
/// # } /// # }
/// ``` /// ```
@ -111,7 +114,7 @@ impl<S: 'static> Route<S> {
/// # extern crate actix_web; /// # extern crate actix_web;
/// # extern crate futures; /// # extern crate futures;
/// #[macro_use] extern crate serde_derive; /// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Path, Result, http}; /// use actix_web::{http, App, Path, Result};
/// ///
/// #[derive(Deserialize)] /// #[derive(Deserialize)]
/// struct Info { /// struct Info {
@ -125,8 +128,9 @@ impl<S: 'static> Route<S> {
/// ///
/// fn main() { /// fn main() {
/// let app = App::new().resource( /// let app = App::new().resource(
/// "/{username}/index.html", // <- define path parameters /// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor /// |r| r.method(http::Method::GET).with(index),
/// ); // <- use `with` extractor
/// } /// }
/// ``` /// ```
/// ///
@ -139,7 +143,7 @@ impl<S: 'static> Route<S> {
/// # extern crate futures; /// # extern crate futures;
/// #[macro_use] extern crate serde_derive; /// #[macro_use] extern crate serde_derive;
/// # use std::collections::HashMap; /// # use std::collections::HashMap;
/// use actix_web::{http, App, Query, Path, Result, Json}; /// use actix_web::{http, App, Json, Path, Query, Result};
/// ///
/// #[derive(Deserialize)] /// #[derive(Deserialize)]
/// struct Info { /// struct Info {
@ -147,14 +151,17 @@ impl<S: 'static> Route<S> {
/// } /// }
/// ///
/// /// extract path info using serde /// /// extract path info using serde
/// fn index(info: (Path<Info>, Query<HashMap<String, String>>, Json<Info>)) -> Result<String> { /// fn index(
/// info: (Path<Info>, Query<HashMap<String, String>>, Json<Info>),
/// ) -> Result<String> {
/// Ok(format!("Welcome {}!", info.0.username)) /// Ok(format!("Welcome {}!", info.0.username))
/// } /// }
/// ///
/// fn main() { /// fn main() {
/// let app = App::new().resource( /// let app = App::new().resource(
/// "/{username}/index.html", // <- define path parameters /// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor /// |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>
@ -177,7 +184,7 @@ impl<S: 'static> Route<S> {
/// # extern crate actix_web; /// # extern crate actix_web;
/// # extern crate futures; /// # extern crate futures;
/// #[macro_use] extern crate serde_derive; /// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Path, Error, http}; /// use actix_web::{http, App, Error, Path};
/// use futures::Future; /// use futures::Future;
/// ///
/// #[derive(Deserialize)] /// #[derive(Deserialize)]
@ -186,15 +193,15 @@ impl<S: 'static> Route<S> {
/// } /// }
/// ///
/// /// extract path info using serde /// /// extract path info using serde
/// fn index(info: Path<Info>) -> Box<Future<Item=&'static str, Error=Error>> { /// fn index(info: Path<Info>) -> Box<Future<Item = &'static str, Error = Error>> {
/// unimplemented!() /// unimplemented!()
/// } /// }
/// ///
/// fn main() { /// fn main() {
/// let app = App::new().resource( /// let app = App::new().resource(
/// "/{username}/index.html", // <- define path parameters /// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET) /// |r| r.method(http::Method::GET).with_async(index),
/// .with_async(index)); // <- use `with` extractor /// ); // <- use `with` extractor
/// } /// }
/// ``` /// ```
pub fn with_async<T, F, R, I, E>(&mut self, handler: F) -> ExtractorConfig<S, T> pub fn with_async<T, F, R, I, E>(&mut self, handler: F) -> ExtractorConfig<S, T>
@ -218,7 +225,7 @@ impl<S: 'static> Route<S> {
/// # extern crate actix_web; /// # extern crate actix_web;
/// # extern crate futures; /// # extern crate futures;
/// #[macro_use] extern crate serde_derive; /// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Query, Path, Result, http}; /// use actix_web::{http, App, Path, Query, Result};
/// ///
/// #[derive(Deserialize)] /// #[derive(Deserialize)]
/// struct PParam { /// struct PParam {
@ -237,8 +244,9 @@ impl<S: 'static> Route<S> {
/// ///
/// fn main() { /// fn main() {
/// let app = App::new().resource( /// let app = App::new().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).with2(index),
/// ); // <- use `with` extractor
/// } /// }
/// ``` /// ```
pub fn with2<T1, T2, F, R>( pub fn with2<T1, T2, F, R>(
@ -296,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]
@ -309,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]
@ -424,7 +428,7 @@ impl<S: 'static> StartMiddlewares<S> {
_s: PhantomData, _s: PhantomData,
}) })
} }
Err(err) => return FinishingMiddlewares::init(info, err.into()), Err(err) => return RunMiddlewares::init(info, err.into()),
} }
} }
} }
@ -440,11 +444,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));
} }
if info.count == len { loop {
let reply = info.handler.handle(info.req.clone()); if info.count == len {
return Some(WaitingResponse::init(info, reply)); let reply = info.handler.handle(info.req.clone());
} else { return Some(WaitingResponse::init(info, reply));
loop { } else {
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)) => {
@ -455,16 +459,13 @@ impl<S: 'static> StartMiddlewares<S> {
continue 'outer; continue 'outer;
} }
Err(err) => { Err(err) => {
return Some(FinishingMiddlewares::init( return Some(RunMiddlewares::init(info, err.into()))
info,
err.into(),
))
} }
} }
} }
} }
} }
Err(err) => return Some(FinishingMiddlewares::init(info, err.into())), Err(err) => return Some(RunMiddlewares::init(info, err.into())),
} }
} }
} }

View File

@ -216,7 +216,8 @@ impl Resource {
Ok(re) => re, Ok(re) => re,
Err(err) => panic!("Wrong path pattern: \"{}\" {}", path, err), Err(err) => panic!("Wrong path pattern: \"{}\" {}", path, err),
}; };
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, len) PatternType::Dynamic(re, names, len)
@ -310,8 +311,16 @@ impl Resource {
None None
} }
} }
PatternType::Prefix(ref s) => if path.starts_with(s) { PatternType::Prefix(ref s) => if path == s {
Some(s.len()) Some(s.len())
} else if path.starts_with(s)
&& (s.ends_with('/') || path.split_at(s.len()).1.starts_with('/'))
{
if s.ends_with('/') {
Some(s.len() - 1)
} else {
Some(s.len())
}
} else { } else {
None None
}, },
@ -326,22 +335,41 @@ impl Resource {
U: IntoIterator<Item = I>, U: IntoIterator<Item = I>,
I: AsRef<str>, I: AsRef<str>,
{ {
let mut iter = elements.into_iter(); let mut path = match self.tp {
let mut path = if self.rtp != ResourceType::External { PatternType::Prefix(ref p) => p.to_owned(),
format!("{}/", router.prefix()) PatternType::Static(ref p) => p.to_owned(),
} else { PatternType::Dynamic(..) => {
String::new() let mut path = String::new();
}; let mut iter = elements.into_iter();
for el in &self.elements { for el in &self.elements {
match *el { match *el {
PatternElement::Str(ref s) => path.push_str(s), PatternElement::Str(ref s) => path.push_str(s),
PatternElement::Var(_) => { PatternElement::Var(_) => {
if let Some(val) = iter.next() { if let Some(val) = iter.next() {
path.push_str(val.as_ref()) path.push_str(val.as_ref())
} else { } else {
return Err(UrlGenerationError::NotEnoughElements); return Err(UrlGenerationError::NotEnoughElements);
}
}
} }
} }
path
}
};
if self.rtp != ResourceType::External {
let prefix = router.prefix();
if prefix.ends_with('/') {
if path.starts_with('/') {
path.insert_str(0, &prefix[..prefix.len() - 1]);
} else {
path.insert_str(0, prefix);
}
} else {
if !path.starts_with('/') {
path.insert(0, '/');
}
path.insert_str(0, prefix);
} }
} }
Ok(path) Ok(path)
@ -406,6 +434,10 @@ impl Resource {
} }
} }
if !el.is_empty() {
elems.push(PatternElement::Str(el.clone()));
}
let re = if is_dynamic { let re = if is_dynamic {
if !for_prefix { if !for_prefix {
re1.push('$'); re1.push('$');
@ -438,12 +470,9 @@ mod tests {
use test::TestRequest; use test::TestRequest;
#[test] #[test]
fn test_recognizer() { fn test_recognizer10() {
let routes = vec![ let routes = vec![
( (Resource::new("", "/name"), Some(ResourceHandler::default())),
Resource::new("", "/name"),
Some(ResourceHandler::default()),
),
( (
Resource::new("", "/name/{val}"), Resource::new("", "/name/{val}"),
Some(ResourceHandler::default()), Some(ResourceHandler::default()),
@ -464,6 +493,10 @@ mod tests {
Resource::new("", "/v/{tail:.*}"), Resource::new("", "/v/{tail:.*}"),
Some(ResourceHandler::default()), Some(ResourceHandler::default()),
), ),
(
Resource::new("", "/test2/{test}.html"),
Some(ResourceHandler::default()),
),
( (
Resource::new("", "{test}/index.html"), Resource::new("", "{test}/index.html"),
Some(ResourceHandler::default()), Some(ResourceHandler::default()),
@ -501,8 +534,12 @@ mod tests {
"blah-blah/index.html" "blah-blah/index.html"
); );
let mut req = TestRequest::with_uri("/bbb/index.html").finish(); let mut req = TestRequest::with_uri("/test2/index.html").finish();
assert_eq!(rec.recognize(&mut req), Some(6)); assert_eq!(rec.recognize(&mut req), Some(6));
assert_eq!(req.match_info().get("test").unwrap(), "index");
let mut req = TestRequest::with_uri("/bbb/index.html").finish();
assert_eq!(rec.recognize(&mut req), Some(7));
assert_eq!(req.match_info().get("test").unwrap(), "bbb"); assert_eq!(req.match_info().get("test").unwrap(), "bbb");
} }
@ -530,10 +567,7 @@ mod tests {
#[test] #[test]
fn test_recognizer_with_prefix() { fn test_recognizer_with_prefix() {
let routes = vec![ let routes = vec![
( (Resource::new("", "/name"), Some(ResourceHandler::default())),
Resource::new("", "/name"),
Some(ResourceHandler::default()),
),
( (
Resource::new("", "/name/{val}"), Resource::new("", "/name/{val}"),
Some(ResourceHandler::default()), Some(ResourceHandler::default()),
@ -554,10 +588,7 @@ mod tests {
// same patterns // same patterns
let routes = vec![ let routes = vec![
( (Resource::new("", "/name"), Some(ResourceHandler::default())),
Resource::new("", "/name"),
Some(ResourceHandler::default()),
),
( (
Resource::new("", "/name/{val}"), Resource::new("", "/name/{val}"),
Some(ResourceHandler::default()), Some(ResourceHandler::default()),

View File

@ -10,8 +10,10 @@ 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 pred::Predicate;
use resource::ResourceHandler; use resource::ResourceHandler;
use router::Resource; use router::Resource;
@ -36,12 +38,12 @@ type NestedInfo<S> = (Resource, Route<S>, Vec<Box<Predicate<S>>>);
/// use actix_web::{http, App, HttpRequest, HttpResponse}; /// use actix_web::{http, App, HttpRequest, HttpResponse};
/// ///
/// fn main() { /// fn main() {
/// let app = App::new() /// let app = App::new().scope("/{project_id}/", |scope| {
/// .scope("/{project_id}/", |scope| { /// scope
/// scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok())) /// .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()))
/// }); /// });
/// } /// }
/// ``` /// ```
/// ///
@ -87,13 +89,14 @@ impl<S: 'static> Scope<S> {
/// } /// }
/// ///
/// fn main() { /// fn main() {
/// let app = App::new() /// let app = App::new().scope("/app", |scope| {
/// .scope("/app", |scope| { /// scope
/// scope.filter(pred::Header("content-type", "text/plain")) /// .filter(pred::Header("content-type", "text/plain"))
/// .route("/test1", http::Method::GET, index) /// .route("/test1", http::Method::GET, index)
/// .route("/test2", http::Method::POST, /// .route("/test2", http::Method::POST, |_: HttpRequest| {
/// |_: HttpRequest| HttpResponse::MethodNotAllowed()) /// HttpResponse::MethodNotAllowed()
/// }); /// })
/// });
/// } /// }
/// ``` /// ```
pub fn filter<T: Predicate<S> + 'static>(mut self, p: T) -> Self { pub fn filter<T: Predicate<S> + 'static>(mut self, p: T) -> Self {
@ -114,12 +117,11 @@ impl<S: 'static> Scope<S> {
/// } /// }
/// ///
/// fn main() { /// fn main() {
/// let app = App::new() /// let app = App::new().scope("/app", |scope| {
/// .scope("/app", |scope| { /// scope.with_state("/state2", AppState, |scope| {
/// scope.with_state("/state2", AppState, |scope| { /// scope.resource("/test1", |r| r.f(index))
/// scope.resource("/test1", |r| r.f(index)) /// })
/// }) /// });
/// });
/// } /// }
/// ``` /// ```
pub fn with_state<F, T: 'static>(mut self, path: &str, state: T, f: F) -> Scope<S> pub fn with_state<F, T: 'static>(mut self, path: &str, state: T, f: F) -> Scope<S>
@ -135,14 +137,6 @@ impl<S: 'static> Scope<S> {
}; };
let mut scope = f(scope); let mut scope = f(scope);
let mut path = path.trim().trim_right_matches('/').to_owned();
if !path.is_empty() && !path.starts_with('/') {
path.insert(0, '/')
}
if !path.ends_with('/') {
path.push('/');
}
let state = Rc::new(state); let state = Rc::new(state);
let filters: Vec<Box<Predicate<S>>> = vec![Box::new(FiltersWrapper { let filters: Vec<Box<Predicate<S>>> = vec![Box::new(FiltersWrapper {
state: Rc::clone(&state), state: Rc::clone(&state),
@ -168,12 +162,9 @@ impl<S: 'static> Scope<S> {
/// } /// }
/// ///
/// fn main() { /// fn main() {
/// let app = App::with_state(AppState) /// let app = App::with_state(AppState).scope("/app", |scope| {
/// .scope("/app", |scope| { /// scope.nested("/v1", |scope| scope.resource("/test1", |r| r.f(index)))
/// scope.nested("/v1", |scope| { /// });
/// scope.resource("/test1", |r| r.f(index))
/// })
/// });
/// } /// }
/// ``` /// ```
pub fn nested<F>(mut self, path: &str, f: F) -> Scope<S> pub fn nested<F>(mut self, path: &str, f: F) -> Scope<S>
@ -189,14 +180,6 @@ impl<S: 'static> Scope<S> {
}; };
let mut scope = f(scope); let mut scope = f(scope);
let mut path = path.trim().trim_right_matches('/').to_owned();
if !path.is_empty() && !path.starts_with('/') {
path.insert(0, '/')
}
if !path.ends_with('/') {
path.push('/');
}
let filters = scope.take_filters(); let filters = scope.take_filters();
self.nested.push(( self.nested.push((
Resource::prefix("", &path), Resource::prefix("", &path),
@ -225,12 +208,13 @@ impl<S: 'static> Scope<S> {
/// } /// }
/// ///
/// fn main() { /// fn main() {
/// let app = App::new() /// let app = App::new().scope("/app", |scope| {
/// .scope("/app", |scope| { /// scope.route("/test1", http::Method::GET, index).route(
/// scope.route("/test1", http::Method::GET, index) /// "/test2",
/// .route("/test2", http::Method::POST, /// http::Method::POST,
/// |_: HttpRequest| HttpResponse::MethodNotAllowed()) /// |_: HttpRequest| HttpResponse::MethodNotAllowed(),
/// }); /// )
/// });
/// } /// }
/// ``` /// ```
pub fn route<T, F, R>(mut self, path: &str, method: Method, f: F) -> Scope<S> pub fn route<T, F, R>(mut self, path: &str, method: Method, f: F) -> Scope<S>
@ -251,7 +235,12 @@ impl<S: 'static> Scope<S> {
let mut handler = ResourceHandler::default(); let mut handler = ResourceHandler::default();
handler.method(method).with(f); handler.method(method).with(f);
let pattern = Resource::new(handler.get_name(), path); let pattern = Resource::with_prefix(
handler.get_name(),
path,
if path.is_empty() { "" } else { "/" },
false,
);
Rc::get_mut(&mut self.resources) Rc::get_mut(&mut self.resources)
.expect("Can not use after configuration") .expect("Can not use after configuration")
.push((pattern, Rc::new(UnsafeCell::new(handler)))); .push((pattern, Rc::new(UnsafeCell::new(handler))));
@ -270,17 +259,16 @@ impl<S: 'static> Scope<S> {
/// use actix_web::*; /// use actix_web::*;
/// ///
/// fn main() { /// fn main() {
/// let app = App::new() /// let app = App::new().scope("/api", |scope| {
/// .scope("/api", |scope| { /// scope.resource("/users/{userid}/{friend}", |r| {
/// scope.resource("/users/{userid}/{friend}", |r| { /// r.get().f(|_| HttpResponse::Ok());
/// r.get().f(|_| HttpResponse::Ok()); /// r.head().f(|_| HttpResponse::MethodNotAllowed());
/// r.head().f(|_| HttpResponse::MethodNotAllowed()); /// r.route()
/// r.route() /// .filter(pred::Any(pred::Get()).or(pred::Put()))
/// .filter(pred::Any(pred::Get()).or(pred::Put())) /// .filter(pred::Header("Content-Type", "text/plain"))
/// .filter(pred::Header("Content-Type", "text/plain")) /// .f(|_| HttpResponse::Ok())
/// .f(|_| HttpResponse::Ok()) /// })
/// }) /// });
/// });
/// } /// }
/// ``` /// ```
pub fn resource<F, R>(mut self, path: &str, f: F) -> Scope<S> pub fn resource<F, R>(mut self, path: &str, f: F) -> Scope<S>
@ -291,7 +279,12 @@ impl<S: 'static> Scope<S> {
let mut handler = ResourceHandler::default(); let mut handler = ResourceHandler::default();
f(&mut handler); f(&mut handler);
let pattern = Resource::new(handler.get_name(), path); let pattern = Resource::with_prefix(
handler.get_name(),
path,
if path.is_empty() { "" } else { "/" },
false,
);
Rc::get_mut(&mut self.resources) Rc::get_mut(&mut self.resources)
.expect("Can not use after configuration") .expect("Can not use after configuration")
.push((pattern, Rc::new(UnsafeCell::new(handler)))); .push((pattern, Rc::new(UnsafeCell::new(handler))));
@ -327,13 +320,13 @@ impl<S: 'static> Scope<S> {
impl<S: 'static> RouteHandler<S> for Scope<S> { impl<S: 'static> RouteHandler<S> for Scope<S> {
fn handle(&mut self, mut req: HttpRequest<S>) -> AsyncResult<HttpResponse> { fn handle(&mut self, mut req: HttpRequest<S>) -> AsyncResult<HttpResponse> {
let path = unsafe { &*(&req.match_info()["tail"] as *const _) }; let path = unsafe { &*(&req.match_info()["tail"] as *const _) };
let path = if path == "" { "/" } else { path };
// recognize resources // 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() };
req.match_info_mut().remove("tail");
if self.middlewares.is_empty() { if self.middlewares.is_empty() {
let resource = unsafe { &mut *resource.get() }; let resource = unsafe { &mut *resource.get() };
return resource.handle(req, Some(default)); return resource.handle(req, Some(default));
@ -361,16 +354,12 @@ impl<S: 'static> RouteHandler<S> for Scope<S> {
continue 'outer; continue 'outer;
} }
} }
let prefix_len = len + prefix_len - 1; let prefix_len = len + prefix_len;
let path: &'static str = let path: &'static str =
unsafe { &*(&req.path()[prefix_len..] as *const _) }; unsafe { &*(&req.path()[prefix_len..] as *const _) };
req.set_prefix_len(prefix_len as u16); req.set_prefix_len(prefix_len as u16);
if path.is_empty() { req.match_info_mut().set("tail", path);
req.match_info_mut().set("tail", "/");
} else {
req.match_info_mut().set("tail", path);
}
let hnd: &mut RouteHandler<_> = let hnd: &mut RouteHandler<_> =
unsafe { (&mut *(handler.get())).as_mut() }; unsafe { (&mut *(handler.get())).as_mut() };
@ -400,8 +389,7 @@ 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)))
} }
} }
@ -527,7 +515,7 @@ impl<S: 'static> StartMiddlewares<S> {
_s: PhantomData, _s: PhantomData,
}) })
} }
Err(err) => return Response::init(err.into()), Err(err) => return RunMiddlewares::init(info, err.into()),
} }
} }
} }
@ -543,17 +531,17 @@ 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));
} }
if info.count == len { loop {
let resource = unsafe { &mut *info.resource.get() }; if info.count == len {
let reply = if let Some(ref default) = info.default { let resource = unsafe { &mut *info.resource.get() };
let d = unsafe { &mut *default.as_ref().get() }; let reply = if let Some(ref default) = info.default {
resource.handle(info.req.clone(), Some(d)) let d = unsafe { &mut *default.as_ref().get() };
resource.handle(info.req.clone(), Some(d))
} else {
resource.handle(info.req.clone(), None)
};
return Some(WaitingResponse::init(info, reply));
} else { } else {
resource.handle(info.req.clone(), None)
};
return Some(WaitingResponse::init(info, reply));
} 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)) => {
@ -563,12 +551,14 @@ impl<S: 'static> StartMiddlewares<S> {
self.fut = Some(fut); self.fut = Some(fut);
continue 'outer; continue 'outer;
} }
Err(err) => return Some(Response::init(err.into())), Err(err) => {
return Some(RunMiddlewares::init(info, err.into()))
}
} }
} }
} }
} }
Err(err) => return Some(Response::init(err.into())), Err(err) => return Some(RunMiddlewares::init(info, err.into())),
} }
} }
} }
@ -782,6 +772,59 @@ mod tests {
assert_eq!(resp.as_msg().status(), StatusCode::OK); assert_eq!(resp.as_msg().status(), StatusCode::OK);
} }
#[test]
fn test_scope_root() {
let mut app = App::new()
.scope("/app", |scope| {
scope
.resource("", |r| r.f(|_| HttpResponse::Ok()))
.resource("/", |r| r.f(|_| HttpResponse::Created()))
})
.finish();
let req = TestRequest::with_uri("/app").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/app/").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
}
#[test]
fn test_scope_root2() {
let mut app = App::new()
.scope("/app/", |scope| {
scope.resource("", |r| r.f(|_| HttpResponse::Ok()))
})
.finish();
let req = TestRequest::with_uri("/app").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/app/").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
}
#[test]
fn test_scope_root3() {
let mut app = App::new()
.scope("/app/", |scope| {
scope.resource("/", |r| r.f(|_| HttpResponse::Ok()))
})
.finish();
let req = TestRequest::with_uri("/app").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/app/").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
}
#[test] #[test]
fn test_scope_route() { fn test_scope_route() {
let mut app = App::new() let mut app = App::new()
@ -883,6 +926,71 @@ mod tests {
assert_eq!(resp.as_msg().status(), StatusCode::CREATED); assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
} }
#[test]
fn test_scope_with_state_root() {
struct State;
let mut app = App::new()
.scope("/app", |scope| {
scope.with_state("/t1", State, |scope| {
scope
.resource("", |r| r.f(|_| HttpResponse::Ok()))
.resource("/", |r| r.f(|_| HttpResponse::Created()))
})
})
.finish();
let req = TestRequest::with_uri("/app/t1").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/app/t1/").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
}
#[test]
fn test_scope_with_state_root2() {
struct State;
let mut app = App::new()
.scope("/app", |scope| {
scope.with_state("/t1/", State, |scope| {
scope.resource("", |r| r.f(|_| HttpResponse::Ok()))
})
})
.finish();
let req = TestRequest::with_uri("/app/t1").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/app/t1/").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
}
#[test]
fn test_scope_with_state_root3() {
struct State;
let mut app = App::new()
.scope("/app", |scope| {
scope.with_state("/t1/", State, |scope| {
scope.resource("/", |r| r.f(|_| HttpResponse::Ok()))
})
})
.finish();
let req = TestRequest::with_uri("/app/t1").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/app/t1/").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
}
#[test] #[test]
fn test_scope_with_state_filter() { fn test_scope_with_state_filter() {
struct State; struct State;
@ -925,6 +1033,27 @@ mod tests {
assert_eq!(resp.as_msg().status(), StatusCode::CREATED); assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
} }
#[test]
fn test_nested_scope_root() {
let mut app = App::new()
.scope("/app", |scope| {
scope.nested("/t1", |scope| {
scope
.resource("", |r| r.f(|_| HttpResponse::Ok()))
.resource("/", |r| r.f(|_| HttpResponse::Created()))
})
})
.finish();
let req = TestRequest::with_uri("/app/t1").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/app/t1/").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
}
#[test] #[test]
fn test_nested_scope_filter() { fn test_nested_scope_filter() {
let mut app = App::new() let mut app = App::new()

View File

@ -61,7 +61,7 @@ where
settings, settings,
peer, peer,
io, io,
BytesMut::with_capacity(4096), BytesMut::with_capacity(8192),
)), )),
} }
} }
@ -93,12 +93,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 +168,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 => {

View File

@ -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};
@ -31,6 +33,7 @@ pub(crate) enum PayloadType {
} }
impl PayloadType { impl PayloadType {
#[cfg(any(feature = "brotli", feature = "flate2"))]
pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType { pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType {
// check content-encoding // check content-encoding
let enc = if let Some(enc) = headers.get(CONTENT_ENCODING) { let enc = if let Some(enc) = headers.get(CONTENT_ENCODING) {
@ -50,6 +53,11 @@ impl PayloadType {
_ => PayloadType::Encoding(Box::new(EncodedPayload::new(sender, enc))), _ => PayloadType::Encoding(Box::new(EncodedPayload::new(sender, enc))),
} }
} }
#[cfg(not(any(feature = "brotli", feature = "flate2")))]
pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType {
PayloadType::Sender(sender)
}
} }
impl PayloadWriter for PayloadType { impl PayloadWriter for PayloadType {
@ -383,17 +391,21 @@ impl ContentEncoder {
) -> 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 {
Body::Empty => false, #[cfg_attr(feature = "cargo-clippy", allow(match_ref_pats))]
Body::Binary(ref bin) => { let has_body = match resp.body() {
!(response_encoding == ContentEncoding::Auto && bin.len() < 96) &Body::Empty => false,
&Body::Binary(ref bin) => {
len = bin.len();
!(response_encoding == ContentEncoding::Auto && len < 96)
} }
_ => true, _ => true,
}; };
// Enable content encoding only if response does not contain Content-Encoding // Enable content encoding only if response does not contain Content-Encoding
// header // header
#[cfg(any(feature = "brotli", feature = "flate2"))]
let mut encoding = if has_body { let mut encoding = if has_body {
let encoding = match response_encoding { let encoding = match response_encoding {
ContentEncoding::Auto => { ContentEncoding::Auto => {
@ -420,47 +432,59 @@ impl ContentEncoder {
} else { } else {
ContentEncoding::Identity ContentEncoding::Identity
}; };
#[cfg(not(any(feature = "brotli", feature = "flate2")))]
let mut encoding = ContentEncoding::Identity;
let mut transfer = match body { #[cfg_attr(feature = "cargo-clippy", allow(match_ref_pats))]
Body::Empty => { let mut transfer = match resp.body() {
&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 #[cfg(any(feature = "brotli", feature = "flate2"))]
|| encoding == ContentEncoding::Auto)
{ {
let tmp = SharedBytes::default(); if !(encoding == ContentEncoding::Identity
let transfer = TransferEncoding::eof(tmp.clone()); || encoding == ContentEncoding::Auto)
let mut enc = match encoding { {
#[cfg(feature = "flate2")] let tmp = SharedBytes::default();
ContentEncoding::Deflate => ContentEncoder::Deflate( let transfer = TransferEncoding::eof(tmp.clone());
DeflateEncoder::new(transfer, Compression::fast()), let mut enc = match encoding {
), #[cfg(feature = "flate2")]
#[cfg(feature = "flate2")] ContentEncoding::Deflate => ContentEncoder::Deflate(
ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new( DeflateEncoder::new(transfer, Compression::fast()),
transfer, ),
Compression::fast(), #[cfg(feature = "flate2")]
)), ContentEncoding::Gzip => ContentEncoder::Gzip(
#[cfg(feature = "brotli")] GzEncoder::new(transfer, Compression::fast()),
ContentEncoding::Br => { ),
ContentEncoder::Br(BrotliEncoder::new(transfer, 3)) #[cfg(feature = "brotli")]
} ContentEncoding::Br => {
ContentEncoding::Identity => ContentEncoder::Identity(transfer), ContentEncoder::Br(BrotliEncoder::new(transfer, 3))
ContentEncoding::Auto => unreachable!(), }
}; ContentEncoding::Identity | ContentEncoding::Auto => {
// TODO return error! unreachable!()
let _ = enc.write(bytes.clone()); }
let _ = enc.write_eof(); };
*bytes = Binary::from(tmp.take()); let bin = resp.replace_body(Body::Empty).binary();
encoding = ContentEncoding::Identity;
// 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 +494,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");
@ -481,15 +505,19 @@ impl ContentEncoder {
} }
TransferEncoding::eof(buf) TransferEncoding::eof(buf)
} else { } else {
if !(encoding == ContentEncoding::Identity
|| encoding == ContentEncoding::Auto)
{
resp.headers_mut().remove(CONTENT_LENGTH);
}
ContentEncoder::streaming_encoding(buf, version, resp) ContentEncoder::streaming_encoding(buf, version, resp)
} }
} }
}; };
// // 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 {
@ -590,7 +618,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 +630,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 +639,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 +648,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 +791,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 +804,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,15 +877,13 @@ 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.
pub fn parse(raw: &str) -> ContentEncoding { pub fn parse(raw: &str) -> ContentEncoding {
let mut encodings: Vec<_> = raw.replace(' ', "") let mut encodings: Vec<_> = raw
.replace(' ', "")
.split(',') .split(',')
.map(|l| AcceptEncoding::new(l)) .map(|l| AcceptEncoding::new(l))
.collect(); .collect();
@ -879,9 +906,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(),

View File

@ -146,10 +146,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 +160,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 +206,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
@ -270,7 +270,12 @@ where
debug!("Error sending data: {}", err); debug!("Error sending data: {}", err);
return Err(()); return Err(());
} }
_ => (), Ok(Async::Ready(_)) => {
// non consumed payload in that case close connection
if self.payload.is_some() && self.tasks.is_empty() {
return Ok(Async::Ready(false));
}
}
} }
} }
@ -324,7 +329,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 +381,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 +390,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 +403,7 @@ where
}; };
payload.set_error(e); payload.set_error(e);
} }
break;
} }
} }
} }
@ -398,8 +435,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 +504,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 +750,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"),
} }
@ -635,7 +768,8 @@ mod tests {
let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); let msg = reader.decode(&mut buf, &settings).unwrap().unwrap();
let req = HttpRequest::from_message(msg.message()); let req = HttpRequest::from_message(msg.message());
let val: Vec<_> = req.headers() let val: Vec<_> = req
.headers()
.get_all("Set-Cookie") .get_all("Set-Cookie")
.iter() .iter()
.map(|v| v.to_str().unwrap().to_owned()) .map(|v| v.to_str().unwrap().to_owned())
@ -918,13 +1052,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 +1133,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 +1151,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());

View File

@ -60,22 +60,16 @@ impl H1Decoder {
} }
} }
match self.parse_message(src, settings) match self
.parse_message(src, settings)
.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: self.decoder.is_some(),
payload: true, }))
}))
} else {
Ok(Some(Message::Message {
msg,
payload: false,
}))
}
} }
Async::NotReady => { Async::NotReady => {
if src.len() >= MAX_BUFFER_SIZE { if src.len() >= MAX_BUFFER_SIZE {
@ -148,7 +142,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);

View File

@ -1,6 +1,6 @@
#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] #![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))]
use bytes::BufMut; use bytes::{BufMut, BytesMut};
use futures::{Async, Poll}; use futures::{Async, Poll};
use std::io; use std::io;
use std::rc::Rc; use std::rc::Rc;
@ -45,7 +45,7 @@ impl<T: AsyncWrite, H: 'static> H1Writer<T, H> {
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::KEEPALIVE,
encoder: ContentEncoder::empty(buf.clone()), encoder: ContentEncoder::empty(buf.clone()),
written: 0, written: 0,
headers_size: 0, headers_size: 0,
@ -62,7 +62,7 @@ impl<T: AsyncWrite, H: 'static> H1Writer<T, H> {
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.written = 0; self.written = 0;
self.flags = Flags::empty(); self.flags = Flags::KEEPALIVE;
} }
pub fn disconnected(&mut self) { pub fn disconnected(&mut self) {
@ -100,6 +100,16 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
self.written self.written
} }
#[inline]
fn set_date(&self, dst: &mut BytesMut) {
self.settings.set_date(dst)
}
#[inline]
fn buffer(&self) -> &mut BytesMut {
self.buffer.get_mut()
}
fn start( fn start(
&mut self, req: &mut HttpInnerMessage, msg: &mut HttpResponse, &mut self, req: &mut HttpInnerMessage, msg: &mut HttpResponse,
encoding: ContentEncoding, encoding: ContentEncoding,
@ -108,9 +118,9 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
self.encoder = self.encoder =
ContentEncoder::for_server(self.buffer.clone(), req, msg, encoding); ContentEncoder::for_server(self.buffer.clone(), req, msg, encoding);
if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) { if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) {
self.flags.insert(Flags::STARTED | Flags::KEEPALIVE); self.flags = Flags::STARTED | Flags::KEEPALIVE;
} else { } else {
self.flags.insert(Flags::STARTED); self.flags = Flags::STARTED;
} }
// Connection upgrade // Connection upgrade
@ -138,7 +148,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 +267,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 +286,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);
} }
} }

View File

@ -133,7 +133,8 @@ where
Err(err) => { Err(err) => {
error!("Unhandled error: {}", err); error!("Unhandled error: {}", err);
item.flags.insert( item.flags.insert(
EntryFlags::EOF | EntryFlags::ERROR EntryFlags::EOF
| EntryFlags::ERROR
| EntryFlags::WRITE_DONE, | EntryFlags::WRITE_DONE,
); );
item.stream.reset(Reason::INTERNAL_ERROR); item.stream.reset(Reason::INTERNAL_ERROR);
@ -150,7 +151,8 @@ where
} }
Err(err) => { Err(err) => {
item.flags.insert( item.flags.insert(
EntryFlags::ERROR | EntryFlags::WRITE_DONE EntryFlags::ERROR
| EntryFlags::WRITE_DONE
| EntryFlags::FINISHED, | EntryFlags::FINISHED,
); );
error!("Unhandled error: {}", err); error!("Unhandled error: {}", err);
@ -248,7 +250,8 @@ where
if not_ready { if not_ready {
if self.tasks.is_empty() && self.flags.contains(Flags::DISCONNECTED) if self.tasks.is_empty() && self.flags.contains(Flags::DISCONNECTED)
{ {
return conn.poll_close() return conn
.poll_close()
.map_err(|e| error!("Error during connection close: {}", e)); .map_err(|e| error!("Error during connection close: {}", e));
} else { } else {
return Ok(Async::NotReady); return Ok(Async::NotReady);

View File

@ -71,6 +71,16 @@ impl<H: 'static> Writer for H2Writer<H> {
self.written self.written
} }
#[inline]
fn set_date(&self, dst: &mut BytesMut) {
self.settings.set_date(dst)
}
#[inline]
fn buffer(&self) -> &mut BytesMut {
self.buffer.get_mut()
}
fn start( fn start(
&mut self, req: &mut HttpInnerMessage, msg: &mut HttpResponse, &mut self, req: &mut HttpInnerMessage, msg: &mut HttpResponse,
encoding: ContentEncoding, encoding: ContentEncoding,
@ -79,9 +89,6 @@ impl<H: 'static> Writer for H2Writer<H> {
self.flags.insert(Flags::STARTED); self.flags.insert(Flags::STARTED);
self.encoder = self.encoder =
ContentEncoder::for_server(self.buffer.clone(), req, msg, encoding); ContentEncoder::for_server(self.buffer.clone(), req, msg, encoding);
if let Body::Empty = *msg.body() {
self.flags.insert(Flags::EOF);
}
// http2 specific // http2 specific
msg.headers_mut().remove(CONNECTION); msg.headers_mut().remove(CONNECTION);
@ -98,15 +105,22 @@ impl<H: 'static> Writer for H2Writer<H> {
let body = msg.replace_body(Body::Empty); let body = msg.replace_body(Body::Empty);
match body { match body {
Body::Binary(ref bytes) => { Body::Binary(ref bytes) => {
let mut val = BytesMut::new(); if bytes.is_empty() {
helpers::convert_usize(bytes.len(), &mut val); msg.headers_mut()
let l = val.len(); .insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
msg.headers_mut().insert( self.flags.insert(Flags::EOF);
CONTENT_LENGTH, } else {
HeaderValue::try_from(val.split_to(l - 2).freeze()).unwrap(), let mut val = BytesMut::new();
); helpers::convert_usize(bytes.len(), &mut val);
let l = val.len();
msg.headers_mut().insert(
CONTENT_LENGTH,
HeaderValue::try_from(val.split_to(l - 2).freeze()).unwrap(),
);
}
} }
Body::Empty => { Body::Empty => {
self.flags.insert(Flags::EOF);
msg.headers_mut() msg.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::from_static("0")); .insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
} }
@ -120,7 +134,8 @@ impl<H: 'static> Writer for H2Writer<H> {
resp.headers_mut().insert(key, value.clone()); resp.headers_mut().insert(key, value.clone());
} }
match self.respond match self
.respond
.send_response(resp, self.flags.contains(Flags::EOF)) .send_response(resp, self.flags.contains(Flags::EOF))
{ {
Ok(stream) => self.stream = Some(stream), Ok(stream) => self.stream = Some(stream),
@ -130,14 +145,18 @@ impl<H: 'static> Writer for H2Writer<H> {
trace!("Response: {:?}", msg); trace!("Response: {:?}", msg);
if let Body::Binary(bytes) = body { if let Body::Binary(bytes) = body {
self.flags.insert(Flags::EOF); if bytes.is_empty() {
self.written = bytes.len() as u64; Ok(WriterState::Done)
self.encoder.write(bytes)?; } else {
if let Some(ref mut stream) = self.stream { self.flags.insert(Flags::EOF);
self.flags.insert(Flags::RESERVED); self.written = bytes.len() as u64;
stream.reserve_capacity(cmp::min(self.buffer.len(), CHUNK_SIZE)); self.encoder.write(bytes)?;
if let Some(ref mut stream) = self.stream {
self.flags.insert(Flags::RESERVED);
stream.reserve_capacity(cmp::min(self.buffer.len(), CHUNK_SIZE));
}
Ok(WriterState::Pause)
} }
Ok(WriterState::Pause)
} else { } else {
msg.replace_body(body); msg.replace_body(body);
self.buffer_capacity = msg.write_buffer_capacity(); self.buffer_capacity = msg.write_buffer_capacity();
@ -166,10 +185,8 @@ impl<H: 'static> Writer for H2Writer<H> {
} }
fn write_eof(&mut self) -> io::Result<WriterState> { fn write_eof(&mut self) -> io::Result<WriterState> {
self.encoder.write_eof()?;
self.flags.insert(Flags::EOF); self.flags.insert(Flags::EOF);
if !self.encoder.is_eof() { if !self.encoder.write_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",

View File

@ -143,7 +143,7 @@ pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesM
} }
/// NOTE: bytes object has to contain enough space /// NOTE: bytes object has to contain enough space
pub(crate) fn write_content_length(mut n: usize, bytes: &mut BytesMut) { pub fn write_content_length(mut n: usize, bytes: &mut BytesMut) {
if n < 10 { if n < 10 {
let mut buf: [u8; 21] = [ let mut buf: [u8; 21] = [
b'\r', b'\n', b'c', b'o', b'n', b't', b'e', b'n', b't', b'-', b'l', b'e', b'\r', b'\n', b'c', b'o', b'n', b't', b'e', b'n', b't', b'-', b'l', b'e',
@ -251,63 +251,33 @@ mod tests {
let mut bytes = BytesMut::new(); let mut bytes = BytesMut::new();
bytes.reserve(50); bytes.reserve(50);
write_content_length(0, &mut bytes); write_content_length(0, &mut bytes);
assert_eq!( assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 0\r\n"[..]);
bytes.take().freeze(),
b"\r\ncontent-length: 0\r\n"[..]
);
bytes.reserve(50); bytes.reserve(50);
write_content_length(9, &mut bytes); write_content_length(9, &mut bytes);
assert_eq!( assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 9\r\n"[..]);
bytes.take().freeze(),
b"\r\ncontent-length: 9\r\n"[..]
);
bytes.reserve(50); bytes.reserve(50);
write_content_length(10, &mut bytes); write_content_length(10, &mut bytes);
assert_eq!( assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 10\r\n"[..]);
bytes.take().freeze(),
b"\r\ncontent-length: 10\r\n"[..]
);
bytes.reserve(50); bytes.reserve(50);
write_content_length(99, &mut bytes); write_content_length(99, &mut bytes);
assert_eq!( assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 99\r\n"[..]);
bytes.take().freeze(),
b"\r\ncontent-length: 99\r\n"[..]
);
bytes.reserve(50); bytes.reserve(50);
write_content_length(100, &mut bytes); write_content_length(100, &mut bytes);
assert_eq!( assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 100\r\n"[..]);
bytes.take().freeze(),
b"\r\ncontent-length: 100\r\n"[..]
);
bytes.reserve(50); bytes.reserve(50);
write_content_length(101, &mut bytes); write_content_length(101, &mut bytes);
assert_eq!( assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 101\r\n"[..]);
bytes.take().freeze(),
b"\r\ncontent-length: 101\r\n"[..]
);
bytes.reserve(50); bytes.reserve(50);
write_content_length(998, &mut bytes); write_content_length(998, &mut bytes);
assert_eq!( assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 998\r\n"[..]);
bytes.take().freeze(),
b"\r\ncontent-length: 998\r\n"[..]
);
bytes.reserve(50); bytes.reserve(50);
write_content_length(1000, &mut bytes); write_content_length(1000, &mut bytes);
assert_eq!( assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 1000\r\n"[..]);
bytes.take().freeze(),
b"\r\ncontent-length: 1000\r\n"[..]
);
bytes.reserve(50); bytes.reserve(50);
write_content_length(1001, &mut bytes); write_content_length(1001, &mut bytes);
assert_eq!( assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 1001\r\n"[..]);
bytes.take().freeze(),
b"\r\ncontent-length: 1001\r\n"[..]
);
bytes.reserve(50); bytes.reserve(50);
write_content_length(5909, &mut bytes); write_content_length(5909, &mut bytes);
assert_eq!( assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 5909\r\n"[..]);
bytes.take().freeze(),
b"\r\ncontent-length: 5909\r\n"[..]
);
} }
} }

View File

@ -3,7 +3,8 @@ use std::net::Shutdown;
use std::{io, time}; use std::{io, time};
use actix; use actix;
use futures::Poll; use bytes::BytesMut;
use futures::{Async, Poll};
use tokio_core::net::TcpStream; use tokio_core::net::TcpStream;
use tokio_io::{AsyncRead, AsyncWrite}; use tokio_io::{AsyncRead, AsyncWrite};
@ -24,6 +25,9 @@ mod worker;
pub use self::settings::ServerSettings; pub use self::settings::ServerSettings;
pub use self::srv::HttpServer; pub use self::srv::HttpServer;
#[doc(hidden)]
pub use self::helpers::write_content_length;
use body::Binary; use body::Binary;
use error::Error; use error::Error;
use header::ContentEncoding; use header::ContentEncoding;
@ -132,13 +136,15 @@ impl HttpHandler for Box<HttpHandler> {
#[doc(hidden)] #[doc(hidden)]
pub trait HttpHandlerTask { pub trait HttpHandlerTask {
/// Poll task, this method is used before or after *io* object is available /// Poll task, this method is used before or after *io* object is available
fn poll(&mut self) -> Poll<(), Error>; fn poll(&mut self) -> Poll<(), Error> {
Ok(Async::Ready(()))
}
/// Poll task when *io* object is available /// Poll task when *io* object is available
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error>; fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error>;
/// Connection is disconnected /// Connection is disconnected
fn disconnected(&mut self); fn disconnected(&mut self) {}
} }
/// Conversion helper trait /// Conversion helper trait
@ -168,8 +174,16 @@ pub enum WriterState {
#[doc(hidden)] #[doc(hidden)]
/// Stream writer /// Stream writer
pub trait Writer { pub trait Writer {
/// number of bytes written to the stream
fn written(&self) -> u64; fn written(&self) -> u64;
#[doc(hidden)]
fn set_date(&self, st: &mut BytesMut);
#[doc(hidden)]
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref))]
fn buffer(&self) -> &mut BytesMut;
fn start( fn start(
&mut self, req: &mut HttpInnerMessage, resp: &mut HttpResponse, &mut self, req: &mut HttpInnerMessage, resp: &mut HttpResponse,
encoding: ContentEncoding, encoding: ContentEncoding,

View File

@ -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>>,
} }
@ -255,10 +266,7 @@ mod tests {
#[test] #[test]
fn test_date_len() { fn test_date_len() {
assert_eq!( assert_eq!(DATE_VALUE_LENGTH, "Sun, 06 Nov 1994 08:49:37 GMT".len());
DATE_VALUE_LENGTH,
"Sun, 06 Nov 1994 08:49:37 GMT".len()
);
} }
#[test] #[test]

View File

@ -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,19 @@ 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 +238,42 @@ 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;
@ -246,7 +309,7 @@ where
/// The socket address to bind /// The socket address to bind
/// ///
/// To mind multiple addresses this method can be call multiple times. /// To bind multiple addresses this method can be called multiple times.
pub fn bind<S: net::ToSocketAddrs>(mut self, addr: S) -> io::Result<Self> { pub fn bind<S: net::ToSocketAddrs>(mut self, addr: S) -> io::Result<Self> {
let sockets = self.bind2(addr)?; let sockets = self.bind2(addr)?;
self.sockets.extend(sockets); self.sockets.extend(sockets);
@ -256,7 +319,7 @@ where
#[cfg(feature = "tls")] #[cfg(feature = "tls")]
/// The ssl socket address to bind /// The ssl socket address to bind
/// ///
/// To mind multiple addresses this method can be call multiple times. /// To bind multiple addresses this method can be called multiple times.
pub fn bind_tls<S: net::ToSocketAddrs>( pub fn bind_tls<S: net::ToSocketAddrs>(
mut self, addr: S, acceptor: TlsAcceptor, mut self, addr: S, acceptor: TlsAcceptor,
) -> io::Result<Self> { ) -> io::Result<Self> {
@ -277,15 +340,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();
@ -395,7 +450,6 @@ impl<H: IntoHttpHandler> HttpServer<H> {
self.accept.push(start_accept_thread( self.accept.push(start_accept_thread(
token, token,
sock, sock,
self.backlog,
tx.clone(), tx.clone(),
socks.clone(), socks.clone(),
workers.clone(), workers.clone(),
@ -727,7 +781,7 @@ enum Command {
} }
fn start_accept_thread( fn start_accept_thread(
token: usize, sock: Socket, backlog: i32, srv: mpsc::UnboundedSender<ServerCommand>, token: usize, sock: Socket, srv: mpsc::UnboundedSender<ServerCommand>,
socks: Slab<SocketInfo>, socks: Slab<SocketInfo>,
mut workers: Vec<(usize, mpsc::UnboundedSender<Conn<net::TcpStream>>)>, mut workers: Vec<(usize, mpsc::UnboundedSender<Conn<net::TcpStream>>)>,
) -> (mio::SetReadiness, sync_mpsc::Sender<Command>) { ) -> (mio::SetReadiness, sync_mpsc::Sender<Command>) {
@ -764,12 +818,9 @@ fn start_accept_thread(
} }
// Start listening for incoming commands // Start listening for incoming commands
if let Err(err) = poll.register( if let Err(err) =
&reg, poll.register(&reg, CMD, mio::Ready::readable(), mio::PollOpt::edge())
CMD, {
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
panic!("Can not register Registration: {}", err); panic!("Can not register Registration: {}", err);
} }
@ -840,8 +891,8 @@ fn start_accept_thread(
}, },
CMD => match rx.try_recv() { CMD => match rx.try_recv() {
Ok(cmd) => match cmd { Ok(cmd) => match cmd {
Command::Pause => if let Some(server) = server.take() { Command::Pause => if let Some(ref server) = server {
if let Err(err) = poll.deregister(&server) { if let Err(err) = poll.deregister(server) {
error!( error!(
"Can not deregister server socket {}", "Can not deregister server socket {}",
err err
@ -854,15 +905,6 @@ fn start_accept_thread(
} }
}, },
Command::Resume => { Command::Resume => {
let lst = create_tcp_listener(addr, backlog)
.expect("Can not create net::TcpListener");
server = Some(
mio::net::TcpListener::from_std(lst).expect(
"Can not create mio::net::TcpListener",
),
);
if let Some(ref server) = server { if let Some(ref server) = server {
if let Err(err) = poll.register( if let Err(err) = poll.register(
server, server,

View File

@ -85,9 +85,7 @@ impl<H: HttpHandler + 'static> Worker<H> {
fn update_time(&self, ctx: &mut Context<Self>) { fn update_time(&self, ctx: &mut Context<Self>) {
self.settings.update_date(); self.settings.update_date();
ctx.run_later(time::Duration::new(1, 0), |slf, ctx| { ctx.run_later(time::Duration::new(1, 0), |slf, ctx| slf.update_time(ctx));
slf.update_time(ctx)
});
} }
fn shutdown_timeout( fn shutdown_timeout(
@ -195,18 +193,17 @@ impl StreamHandlerType {
let io = TcpStream::from_stream(io, hnd) let io = TcpStream::from_stream(io, hnd)
.expect("failed to associate TCP stream"); .expect("failed to associate TCP stream");
hnd.spawn( hnd.spawn(TlsAcceptorExt::accept_async(acceptor, io).then(move |res| {
TlsAcceptorExt::accept_async(acceptor, io).then(move |res| { match res {
match res { Ok(io) => {
Ok(io) => Arbiter::handle() Arbiter::handle().spawn(HttpChannel::new(h, io, peer, http2))
.spawn(HttpChannel::new(h, io, peer, http2)), }
Err(err) => { Err(err) => {
trace!("Error during handling tls connection: {}", err) trace!("Error during handling tls connection: {}", err)
} }
}; };
future::result(Ok(())) future::result(Ok(()))
}), }));
);
} }
#[cfg(feature = "alpn")] #[cfg(feature = "alpn")]
StreamHandlerType::Alpn(ref acceptor) => { StreamHandlerType::Alpn(ref acceptor) => {
@ -215,28 +212,36 @@ impl StreamHandlerType {
let io = TcpStream::from_stream(io, hnd) let io = TcpStream::from_stream(io, hnd)
.expect("failed to associate TCP stream"); .expect("failed to associate TCP stream");
hnd.spawn( hnd.spawn(SslAcceptorExt::accept_async(acceptor, io).then(move |res| {
SslAcceptorExt::accept_async(acceptor, io).then(move |res| { match res {
match res { Ok(io) => {
Ok(io) => { let http2 = if let Some(p) =
let http2 = if let Some(p) = io.get_ref().ssl().selected_alpn_protocol()
io.get_ref().ssl().selected_alpn_protocol() {
{ p.len() == 2 && &p == b"h2"
p.len() == 2 && &p == b"h2" } else {
} else { false
false };
}; Arbiter::handle()
Arbiter::handle() .spawn(HttpChannel::new(h, io, peer, http2));
.spawn(HttpChannel::new(h, io, peer, http2)); }
} Err(err) => {
Err(err) => { trace!("Error during handling tls connection: {}", err)
trace!("Error during handling tls connection: {}", err) }
} };
}; future::result(Ok(()))
future::result(Ok(())) }));
}),
);
} }
} }
} }
pub(crate) fn scheme(&self) -> &'static str {
match *self {
StreamHandlerType::Normal => "http",
#[cfg(feature = "tls")]
StreamHandlerType::Tls(_) => "https",
#[cfg(feature = "alpn")]
StreamHandlerType::Alpn(_) => "https",
}
}
} }

View File

@ -217,7 +217,7 @@ impl TestServer {
/// Create `POST` request /// Create `POST` request
pub fn post(&self) -> ClientRequestBuilder { pub fn post(&self) -> ClientRequestBuilder {
ClientRequest::get(self.url("/").as_str()) ClientRequest::post(self.url("/").as_str())
} }
/// Create `HEAD` request /// Create `HEAD` request
@ -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)
} }

View File

@ -794,15 +794,13 @@ where
}; };
let hnd: &mut F = unsafe { &mut *self.hnd.get() }; let hnd: &mut F = unsafe { &mut *self.hnd.get() };
let item = match (*hnd)( let item =
self.item1.take().unwrap(), match (*hnd)(self.item1.take().unwrap(), self.item2.take().unwrap(), item)
self.item2.take().unwrap(), .respond_to(&self.req)
item, {
).respond_to(&self.req) Ok(item) => item.into(),
{ Err(err) => return Err(err.into()),
Ok(item) => item.into(), };
Err(err) => return Err(err.into()),
};
match item.into() { match item.into() {
AsyncResultItem::Err(err) => return Err(err), AsyncResultItem::Err(err) => return Err(err),

View File

@ -22,8 +22,10 @@ use header::IntoHeaderValue;
use httpmessage::HttpMessage; use httpmessage::HttpMessage;
use payload::PayloadHelper; use payload::PayloadHelper;
use client::{ClientConnector, ClientRequest, ClientRequestBuilder, ClientResponse, use client::{
HttpResponseParserError, SendRequest, SendRequestError}; ClientConnector, ClientRequest, ClientRequestBuilder, ClientResponse,
HttpResponseParserError, SendRequest, SendRequestError,
};
use super::frame::Frame; use super::frame::Frame;
use super::proto::{CloseReason, OpCode}; use super::proto::{CloseReason, OpCode};
@ -218,8 +220,7 @@ impl Client {
self.request.upgrade(); self.request.upgrade();
self.request.set_header(header::UPGRADE, "websocket"); self.request.set_header(header::UPGRADE, "websocket");
self.request.set_header(header::CONNECTION, "upgrade"); self.request.set_header(header::CONNECTION, "upgrade");
self.request self.request.set_header(header::SEC_WEBSOCKET_VERSION, "13");
.set_header(header::SEC_WEBSOCKET_VERSION, "13");
self.request.with_connector(self.conn.clone()); self.request.with_connector(self.conn.clone());
if let Some(protocols) = self.protocols.take() { if let Some(protocols) = self.protocols.take() {
@ -235,7 +236,9 @@ impl Client {
return ClientHandshake::error(ClientError::InvalidUrl); return ClientHandshake::error(ClientError::InvalidUrl);
} }
if let Some(scheme) = request.uri().scheme_part() { if let Some(scheme) = request.uri().scheme_part() {
if scheme != "http" && scheme != "https" && scheme != "ws" if scheme != "http"
&& scheme != "https"
&& scheme != "ws"
&& scheme != "wss" && scheme != "wss"
{ {
return ClientHandshake::error(ClientError::InvalidUrl); return ClientHandshake::error(ClientError::InvalidUrl);
@ -394,10 +397,7 @@ impl Future for ClientHandshake {
encoded, encoded,
key key
); );
return Err(ClientError::InvalidChallengeResponse( return Err(ClientError::InvalidChallengeResponse(encoded, key.clone()));
encoded,
key.clone(),
));
} }
} else { } else {
trace!("Missing SEC-WEBSOCKET-ACCEPT header"); trace!("Missing SEC-WEBSOCKET-ACCEPT header");
@ -534,23 +534,13 @@ impl ClientWriter {
/// Send ping frame /// Send ping frame
#[inline] #[inline]
pub fn ping(&mut self, message: &str) { pub fn ping(&mut self, message: &str) {
self.write(Frame::message( self.write(Frame::message(Vec::from(message), OpCode::Ping, true, true));
Vec::from(message),
OpCode::Ping,
true,
true,
));
} }
/// Send pong frame /// Send pong frame
#[inline] #[inline]
pub fn pong(&mut self, message: &str) { pub fn pong(&mut self, message: &str) {
self.write(Frame::message( self.write(Frame::message(Vec::from(message), OpCode::Pong, true, true));
Vec::from(message),
OpCode::Pong,
true,
true,
));
} }
/// Send close frame /// Send close frame

View File

@ -5,8 +5,10 @@ use smallvec::SmallVec;
use actix::dev::{ContextImpl, SyncEnvelope, ToEnvelope}; use actix::dev::{ContextImpl, SyncEnvelope, ToEnvelope};
use actix::fut::ActorFuture; use actix::fut::ActorFuture;
use actix::{Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, use actix::{
SpawnHandle, Syn, Unsync}; Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle,
Syn, Unsync,
};
use body::{Binary, Body}; use body::{Binary, Body};
use context::{ActorHttpContext, Drain, Frame as ContextFrame}; use context::{ActorHttpContext, Drain, Frame as ContextFrame};
@ -64,7 +66,8 @@ where
#[doc(hidden)] #[doc(hidden)]
#[inline] #[inline]
fn waiting(&self) -> bool { fn waiting(&self) -> bool {
self.inner.waiting() || self.inner.state() == ActorState::Stopping self.inner.waiting()
|| self.inner.state() == ActorState::Stopping
|| self.inner.state() == ActorState::Stopped || self.inner.state() == ActorState::Stopped
} }

View File

@ -123,9 +123,7 @@ impl Frame {
None None
}; };
Ok(Async::Ready(Some(( Ok(Async::Ready(Some((idx, finished, opcode, length, mask))))
idx, finished, opcode, length, mask,
))))
} }
fn read_chunk_md( fn read_chunk_md(
@ -284,10 +282,7 @@ impl Frame {
} else { } else {
None None
}; };
Some(CloseReason { Some(CloseReason { code, description })
code,
description,
})
} else { } else {
None None
} }

View File

@ -62,7 +62,9 @@ mod frame;
mod mask; mod mask;
mod proto; mod proto;
pub use self::client::{Client, ClientError, ClientHandshake, ClientReader, ClientWriter}; pub use self::client::{
Client, ClientError, ClientHandshake, ClientReader, ClientWriter,
};
pub use self::context::WebsocketContext; pub use self::context::WebsocketContext;
pub use self::frame::Frame; pub use self::frame::Frame;
pub use self::proto::{CloseCode, CloseReason, OpCode}; pub use self::proto::{CloseCode, CloseReason, OpCode};
@ -216,9 +218,7 @@ pub fn handshake<S>(
} }
// check supported version // check supported version
if !req.headers() if !req.headers().contains_key(header::SEC_WEBSOCKET_VERSION) {
.contains_key(header::SEC_WEBSOCKET_VERSION)
{
return Err(HandshakeError::NoVersionHeader); return Err(HandshakeError::NoVersionHeader);
} }
let supported_ver = { let supported_ver = {
@ -387,10 +387,7 @@ mod tests {
); );
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();
headers.insert( headers.insert(header::UPGRADE, header::HeaderValue::from_static("test"));
header::UPGRADE,
header::HeaderValue::from_static("test"),
);
let req = HttpRequest::new( let req = HttpRequest::new(
Method::GET, Method::GET,
Uri::from_str("/").unwrap(), Uri::from_str("/").unwrap(),

View File

@ -73,10 +73,7 @@ fn test_with_query_parameter() {
}) })
}); });
let request = srv.get() let request = srv.get().uri(srv.url("/?qp=5").as_str()).finish().unwrap();
.uri(srv.url("/?qp=5").as_str())
.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());
@ -125,7 +122,8 @@ fn test_client_gzip_encoding() {
}); });
// client request // client request
let request = srv.post() let request = srv
.post()
.content_encoding(http::ContentEncoding::Gzip) .content_encoding(http::ContentEncoding::Gzip)
.body(STR) .body(STR)
.unwrap(); .unwrap();
@ -154,7 +152,8 @@ fn test_client_gzip_encoding_large() {
}); });
// client request // client request
let request = srv.post() let request = srv
.post()
.content_encoding(http::ContentEncoding::Gzip) .content_encoding(http::ContentEncoding::Gzip)
.body(data.clone()) .body(data.clone())
.unwrap(); .unwrap();
@ -186,7 +185,8 @@ fn test_client_gzip_encoding_large_random() {
}); });
// client request // client request
let request = srv.post() let request = srv
.post()
.content_encoding(http::ContentEncoding::Gzip) .content_encoding(http::ContentEncoding::Gzip)
.body(data.clone()) .body(data.clone())
.unwrap(); .unwrap();
@ -214,7 +214,8 @@ fn test_client_brotli_encoding() {
}); });
// client request // client request
let request = srv.client(http::Method::POST, "/") let request = srv
.client(http::Method::POST, "/")
.content_encoding(http::ContentEncoding::Br) .content_encoding(http::ContentEncoding::Br)
.body(STR) .body(STR)
.unwrap(); .unwrap();
@ -247,7 +248,8 @@ fn test_client_brotli_encoding_large_random() {
}); });
// client request // client request
let request = srv.client(http::Method::POST, "/") let request = srv
.client(http::Method::POST, "/")
.content_encoding(http::ContentEncoding::Br) .content_encoding(http::ContentEncoding::Br)
.body(data.clone()) .body(data.clone())
.unwrap(); .unwrap();
@ -276,7 +278,8 @@ fn test_client_deflate_encoding() {
}); });
// client request // client request
let request = srv.post() let request = srv
.post()
.content_encoding(http::ContentEncoding::Deflate) .content_encoding(http::ContentEncoding::Deflate)
.body(STR) .body(STR)
.unwrap(); .unwrap();
@ -309,7 +312,8 @@ fn test_client_deflate_encoding_large_random() {
}); });
// client request // client request
let request = srv.post() let request = srv
.post()
.content_encoding(http::ContentEncoding::Deflate) .content_encoding(http::ContentEncoding::Deflate)
.body(data.clone()) .body(data.clone())
.unwrap(); .unwrap();
@ -339,9 +343,7 @@ fn test_client_streaming_explicit() {
let body = once(Ok(Bytes::from_static(STR.as_ref()))); let body = once(Ok(Bytes::from_static(STR.as_ref())));
let request = srv.get() let request = srv.get().body(Body::Streaming(Box::new(body))).unwrap();
.body(Body::Streaming(Box::new(body)))
.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());
@ -414,7 +416,8 @@ fn test_client_cookie_handling() {
}) })
}); });
let request = srv.get() let request = srv
.get()
.cookie(cookie1.clone()) .cookie(cookie1.clone())
.cookie(cookie2.clone()) .cookie(cookie2.clone())
.finish() .finish()

View File

@ -34,10 +34,7 @@ fn test_path_extractor() {
}); });
// client request // client request
let request = srv.get() let request = srv.get().uri(srv.url("/test/index.html")).finish().unwrap();
.uri(srv.url("/test/index.html"))
.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());
@ -55,7 +52,8 @@ fn test_query_extractor() {
}); });
// client request // client request
let request = srv.get() let request = srv
.get()
.uri(srv.url("/index.html?username=test")) .uri(srv.url("/index.html?username=test"))
.finish() .finish()
.unwrap(); .unwrap();
@ -67,10 +65,7 @@ fn test_query_extractor() {
assert_eq!(bytes, Bytes::from_static(b"Welcome test!")); assert_eq!(bytes, Bytes::from_static(b"Welcome test!"));
// client request // client request
let request = srv.get() let request = srv.get().uri(srv.url("/index.html")).finish().unwrap();
.uri(srv.url("/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap(); let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::BAD_REQUEST); assert_eq!(response.status(), StatusCode::BAD_REQUEST);
} }
@ -89,7 +84,8 @@ fn test_async_extractor_async() {
}); });
// client request // client request
let request = srv.post() let request = srv
.post()
.uri(srv.url("/test1/index.html")) .uri(srv.url("/test1/index.html"))
.header("content-type", "application/json") .header("content-type", "application/json")
.body("{\"test\": 1}") .body("{\"test\": 1}")
@ -113,7 +109,8 @@ fn test_path_and_query_extractor() {
}); });
// client request // client request
let request = srv.get() let request = srv
.get()
.uri(srv.url("/test1/index.html?username=test2")) .uri(srv.url("/test1/index.html?username=test2"))
.finish() .finish()
.unwrap(); .unwrap();
@ -125,7 +122,8 @@ fn test_path_and_query_extractor() {
assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - test2!")); assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - test2!"));
// client request // client request
let request = srv.get() let request = srv
.get()
.uri(srv.url("/test1/index.html")) .uri(srv.url("/test1/index.html"))
.finish() .finish()
.unwrap(); .unwrap();
@ -145,7 +143,8 @@ fn test_path_and_query_extractor2() {
}); });
// client request // client request
let request = srv.get() let request = srv
.get()
.uri(srv.url("/test1/index.html?username=test2")) .uri(srv.url("/test1/index.html?username=test2"))
.finish() .finish()
.unwrap(); .unwrap();
@ -157,7 +156,8 @@ fn test_path_and_query_extractor2() {
assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - test2!")); assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - test2!"));
// client request // client request
let request = srv.get() let request = srv
.get()
.uri(srv.url("/test1/index.html")) .uri(srv.url("/test1/index.html"))
.finish() .finish()
.unwrap(); .unwrap();
@ -169,21 +169,21 @@ fn test_path_and_query_extractor2() {
fn test_path_and_query_extractor2_async() { fn test_path_and_query_extractor2_async() {
let mut srv = test::TestServer::new(|app| { let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| { app.resource("/{username}/index.html", |r| {
r.route().with3( r.route()
|p: Path<PParam>, _: Query<PParam>, data: Json<Value>| { .with3(|p: Path<PParam>, _: Query<PParam>, data: Json<Value>| {
Timeout::new(Duration::from_millis(10), &Arbiter::handle()) Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap() .unwrap()
.and_then(move |_| { .and_then(move |_| {
Ok(format!("Welcome {} - {}!", p.username, data.0)) Ok(format!("Welcome {} - {}!", p.username, data.0))
}) })
.responder() .responder()
}, })
)
}); });
}); });
// client request // client request
let request = srv.post() let request = srv
.post()
.uri(srv.url("/test1/index.html?username=test2")) .uri(srv.url("/test1/index.html?username=test2"))
.header("content-type", "application/json") .header("content-type", "application/json")
.body("{\"test\": 1}") .body("{\"test\": 1}")
@ -193,10 +193,7 @@ fn test_path_and_query_extractor2_async() {
// read response // read response
let bytes = srv.execute(response.body()).unwrap(); let bytes = srv.execute(response.body()).unwrap();
assert_eq!( assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!"));
bytes,
Bytes::from_static(b"Welcome test1 - {\"test\":1}!")
);
} }
#[test] #[test]
@ -215,7 +212,8 @@ fn test_path_and_query_extractor3_async() {
}); });
// client request // client request
let request = srv.post() let request = srv
.post()
.uri(srv.url("/test1/index.html")) .uri(srv.url("/test1/index.html"))
.header("content-type", "application/json") .header("content-type", "application/json")
.body("{\"test\": 1}") .body("{\"test\": 1}")
@ -240,7 +238,8 @@ fn test_path_and_query_extractor4_async() {
}); });
// client request // client request
let request = srv.post() let request = srv
.post()
.uri(srv.url("/test1/index.html")) .uri(srv.url("/test1/index.html"))
.header("content-type", "application/json") .header("content-type", "application/json")
.body("{\"test\": 1}") .body("{\"test\": 1}")
@ -253,21 +252,21 @@ fn test_path_and_query_extractor4_async() {
fn test_path_and_query_extractor2_async2() { fn test_path_and_query_extractor2_async2() {
let mut srv = test::TestServer::new(|app| { let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| { app.resource("/{username}/index.html", |r| {
r.route().with3( r.route()
|p: Path<PParam>, data: Json<Value>, _: Query<PParam>| { .with3(|p: Path<PParam>, data: Json<Value>, _: Query<PParam>| {
Timeout::new(Duration::from_millis(10), &Arbiter::handle()) Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap() .unwrap()
.and_then(move |_| { .and_then(move |_| {
Ok(format!("Welcome {} - {}!", p.username, data.0)) Ok(format!("Welcome {} - {}!", p.username, data.0))
}) })
.responder() .responder()
}, })
)
}); });
}); });
// client request // client request
let request = srv.post() let request = srv
.post()
.uri(srv.url("/test1/index.html?username=test2")) .uri(srv.url("/test1/index.html?username=test2"))
.header("content-type", "application/json") .header("content-type", "application/json")
.body("{\"test\": 1}") .body("{\"test\": 1}")
@ -277,13 +276,11 @@ fn test_path_and_query_extractor2_async2() {
// read response // read response
let bytes = srv.execute(response.body()).unwrap(); let bytes = srv.execute(response.body()).unwrap();
assert_eq!( assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!"));
bytes,
Bytes::from_static(b"Welcome test1 - {\"test\":1}!")
);
// client request // client request
let request = srv.get() let request = srv
.get()
.uri(srv.url("/test1/index.html")) .uri(srv.url("/test1/index.html"))
.finish() .finish()
.unwrap(); .unwrap();
@ -295,21 +292,21 @@ fn test_path_and_query_extractor2_async2() {
fn test_path_and_query_extractor2_async3() { fn test_path_and_query_extractor2_async3() {
let mut srv = test::TestServer::new(|app| { let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| { app.resource("/{username}/index.html", |r| {
r.route().with3( r.route()
|data: Json<Value>, p: Path<PParam>, _: Query<PParam>| { .with3(|data: Json<Value>, p: Path<PParam>, _: Query<PParam>| {
Timeout::new(Duration::from_millis(10), &Arbiter::handle()) Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap() .unwrap()
.and_then(move |_| { .and_then(move |_| {
Ok(format!("Welcome {} - {}!", p.username, data.0)) Ok(format!("Welcome {} - {}!", p.username, data.0))
}) })
.responder() .responder()
}, })
)
}); });
}); });
// client request // client request
let request = srv.post() let request = srv
.post()
.uri(srv.url("/test1/index.html?username=test2")) .uri(srv.url("/test1/index.html?username=test2"))
.header("content-type", "application/json") .header("content-type", "application/json")
.body("{\"test\": 1}") .body("{\"test\": 1}")
@ -319,13 +316,11 @@ fn test_path_and_query_extractor2_async3() {
// read response // read response
let bytes = srv.execute(response.body()).unwrap(); let bytes = srv.execute(response.body()).unwrap();
assert_eq!( assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!"));
bytes,
Bytes::from_static(b"Welcome test1 - {\"test\":1}!")
);
// client request // client request
let request = srv.get() let request = srv
.get()
.uri(srv.url("/test1/index.html")) .uri(srv.url("/test1/index.html"))
.finish() .finish()
.unwrap(); .unwrap();
@ -342,11 +337,7 @@ fn test_path_and_query_extractor2_async4() {
Timeout::new(Duration::from_millis(10), &Arbiter::handle()) Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap() .unwrap()
.and_then(move |_| { .and_then(move |_| {
Ok(format!( Ok(format!("Welcome {} - {}!", data.1.username, (data.0).0))
"Welcome {} - {}!",
data.1.username,
(data.0).0
))
}) })
.responder() .responder()
}) })
@ -354,7 +345,8 @@ fn test_path_and_query_extractor2_async4() {
}); });
// client request // client request
let request = srv.post() let request = srv
.post()
.uri(srv.url("/test1/index.html?username=test2")) .uri(srv.url("/test1/index.html?username=test2"))
.header("content-type", "application/json") .header("content-type", "application/json")
.body("{\"test\": 1}") .body("{\"test\": 1}")
@ -364,13 +356,11 @@ fn test_path_and_query_extractor2_async4() {
// read response // read response
let bytes = srv.execute(response.body()).unwrap(); let bytes = srv.execute(response.body()).unwrap();
assert_eq!( assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!"));
bytes,
Bytes::from_static(b"Welcome test1 - {\"test\":1}!")
);
// client request // client request
let request = srv.get() let request = srv
.get()
.uri(srv.url("/test1/index.html")) .uri(srv.url("/test1/index.html"))
.finish() .finish()
.unwrap(); .unwrap();
@ -378,19 +368,84 @@ fn test_path_and_query_extractor2_async4() {
assert_eq!(response.status(), StatusCode::BAD_REQUEST); assert_eq!(response.status(), StatusCode::BAD_REQUEST);
} }
#[test]
fn test_scope_and_path_extractor() {
let mut srv = test::TestServer::with_factory(move || {
App::new().scope("/sc", |scope| {
scope.resource("/{num}/index.html", |r| {
r.route()
.with(|p: Path<(usize,)>| format!("Welcome {}!", p.0))
})
})
});
// client request
let request = srv
.get()
.uri(srv.url("/sc/10/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(b"Welcome 10!"));
// client request
let request = srv
.get()
.uri(srv.url("/sc/test1/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::NOT_FOUND);
}
#[test]
fn test_nested_scope_and_path_extractor() {
let mut srv = test::TestServer::with_factory(move || {
App::new().scope("/sc", |scope| {
scope.nested("/{num}", |scope| {
scope.resource("/{num}/index.html", |r| {
r.route().with(|p: Path<(usize, usize)>| {
format!("Welcome {} {}!", p.0, p.1)
})
})
})
})
});
// client request
let request = srv
.get()
.uri(srv.url("/sc/10/12/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(b"Welcome 10 12!"));
// client request
let request = srv
.get()
.uri(srv.url("/sc/10/test1/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::NOT_FOUND);
}
#[cfg(actix_impl_trait)] #[cfg(actix_impl_trait)]
fn test_impl_trait( fn test_impl_trait(
data: (Json<Value>, Path<PParam>, Query<PParam>), data: (Json<Value>, Path<PParam>, Query<PParam>),
) -> impl Future<Item = String, Error = io::Error> { ) -> impl Future<Item = String, Error = io::Error> {
Timeout::new(Duration::from_millis(10), &Arbiter::handle()) Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap() .unwrap()
.and_then(move |_| { .and_then(move |_| Ok(format!("Welcome {} - {}!", data.1.username, (data.0).0)))
Ok(format!(
"Welcome {} - {}!",
data.1.username,
(data.0).0
))
})
} }
#[cfg(actix_impl_trait)] #[cfg(actix_impl_trait)]
@ -412,7 +467,8 @@ fn test_path_and_query_extractor2_async4_impl_trait() {
}); });
// client request // client request
let request = srv.post() let request = srv
.post()
.uri(srv.url("/test1/index.html?username=test2")) .uri(srv.url("/test1/index.html?username=test2"))
.header("content-type", "application/json") .header("content-type", "application/json")
.body("{\"test\": 1}") .body("{\"test\": 1}")
@ -422,13 +478,11 @@ fn test_path_and_query_extractor2_async4_impl_trait() {
// read response // read response
let bytes = srv.execute(response.body()).unwrap(); let bytes = srv.execute(response.body()).unwrap();
assert_eq!( assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!"));
bytes,
Bytes::from_static(b"Welcome test1 - {\"test\":1}!")
);
// client request // client request
let request = srv.get() let request = srv
.get()
.uri(srv.url("/test1/index.html")) .uri(srv.url("/test1/index.html"))
.finish() .finish()
.unwrap(); .unwrap();
@ -446,7 +500,8 @@ fn test_path_and_query_extractor2_async4_impl_trait_err() {
}); });
// client request // client request
let request = srv.post() let request = srv
.post()
.uri(srv.url("/test1/index.html?username=test2")) .uri(srv.url("/test1/index.html?username=test2"))
.header("content-type", "application/json") .header("content-type", "application/json")
.body("{\"test\": 1}") .body("{\"test\": 1}")
@ -462,7 +517,8 @@ fn test_non_ascii_route() {
}); });
// client request // client request
let request = srv.get() let request = srv
.get()
.uri(srv.url("/中文/index.html")) .uri(srv.url("/中文/index.html"))
.finish() .finish()
.unwrap(); .unwrap();
@ -483,7 +539,8 @@ fn test_unsafe_path_route() {
}); });
// client request // client request
let request = srv.get() let request = srv
.get()
.uri(srv.url("/test/http%3A%2F%2Fexample.com")) .uri(srv.url("/test/http%3A%2F%2Fexample.com"))
.finish() .finish()
.unwrap(); .unwrap();

View File

@ -9,6 +9,7 @@ use std::thread;
use std::time::Duration; use std::time::Duration;
use actix::*; use actix::*;
use actix_web::error::{Error, ErrorInternalServerError};
use actix_web::*; use actix_web::*;
use futures::{future, Future}; use futures::{future, Future};
use tokio_core::reactor::Timeout; use tokio_core::reactor::Timeout;
@ -21,28 +22,22 @@ 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 +182,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 +218,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 +326,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 +388,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);
@ -555,6 +538,42 @@ fn test_async_middleware_multiple() {
assert_eq!(num3.load(Ordering::Relaxed), 2); 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);
}
#[test] #[test]
fn test_async_scope_middleware() { fn test_async_scope_middleware() {
let num1 = Arc::new(AtomicUsize::new(0)); let num1 = Arc::new(AtomicUsize::new(0));
@ -577,10 +596,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 +634,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 +754,246 @@ 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);
}
struct MiddlewareWithErr;
impl<S> middleware::Middleware<S> for MiddlewareWithErr {
fn start(&self, _req: &mut HttpRequest<S>) -> Result<middleware::Started, Error> {
Err(ErrorInternalServerError("middleware error"))
}
}
struct MiddlewareAsyncWithErr;
impl<S> middleware::Middleware<S> for MiddlewareAsyncWithErr {
fn start(&self, _req: &mut HttpRequest<S>) -> Result<middleware::Started, Error> {
Ok(middleware::Started::Future(Box::new(future::err(
ErrorInternalServerError("middleware error"),
))))
}
}
#[test]
fn test_middleware_chain_with_error() {
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 = MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
};
App::new()
.middleware(mw1)
.middleware(MiddlewareWithErr)
.resource("/test", |r| r.h(|_| HttpResponse::Ok()))
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
assert_eq!(num3.load(Ordering::Relaxed), 1);
}
#[test]
fn test_middleware_async_chain_with_error() {
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 = MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
};
App::new()
.middleware(mw1)
.middleware(MiddlewareAsyncWithErr)
.resource("/test", |r| r.h(|_| HttpResponse::Ok()))
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
assert_eq!(num3.load(Ordering::Relaxed), 1);
}
#[test]
fn test_scope_middleware_chain_with_error() {
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 = MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
};
App::new().scope("/scope", |scope| {
scope
.middleware(mw1)
.middleware(MiddlewareWithErr)
.resource("/test", |r| r.h(|_| HttpResponse::Ok()))
})
});
let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
assert_eq!(num3.load(Ordering::Relaxed), 1);
}
#[test]
fn test_scope_middleware_async_chain_with_error() {
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 = MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
};
App::new().scope("/scope", |scope| {
scope
.middleware(mw1)
.middleware(MiddlewareAsyncWithErr)
.resource("/test", |r| r.h(|_| HttpResponse::Ok()))
})
});
let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
assert_eq!(num3.load(Ordering::Relaxed), 1);
}
#[test]
fn test_resource_middleware_chain_with_error() {
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 = 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(MiddlewareWithErr);
r.h(|_| HttpResponse::Ok());
})
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
assert_eq!(num3.load(Ordering::Relaxed), 1);
}
#[test]
fn test_resource_middleware_async_chain_with_error() {
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 = 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(MiddlewareAsyncWithErr);
r.h(|_| HttpResponse::Ok());
})
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
assert_eq!(num3.load(Ordering::Relaxed), 1);
}

View File

@ -54,6 +54,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World"; Hello World Hello World Hello World Hello World Hello World";
#[test] #[test]
#[cfg(unix)]
fn test_start() { fn test_start() {
let _ = test::TestServer::unused_addr(); let _ = test::TestServer::unused_addr();
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
@ -86,12 +87,17 @@ fn test_start() {
// pause // pause
let _ = srv_addr.send(server::PauseServer).wait(); let _ = srv_addr.send(server::PauseServer).wait();
thread::sleep(time::Duration::from_millis(200)); thread::sleep(time::Duration::from_millis(200));
assert!(net::TcpStream::connect(addr).is_err()); {
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
.timeout(time::Duration::from_millis(200))
.finish()
.unwrap();
assert!(sys.run_until_complete(req.send()).is_err());
}
// resume // resume
let _ = srv_addr.send(server::ResumeServer).wait(); let _ = srv_addr.send(server::ResumeServer).wait();
thread::sleep(time::Duration::from_millis(200)); thread::sleep(time::Duration::from_millis(400));
{ {
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
.finish() .finish()
@ -337,11 +343,7 @@ fn test_body_br_streaming() {
#[test] #[test]
fn test_head_empty() { fn test_head_empty() {
let mut srv = test::TestServer::new(|app| { let mut srv = test::TestServer::new(|app| {
app.handler(|_| { app.handler(|_| HttpResponse::Ok().content_length(STR.len() as u64).finish())
HttpResponse::Ok()
.content_length(STR.len() as u64)
.finish()
})
}); });
let request = srv.head().finish().unwrap(); let request = srv.head().finish().unwrap();
@ -529,7 +531,8 @@ fn test_gzip_encoding() {
e.write_all(STR.as_ref()).unwrap(); e.write_all(STR.as_ref()).unwrap();
let enc = e.finish().unwrap(); let enc = e.finish().unwrap();
let request = srv.post() let request = srv
.post()
.header(http::header::CONTENT_ENCODING, "gzip") .header(http::header::CONTENT_ENCODING, "gzip")
.body(enc.clone()) .body(enc.clone())
.unwrap(); .unwrap();
@ -561,7 +564,8 @@ fn test_gzip_encoding_large() {
e.write_all(data.as_ref()).unwrap(); e.write_all(data.as_ref()).unwrap();
let enc = e.finish().unwrap(); let enc = e.finish().unwrap();
let request = srv.post() let request = srv
.post()
.header(http::header::CONTENT_ENCODING, "gzip") .header(http::header::CONTENT_ENCODING, "gzip")
.body(enc.clone()) .body(enc.clone())
.unwrap(); .unwrap();
@ -597,7 +601,8 @@ fn test_reading_gzip_encoding_large_random() {
e.write_all(data.as_ref()).unwrap(); e.write_all(data.as_ref()).unwrap();
let enc = e.finish().unwrap(); let enc = e.finish().unwrap();
let request = srv.post() let request = srv
.post()
.header(http::header::CONTENT_ENCODING, "gzip") .header(http::header::CONTENT_ENCODING, "gzip")
.body(enc.clone()) .body(enc.clone())
.unwrap(); .unwrap();
@ -629,7 +634,8 @@ fn test_reading_deflate_encoding() {
let enc = e.finish().unwrap(); let enc = e.finish().unwrap();
// client request // client request
let request = srv.post() let request = srv
.post()
.header(http::header::CONTENT_ENCODING, "deflate") .header(http::header::CONTENT_ENCODING, "deflate")
.body(enc) .body(enc)
.unwrap(); .unwrap();
@ -661,7 +667,8 @@ fn test_reading_deflate_encoding_large() {
let enc = e.finish().unwrap(); let enc = e.finish().unwrap();
// client request // client request
let request = srv.post() let request = srv
.post()
.header(http::header::CONTENT_ENCODING, "deflate") .header(http::header::CONTENT_ENCODING, "deflate")
.body(enc) .body(enc)
.unwrap(); .unwrap();
@ -697,7 +704,8 @@ fn test_reading_deflate_encoding_large_random() {
let enc = e.finish().unwrap(); let enc = e.finish().unwrap();
// client request // client request
let request = srv.post() let request = srv
.post()
.header(http::header::CONTENT_ENCODING, "deflate") .header(http::header::CONTENT_ENCODING, "deflate")
.body(enc) .body(enc)
.unwrap(); .unwrap();
@ -730,7 +738,8 @@ fn test_brotli_encoding() {
let enc = e.finish().unwrap(); let enc = e.finish().unwrap();
// client request // client request
let request = srv.post() let request = srv
.post()
.header(http::header::CONTENT_ENCODING, "br") .header(http::header::CONTENT_ENCODING, "br")
.body(enc) .body(enc)
.unwrap(); .unwrap();
@ -763,7 +772,8 @@ fn test_brotli_encoding_large() {
let enc = e.finish().unwrap(); let enc = e.finish().unwrap();
// client request // client request
let request = srv.post() let request = srv
.post()
.header(http::header::CONTENT_ENCODING, "br") .header(http::header::CONTENT_ENCODING, "br")
.body(enc) .body(enc)
.unwrap(); .unwrap();
@ -784,7 +794,8 @@ fn test_h2() {
let handle = core.handle(); let handle = core.handle();
let tcp = TcpStream::connect(&addr, &handle); let tcp = TcpStream::connect(&addr, &handle);
let tcp = tcp.then(|res| h2client::handshake(res.unwrap())) let tcp = tcp
.then(|res| h2client::handshake(res.unwrap()))
.then(move |res| { .then(move |res| {
let (mut client, h2) = res.unwrap(); let (mut client, h2) = res.unwrap();

View File

@ -46,9 +46,7 @@ fn test_simple() {
let (item, reader) = srv.execute(reader.into_future()).unwrap(); let (item, reader) = srv.execute(reader.into_future()).unwrap();
assert_eq!( assert_eq!(
item, item,
Some(ws::Message::Binary( Some(ws::Message::Binary(Bytes::from_static(b"text").into()))
Bytes::from_static(b"text").into()
))
); );
writer.ping("ping"); writer.ping("ping");
@ -117,10 +115,7 @@ fn test_large_bin() {
writer.binary(data.clone()); writer.binary(data.clone());
let (item, r) = srv.execute(reader.into_future()).unwrap(); let (item, r) = srv.execute(reader.into_future()).unwrap();
reader = r; reader = r;
assert_eq!( assert_eq!(item, Some(ws::Message::Binary(Binary::from(data.clone()))));
item,
Some(ws::Message::Binary(Binary::from(data.clone())))
);
} }
} }
@ -231,19 +226,17 @@ fn test_ws_server_ssl() {
.set_certificate_chain_file("tests/cert.pem") .set_certificate_chain_file("tests/cert.pem")
.unwrap(); .unwrap();
let mut srv = test::TestServer::build() let mut srv = test::TestServer::build().ssl(builder.build()).start(|app| {
.ssl(builder.build()) app.handler(|req| {
.start(|app| { ws::start(
app.handler(|req| { req,
ws::start( Ws2 {
req, count: 0,
Ws2 { bin: false,
count: 0, },
bin: false, )
}, })
) });
})
});
let (mut reader, _writer) = srv.ws().unwrap(); let (mut reader, _writer) = srv.ws().unwrap();
let data = Some(ws::Message::Text("0".repeat(65_536))); let data = Some(ws::Message::Text("0".repeat(65_536)));

View File

@ -82,43 +82,40 @@ fn main() {
let perf = perf_counters.clone(); let perf = perf_counters.clone();
let addr = Arbiter::new(format!("test {}", t)); let addr = Arbiter::new(format!("test {}", t));
addr.do_send(actix::msgs::Execute::new( addr.do_send(actix::msgs::Execute::new(move || -> Result<(), ()> {
move || -> Result<(), ()> { for _ in 0..concurrency {
for _ in 0..concurrency { let pl2 = pl.clone();
let pl2 = pl.clone(); let perf2 = perf.clone();
let perf2 = perf.clone(); let ws2 = ws.clone();
let ws2 = ws.clone();
Arbiter::handle().spawn( Arbiter::handle().spawn(
ws::Client::new(&ws) ws::Client::new(&ws)
.write_buffer_capacity(0) .write_buffer_capacity(0)
.connect() .connect()
.map_err(|e| { .map_err(|e| {
println!("Error: {}", e); println!("Error: {}", e);
//Arbiter::system().do_send(actix::msgs::SystemExit(0)); //Arbiter::system().do_send(actix::msgs::SystemExit(0));
() ()
}) })
.map(move |(reader, writer)| { .map(move |(reader, writer)| {
let addr: Addr<Syn, _> = let addr: Addr<Syn, _> = ChatClient::create(move |ctx| {
ChatClient::create(move |ctx| { ChatClient::add_stream(reader, ctx);
ChatClient::add_stream(reader, ctx); ChatClient {
ChatClient { url: ws2,
url: ws2, conn: writer,
conn: writer, payload: pl2,
payload: pl2, bin: bin,
bin: bin, ts: time::precise_time_ns(),
ts: time::precise_time_ns(), perf_counters: perf2,
perf_counters: perf2, sent: 0,
sent: 0, max_payload_size: max_payload_size,
max_payload_size: max_payload_size, }
} });
}); }),
}), );
); }
} Ok(())
Ok(()) }));
},
));
} }
let res = sys.run(); let res = sys.run();
@ -126,10 +123,7 @@ fn main() {
fn parse_u64_default(input: Option<&str>, default: u64) -> u64 { fn parse_u64_default(input: Option<&str>, default: u64) -> u64 {
input input
.map(|v| { .map(|v| v.parse().expect(&format!("not a valid number: {}", v)))
v.parse()
.expect(&format!("not a valid number: {}", v))
})
.unwrap_or(default) .unwrap_or(default)
} }
@ -314,7 +308,8 @@ impl PerfCounters {
loop { loop {
let current = self.lat_max.load(Ordering::SeqCst); let current = self.lat_max.load(Ordering::SeqCst);
if current >= nanos if current >= nanos
|| self.lat_max || self
.lat_max
.compare_and_swap(current, nanos, Ordering::SeqCst) .compare_and_swap(current, nanos, Ordering::SeqCst)
== current == current
{ {